[codex] Add independent beta release for the Python SDK (#24828)

## Why

`openai-codex` needs a beta release lifecycle without requiring beta
releases of its pinned runtime package. Previously, SDK staging rewrote
its runtime dependency to the SDK version, which made an SDK-only beta
impossible.

## What changed

- Set the initial SDK beta version to `0.1.0b1` and pin it to published
stable `openai-codex-cli-bin==0.132.0`.
- Decoupled SDK release staging from runtime versioning so it preserves
the reviewed exact runtime pin.
- Added a `python-v*` tag workflow that builds and publishes only
`openai-codex` through PyPI trusted publishing.
- Removed the Beta classifier from runtime package metadata for future
runtime publications.
- Regenerated protocol-derived SDK models from the selected stable
runtime package.

`0.132.0` is the newest stable runtime admitted by the checked-in
dependency date fence and retains the Linux wheel family currently used
by SDK CI.

## Release setup

Before pushing `python-v0.1.0b1`, configure PyPI trusted publishing for
the `openai-codex` project with workflow `python-sdk-release.yml`,
environment `pypi`, and job `publish-python-sdk`.

## Validation

- `uv run --frozen --extra dev ruff check src/openai_codex scripts
examples tests`
- Parsed `.github/workflows/python-sdk-release.yml` with PyYAML.
- Built staged release artifacts locally:
`openai_codex-0.1.0b1-py3-none-any.whl` and
`openai_codex-0.1.0b1.tar.gz`.
- Verified wheel metadata pins `openai-codex-cli-bin==0.132.0`.
- Tests are deferred to online CI for this PR.
This commit is contained in:
Ahmed Ibrahim
2026-05-27 17:57:51 -07:00
committed by GitHub
Unverified
parent 304d15cab0
commit 4d0c4cd058
9 changed files with 335 additions and 292 deletions
+11 -61
View File
@@ -216,25 +216,8 @@ def _rewrite_project_name(pyproject_text: str, name: str) -> str:
return updated
def _rewrite_sdk_runtime_dependency(pyproject_text: str, runtime_version: str) -> str:
match = re.search(r"^dependencies = \[(.*?)\]$", pyproject_text, flags=re.MULTILINE)
if match is None:
raise RuntimeError("Could not find dependencies array in sdk/python/pyproject.toml")
raw_items = [item.strip() for item in match.group(1).split(",") if item.strip()]
raw_items = [
item
for item in raw_items
if RUNTIME_DISTRIBUTION_NAME.removeprefix("openai-") not in item
and RUNTIME_DISTRIBUTION_NAME not in item
]
raw_items.append(f'"{RUNTIME_DISTRIBUTION_NAME}=={runtime_version}"')
replacement = "dependencies = [\n " + ",\n ".join(raw_items) + ",\n]"
return pyproject_text[: match.start()] + replacement + pyproject_text[match.end() :]
def stage_python_sdk_package(staging_dir: Path, codex_version: str) -> Path:
package_version = normalize_codex_version(codex_version)
def stage_python_sdk_package(staging_dir: Path, sdk_version: str) -> Path:
package_version = normalize_codex_version(sdk_version)
_copy_package_tree(sdk_root(), staging_dir)
sdk_bin_dir = staging_dir / "src" / "openai_codex" / "bin"
if sdk_bin_dir.exists():
@@ -244,7 +227,6 @@ def stage_python_sdk_package(staging_dir: Path, codex_version: str) -> Path:
pyproject_text = pyproject_path.read_text()
pyproject_text = _rewrite_project_name(pyproject_text, SDK_DISTRIBUTION_NAME)
pyproject_text = _rewrite_project_version(pyproject_text, package_version)
pyproject_text = _rewrite_sdk_runtime_dependency(pyproject_text, package_version)
pyproject_path.write_text(pyproject_text)
return staging_dir
@@ -1229,28 +1211,20 @@ def build_parser() -> argparse.ArgumentParser:
stage_sdk_parser = subparsers.add_parser(
"stage-sdk",
help="Stage a releasable SDK package pinned to a runtime version",
help="Stage a releasable SDK package while preserving its reviewed runtime pin",
)
stage_sdk_parser.add_argument(
"staging_dir",
type=Path,
help="Output directory for the staged SDK package",
)
stage_sdk_parser.add_argument(
"--codex-version",
help=(
"Codex release version to write into the staged SDK package and exact "
f"{RUNTIME_DISTRIBUTION_NAME} dependency. Accepts PEP 440 versions "
"or release tags such as rust-v0.116.0-alpha.1."
),
)
stage_sdk_parser.add_argument(
"--runtime-version",
help=argparse.SUPPRESS,
)
stage_sdk_parser.add_argument(
"--sdk-version",
help=argparse.SUPPRESS,
required=True,
help=(
"Python SDK release version to write into the staged package. "
"Accepts PEP 440 versions such as 0.1.0b1."
),
)
stage_runtime_parser = subparsers.add_parser(
@@ -1269,15 +1243,12 @@ def build_parser() -> argparse.ArgumentParser:
)
stage_runtime_parser.add_argument(
"--codex-version",
required=True,
help=(
"Codex release version to write into the staged runtime package. "
"Accepts PEP 440 versions or release tags such as rust-v0.116.0-alpha.1."
),
)
stage_runtime_parser.add_argument(
"--runtime-version",
help=argparse.SUPPRESS,
)
stage_runtime_parser.add_argument(
"--platform-tag",
help=(
@@ -1301,40 +1272,19 @@ def default_cli_ops() -> CliOps:
)
def _resolve_codex_version(args: argparse.Namespace) -> str:
versions = [
value
for value in (
getattr(args, "codex_version", None),
getattr(args, "runtime_version", None),
getattr(args, "sdk_version", None),
)
if value is not None
]
if not versions:
raise RuntimeError("Pass --codex-version to stage Python release artifacts")
normalized_versions = [normalize_codex_version(version) for version in versions]
if len(set(normalized_versions)) != 1:
raise RuntimeError("SDK and runtime package versions must match; pass one --codex-version")
return normalized_versions[0]
def run_command(args: argparse.Namespace, ops: CliOps) -> None:
if args.command == "generate-types":
ops.generate_types()
elif args.command == "stage-sdk":
codex_version = _resolve_codex_version(args)
ops.generate_types()
ops.stage_python_sdk_package(
args.staging_dir,
codex_version,
normalize_codex_version(args.sdk_version),
)
elif args.command == "stage-runtime":
codex_version = _resolve_codex_version(args)
ops.stage_python_runtime_package(
args.staging_dir,
codex_version,
normalize_codex_version(args.codex_version),
args.package_archive.resolve(),
args.platform_tag,
)