[codex] Split Python SDK helper logic (#22939)

## Summary
- Move approval-mode mapping into
`sdk/python/src/openai_codex/_approval_mode.py`.
- Move initialize metadata parsing and normalization into
`sdk/python/src/openai_codex/_initialize_metadata.py`.
- Keep the public `ApprovalMode` export stable and retarget direct
metadata helper coverage.

## Integration coverage
- Add an app-server harness smoke that exercises sync and async SDK
initialization plus thread creation.

## Validation
- Local tests were not run per repo guidance. CI should validate this
branch once the PR is online.
This commit is contained in:
Ahmed Ibrahim
2026-05-16 19:47:51 +03:00
committed by GitHub
parent 108234b5eb
commit a280248021
5 changed files with 173 additions and 102 deletions

View File

@@ -39,6 +39,61 @@ def test_thread_set_name_and_read(tmp_path) -> None:
}
def test_sync_and_async_initialization_round_trip_metadata(tmp_path) -> None:
"""Public clients should initialize and start threads through app-server."""
async def async_scenario(harness: AppServerHarness) -> dict[str, object]:
async with AsyncCodex(config=harness.app_server_config()) as codex:
thread = await codex.thread_start()
server = codex.metadata.serverInfo
return {
"thread_id": thread.id,
"user_agent": codex.metadata.userAgent,
"server_name": None if server is None else server.name,
"server_version": None if server is None else server.version,
}
with AppServerHarness(tmp_path) as harness:
with Codex(config=harness.app_server_config()) as codex:
thread = codex.thread_start()
server = codex.metadata.serverInfo
sync_summary = {
"thread_id": thread.id,
"user_agent": codex.metadata.userAgent,
"server_name": None if server is None else server.name,
"server_version": None if server is None else server.version,
}
async_summary = asyncio.run(async_scenario(harness))
assert {
"sync": {
"thread_id_present": bool(sync_summary["thread_id"]),
"user_agent_present": bool(sync_summary["user_agent"]),
"server_name_present": bool(sync_summary["server_name"]),
"server_version_present": bool(sync_summary["server_version"]),
},
"async": {
"thread_id_present": bool(async_summary["thread_id"]),
"user_agent_present": bool(async_summary["user_agent"]),
"server_name_present": bool(async_summary["server_name"]),
"server_version_present": bool(async_summary["server_version"]),
},
} == {
"sync": {
"thread_id_present": True,
"user_agent_present": True,
"server_name_present": True,
"server_version_present": True,
},
"async": {
"thread_id_present": True,
"user_agent_present": True,
"server_name_present": True,
"server_version_present": True,
},
}
def test_thread_list_filters_archived_threads(tmp_path) -> None:
"""Thread listing should reflect archive state through app-server."""
with AppServerHarness(tmp_path) as harness:

View File

@@ -18,6 +18,7 @@ from openai_codex import (
RunResult,
Thread,
)
from openai_codex._initialize_metadata import validate_initialize_metadata
from openai_codex.types import InitializeResponse
EXPECTED_ROOT_EXPORTS = [
@@ -444,7 +445,7 @@ def test_lifecycle_methods_are_codex_scoped() -> None:
def test_initialize_metadata_parses_user_agent_shape() -> None:
"""Initialize metadata should accept the legacy user-agent-only payload shape."""
payload = InitializeResponse.model_validate({"userAgent": "codex-cli/1.2.3"})
parsed = Codex._validate_initialize(payload)
parsed = validate_initialize_metadata(payload)
assert parsed is payload
assert parsed.userAgent == "codex-cli/1.2.3"
assert parsed.serverInfo is not None
@@ -455,7 +456,7 @@ def test_initialize_metadata_parses_user_agent_shape() -> None:
def test_initialize_metadata_requires_non_empty_information() -> None:
"""Initialize metadata should fail when the runtime gives no identity signal."""
try:
Codex._validate_initialize(InitializeResponse.model_validate({}))
validate_initialize_metadata(InitializeResponse.model_validate({}))
except RuntimeError as exc:
assert "missing required metadata" in str(exc)
else: