Generate Python SDK types from pinned runtime

Make the SDK artifact generator fetch schema from the pinned runtime package, regenerate the checked-in Python types from that schema, and assert generated artifacts stay up to date.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Ahmed Ibrahim
2026-05-09 10:03:54 +03:00
parent c24694bdb0
commit b7635f4d77
11 changed files with 722 additions and 519 deletions

View File

@@ -52,7 +52,8 @@ def test_generate_types_wires_all_generation_steps() -> None:
(
node
for node in tree.body
if isinstance(node, ast.FunctionDef) and node.name == "generate_types"
if isinstance(node, ast.FunctionDef)
and node.name == "generate_types_from_schema_dir"
),
None,
)
@@ -72,19 +73,17 @@ def test_generate_types_wires_all_generation_steps() -> None:
]
def test_schema_normalization_only_flattens_string_literal_oneofs() -> None:
def _load_runtime_schema_bundle(tmp_path: Path) -> dict:
script = _load_update_script_module()
schema = json.loads(
(
ROOT.parent.parent
/ "codex-rs"
/ "app-server-protocol"
/ "schema"
/ "json"
/ "codex_app_server_protocol.v2.schemas.json"
).read_text()
)
schema_dir = script.generate_schema_from_pinned_runtime(tmp_path / "schema")
return json.loads(script.schema_bundle_path(schema_dir).read_text())
def test_schema_normalization_only_flattens_string_literal_oneofs(
tmp_path: Path,
) -> None:
script = _load_update_script_module()
schema = _load_runtime_schema_bundle(tmp_path)
definitions = schema["definitions"]
flattened = [
name
@@ -94,27 +93,22 @@ def test_schema_normalization_only_flattens_string_literal_oneofs() -> None:
]
assert flattened == [
"AuthMode",
"CommandExecOutputStream",
"ExperimentalFeatureStage",
"InputModality",
"MessagePhase",
"TurnItemsView",
"PluginAvailability",
"AuthMode",
"InputModality",
"ExperimentalFeatureStage",
"CommandExecOutputStream",
"ProcessOutputStream",
]
def test_python_codegen_schema_annotation_adds_stable_variant_titles() -> None:
def test_python_codegen_schema_annotation_adds_stable_variant_titles(
tmp_path: Path,
) -> None:
script = _load_update_script_module()
schema = json.loads(
(
ROOT.parent.parent
/ "codex-rs"
/ "app-server-protocol"
/ "schema"
/ "json"
/ "codex_app_server_protocol.v2.schemas.json"
).read_text()
)
schema = _load_runtime_schema_bundle(tmp_path)
script._annotate_schema(schema)
definitions = schema["definitions"]
@@ -186,6 +180,25 @@ def test_runtime_distribution_name_is_consistent() -> None:
)
def test_source_sdk_package_pins_published_runtime() -> None:
"""The source package metadata should pin the runtime wheel that ships schemas."""
script = _load_update_script_module()
pyproject = tomllib.loads((ROOT / "pyproject.toml").read_text())
assert {
"sdk_version": pyproject["project"]["version"],
"runtime_pin": script.pinned_runtime_version(),
"dependencies": pyproject["project"]["dependencies"],
} == {
"sdk_version": "0.131.0a4",
"runtime_pin": "0.131.0a4",
"dependencies": [
"pydantic>=2.12",
"openai-codex-cli-bin==0.131.0a4",
],
}
def test_release_metadata_retries_without_invalid_auth(
monkeypatch: pytest.MonkeyPatch,
) -> None:
@@ -212,22 +225,6 @@ def test_release_metadata_retries_without_invalid_auth(
assert authorizations == ["Bearer invalid-token", None]
def test_source_sdk_package_pins_published_runtime() -> None:
"""The source package metadata should pin the runtime wheel that ships schemas."""
pyproject = tomllib.loads((ROOT / "pyproject.toml").read_text())
assert {
"sdk_version": pyproject["project"]["version"],
"dependencies": pyproject["project"]["dependencies"],
} == {
"sdk_version": "0.131.0a4",
"dependencies": [
"pydantic>=2.12",
"openai-codex-cli-bin==0.131.0a4",
],
}
def test_runtime_setup_uses_pep440_package_version_and_codex_release_tags() -> None:
"""The SDK uses PEP 440 package pins and converts only when fetching releases."""
runtime_setup = _load_runtime_setup_module()
@@ -422,9 +419,7 @@ def test_runtime_resource_binaries_are_included_by_wheel_config(
pyproject = tomllib.loads((staged / "pyproject.toml").read_text())
assert {
"include": pyproject["tool"]["hatch"]["build"]["targets"]["wheel"]["include"],
"helper": (
staged / "src" / "codex_cli_bin" / "bin" / "helper"
).read_text(),
"helper": (staged / "src" / "codex_cli_bin" / "bin" / "helper").read_text(),
} == {
"include": ["src/codex_cli_bin/bin/**"],
"helper": "fake helper\n",

View File

@@ -66,6 +66,7 @@ def test_thread_resume_response_accepts_auto_review_reviewer() -> None:
"id": "thread-1",
"modelProvider": "openai",
"preview": "",
"sessionId": "session-1",
"source": "cli",
"status": {"type": "idle"},
"turns": [],

View File

@@ -1,5 +1,6 @@
from __future__ import annotations
import importlib.metadata
import os
import subprocess
import sys
@@ -29,15 +30,19 @@ def _snapshot_target(root: Path, rel_path: Path) -> dict[str, bytes] | bytes | N
def _snapshot_targets(root: Path) -> dict[str, dict[str, bytes] | bytes | None]:
return {
str(rel_path): _snapshot_target(root, rel_path) for rel_path in GENERATED_TARGETS
str(rel_path): _snapshot_target(root, rel_path)
for rel_path in GENERATED_TARGETS
}
def test_generated_files_are_up_to_date():
before = _snapshot_targets(ROOT)
# Regenerate contract artifacts via single maintenance entrypoint.
# Regenerate contract artifacts via the pinned runtime package, not a local
# app-server binary from the checkout or CI environment.
assert importlib.metadata.version("openai-codex-cli-bin") == "0.131.0a4"
env = os.environ.copy()
env.pop("CODEX_EXEC_PATH", None)
python_bin = str(Path(sys.executable).parent)
env["PATH"] = f"{python_bin}{os.pathsep}{env.get('PATH', '')}"

View File

@@ -93,6 +93,7 @@ def _item_completed_notification(
method="item/completed",
payload=ItemCompletedNotification.model_validate(
{
"completedAtMs": 1,
"item": item,
"threadId": thread_id,
"turnId": turn_id,

View File

@@ -70,6 +70,7 @@ def test_generated_public_signatures_are_snake_case_and_typed() -> None:
"service_name",
"service_tier",
"session_start_source",
"thread_source",
],
Codex.thread_list: [
"archived",
@@ -108,6 +109,7 @@ def test_generated_public_signatures_are_snake_case_and_typed() -> None:
"model_provider",
"sandbox",
"service_tier",
"thread_source",
],
Thread.turn: [
"approval_policy",
@@ -148,6 +150,7 @@ def test_generated_public_signatures_are_snake_case_and_typed() -> None:
"service_name",
"service_tier",
"session_start_source",
"thread_source",
],
AsyncCodex.thread_list: [
"archived",
@@ -186,6 +189,7 @@ def test_generated_public_signatures_are_snake_case_and_typed() -> None:
"model_provider",
"sandbox",
"service_tier",
"thread_source",
],
AsyncThread.turn: [
"approval_policy",