sdk: launch packaged Codex runtimes (#23786)

## Why

The Python and TypeScript SDKs launch the native Codex runtime directly,
so they need to consume the same package artifact shape that release
jobs now produce. The runtime wheel should be built from the canonical
Codex package archive rather than reconstructing a parallel layout from
loose binaries.

## What Changed

- Stage `openai-codex-cli-bin` by extracting
`codex-package-<target>.tar.gz` into `src/codex_cli_bin` and validating
the expected package layout.
- Update release workflows to pass the generated package archive into
`stage-runtime` instead of the temporary package directory.
- Update Python runtime setup to download `codex-package-*.tar.gz`
release assets directly.
- Expose Python runtime helpers for the bundled package directory and
`codex-path`, and prepend that path when `openai_codex` launches the
installed runtime without duplicating Windows `Path`/`PATH` keys.
- Teach the TypeScript SDK to resolve package-layout optional
dependencies while keeping the existing npm fallback layout, and
preserve the existing Windows path variable casing when prepending
`codex-path`.

## Test Plan

- `python3 -m py_compile sdk/python/scripts/update_sdk_artifacts.py
sdk/python/_runtime_setup.py sdk/python/src/openai_codex/client.py
sdk/python-runtime/src/codex_cli_bin/__init__.py`
- `uv run --frozen --project sdk/python --extra dev ruff check
sdk/python/scripts/update_sdk_artifacts.py sdk/python/_runtime_setup.py
sdk/python/src/openai_codex/client.py
sdk/python/tests/test_artifact_workflow_and_binaries.py
sdk/python-runtime/src/codex_cli_bin/__init__.py`
- `uv run --frozen --project sdk/python --extra dev pytest
sdk/python/tests/test_artifact_workflow_and_binaries.py`
- `pnpm eslint src/exec.ts tests/exec.test.ts`
- `pnpm test --runInBand tests/exec.test.ts`
This commit is contained in:
Michael Bolin
2026-05-20 18:01:22 -07:00
committed by GitHub
parent 63a72e6b78
commit 0b4f86095c
10 changed files with 425 additions and 213 deletions

View File

@@ -1,5 +1,3 @@
from __future__ import annotations
import importlib
import importlib.metadata
import importlib.util
@@ -10,11 +8,9 @@ import re
import shutil
import subprocess
import sys
import tarfile
import tempfile
import urllib.error
import urllib.request
import zipfile
from pathlib import Path
PACKAGE_NAME = "openai-codex-cli-bin"
@@ -65,11 +61,10 @@ def ensure_runtime_package_installed(
with tempfile.TemporaryDirectory(prefix="codex-python-runtime-") as temp_root_str:
temp_root = Path(temp_root_str)
archive_path = _download_release_archive(requested_version, temp_root)
runtime_binary = _extract_runtime_binary(archive_path, temp_root)
staged_runtime_dir = _stage_runtime_package(
sdk_python_dir,
requested_version,
runtime_binary,
archive_path,
temp_root / "runtime-stage",
)
_install_runtime_package(python_executable, staged_runtime_dir, install_target)
@@ -98,19 +93,19 @@ def platform_asset_name() -> str:
if system == "darwin":
if machine in {"arm64", "aarch64"}:
return "codex-aarch64-apple-darwin.tar.gz"
return "codex-package-aarch64-apple-darwin.tar.gz"
if machine in {"x86_64", "amd64"}:
return "codex-x86_64-apple-darwin.tar.gz"
return "codex-package-x86_64-apple-darwin.tar.gz"
elif system == "linux":
if machine in {"aarch64", "arm64"}:
return "codex-aarch64-unknown-linux-musl.tar.gz"
return "codex-package-aarch64-unknown-linux-musl.tar.gz"
if machine in {"x86_64", "amd64"}:
return "codex-x86_64-unknown-linux-musl.tar.gz"
return "codex-package-x86_64-unknown-linux-musl.tar.gz"
elif system == "windows":
if machine in {"aarch64", "arm64"}:
return "codex-aarch64-pc-windows-msvc.exe.zip"
return "codex-package-aarch64-pc-windows-msvc.tar.gz"
if machine in {"x86_64", "amd64"}:
return "codex-x86_64-pc-windows-msvc.exe.zip"
return "codex-package-x86_64-pc-windows-msvc.tar.gz"
raise RuntimeSetupError(
f"Unsupported runtime artifact platform: system={platform.system()!r}, "
@@ -118,10 +113,6 @@ def platform_asset_name() -> str:
)
def runtime_binary_name() -> str:
return "codex.exe" if platform.system().lower() == "windows" else "codex"
def _installed_runtime_version(python_executable: str | Path) -> str | None:
snippet = (
"import importlib.metadata, json, sys\n"
@@ -260,49 +251,17 @@ def _download_release_archive(version: str, temp_root: Path) -> Path:
return archive_path
def _extract_runtime_binary(archive_path: Path, temp_root: Path) -> Path:
extract_dir = temp_root / "extracted"
extract_dir.mkdir(parents=True, exist_ok=True)
if archive_path.name.endswith(".tar.gz"):
with tarfile.open(archive_path, "r:gz") as tar:
try:
tar.extractall(extract_dir, filter="data")
except TypeError:
tar.extractall(extract_dir)
elif archive_path.suffix == ".zip":
with zipfile.ZipFile(archive_path) as zip_file:
zip_file.extractall(extract_dir)
else:
raise RuntimeSetupError(f"Unsupported release archive format: {archive_path.name}")
binary_name = runtime_binary_name()
archive_stem = archive_path.name.removesuffix(".tar.gz").removesuffix(".zip")
candidates = [
path
for path in extract_dir.rglob("*")
if path.is_file()
and (
path.name == binary_name or path.name == archive_stem or path.name.startswith("codex-")
)
]
if not candidates:
raise RuntimeSetupError(
f"Failed to find {binary_name} in extracted runtime archive {archive_path.name}."
)
return candidates[0]
def _stage_runtime_package(
sdk_python_dir: Path,
runtime_version: str,
runtime_binary: Path,
runtime_package_archive: Path,
staging_dir: Path,
) -> Path:
script_module = _load_update_script_module(sdk_python_dir)
return script_module.stage_python_runtime_package( # type: ignore[no-any-return]
staging_dir,
runtime_version,
runtime_binary.resolve(),
runtime_package_archive.resolve(),
)