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
Unverified
parent 63a72e6b78
commit 0b4f86095c
10 changed files with 425 additions and 213 deletions
+43 -2
View File
@@ -1,5 +1,3 @@
from __future__ import annotations
import json
import os
import subprocess
@@ -103,6 +101,45 @@ def _installed_codex_path() -> Path:
return bundled_codex_path()
def _installed_codex_path_dirs() -> tuple[Path, ...]:
try:
from codex_cli_bin import bundled_path_dir
except (ImportError, AttributeError):
return ()
path_dir = bundled_path_dir()
return (path_dir,) if path_dir is not None else ()
def _prepend_path_dirs(env: dict[str, str], path_dirs: tuple[Path, ...]) -> None:
if not path_dirs:
return
path_key = _path_env_key(env)
if os.name == "nt":
for key in list(env):
if key.upper() == "PATH" and key != path_key:
env.pop(key)
path_sep = os.pathsep
existing_path = env.get(path_key, "")
path_dir_values = [str(path_dir) for path_dir in path_dirs]
existing_entries = [
entry for entry in existing_path.split(path_sep) if entry and entry not in path_dir_values
]
env[path_key] = path_sep.join([*path_dir_values, *existing_entries])
def _path_env_key(env: dict[str, str]) -> str:
if os.name != "nt":
return "PATH"
matching_keys = [key for key in env if key.upper() == "PATH"]
if "Path" in matching_keys:
return "Path"
return matching_keys[-1] if matching_keys else "PATH"
@dataclass(frozen=True)
class CodexBinResolverOps:
installed_codex_path: Callable[[], Path]
@@ -174,10 +211,13 @@ class AppServerClient:
if self._proc is not None:
return
path_dirs: tuple[Path, ...] = ()
if self.config.launch_args_override is not None:
args = list(self.config.launch_args_override)
else:
codex_bin = _resolve_codex_bin(self.config)
if self.config.codex_bin is None:
path_dirs = _installed_codex_path_dirs()
args = [str(codex_bin)]
for kv in self.config.config_overrides:
args.extend(["--config", kv])
@@ -186,6 +226,7 @@ class AppServerClient:
env = os.environ.copy()
if self.config.env:
env.update(self.config.env)
_prepend_path_dirs(env, path_dirs)
self._proc = subprocess.Popen(
args,