[codex] Rename Python SDK AppServerConfig to CodexConfig (#24800)

## Why

`AppServerConfig` is exported as part of the ergonomic Python SDK
surface and passed to `Codex(...)` and `AsyncCodex(...)`. That name
exposes the underlying app-server transport at the same layer where
users are configuring the Codex client. `CodexConfig` makes the common
callsite read naturally and names the object it configures.

## What changed

- Renamed the public configuration dataclass from `AppServerConfig` to
`CodexConfig`.
- Updated `Codex`, `AsyncCodex`, and the transport clients to accept
`CodexConfig`.
- Updated binary-resolution messages, package exports, docs, examples,
and related coverage to use the new public name.

## API impact

```python
from openai_codex import Codex, CodexConfig

with Codex(config=CodexConfig(codex_bin="/path/to/codex")) as codex:
    ...
```

Callers should now import and construct `CodexConfig`; `AppServerConfig`
is no longer part of the Python SDK surface.

## Validation

- `uv run --frozen --extra dev ruff check src/openai_codex scripts
examples tests`
- Tests are deferred to online CI for this PR.
This commit is contained in:
Ahmed Ibrahim
2026-05-27 16:10:15 -07:00
committed by GitHub
Unverified
parent 090144e0ec
commit 0db49a7e6a
22 changed files with 127 additions and 126 deletions
+3 -3
View File
@@ -10,7 +10,7 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from typing import Any
from openai_codex import AppServerConfig
from openai_codex import CodexConfig
Json = dict[str, Any]
@@ -225,9 +225,9 @@ class AppServerHarness:
shutil.rmtree(self.codex_home, ignore_errors=True)
shutil.rmtree(self.workspace, ignore_errors=True)
def app_server_config(self) -> AppServerConfig:
def app_server_config(self) -> CodexConfig:
"""Build SDK config for an isolated pinned-runtime app-server process."""
return AppServerConfig(
return CodexConfig(
cwd=str(self.workspace),
env={
"CODEX_HOME": str(self.codex_home),
+2 -2
View File
@@ -5,14 +5,14 @@ import json
from app_server_harness import AppServerHarness
from openai_codex import AppServerConfig, Codex
from openai_codex import Codex, CodexConfig
from openai_codex.generated.v2_all import (
ChatgptAuthTokensLoginAccountParams,
LoginAccountParams,
)
def _app_server_config(harness: AppServerHarness) -> AppServerConfig:
def _app_server_config(harness: AppServerHarness) -> CodexConfig:
"""Build an isolated login config without inheriting ambient API-key auth."""
config = harness.app_server_config()
config.env = {**(config.env or {}), "OPENAI_API_KEY": ""}
@@ -114,7 +114,7 @@ def test_async_stream_routes_text_deltas_and_completion(tmp_path) -> None:
def test_low_level_sync_stream_text_uses_real_turn_routing(tmp_path) -> None:
"""AppServerClient.stream_text should stream through a real app-server turn."""
"""CodexClient.stream_text should stream through a real app-server turn."""
with AppServerHarness(tmp_path) as harness:
harness.responses.enqueue_sse(
streaming_response("low-sync-stream", "msg-low-sync-stream", ["fir", "st"])
@@ -669,7 +669,7 @@ def test_default_runtime_is_resolved_from_installed_runtime_package(
path_exists=lambda path: path == fake_binary,
)
config = client_module.AppServerConfig()
config = client_module.CodexConfig()
assert config.codex_bin is None
assert client_module.resolve_codex_bin(config, ops) == fake_binary
@@ -717,7 +717,7 @@ def test_explicit_codex_bin_override_takes_priority(tmp_path: Path) -> None:
path_exists=lambda path: path == explicit_binary,
)
config = client_module.AppServerConfig(codex_bin=str(explicit_binary))
config = client_module.CodexConfig(codex_bin=str(explicit_binary))
assert client_module.resolve_codex_bin(config, ops) == explicit_binary
@@ -732,7 +732,7 @@ def test_missing_runtime_package_requires_explicit_codex_bin() -> None:
)
with pytest.raises(FileNotFoundError, match="missing packaged runtime"):
client_module.resolve_codex_bin(client_module.AppServerConfig(), ops)
client_module.resolve_codex_bin(client_module.CodexConfig(), ops)
def test_broken_runtime_package_does_not_fall_back() -> None:
@@ -746,6 +746,6 @@ def test_broken_runtime_package_does_not_fall_back() -> None:
)
with pytest.raises(FileNotFoundError) as exc_info:
client_module.resolve_codex_bin(client_module.AppServerConfig(), ops)
client_module.resolve_codex_bin(client_module.CodexConfig(), ops)
assert str(exc_info.value) == ("missing packaged binary")
@@ -3,7 +3,7 @@ from __future__ import annotations
import asyncio
import time
from openai_codex.async_client import AsyncAppServerClient
from openai_codex.async_client import AsyncCodexClient
from openai_codex.generated.v2_all import (
TurnCompletedNotification,
)
@@ -15,7 +15,7 @@ def test_async_client_allows_concurrent_transport_calls() -> None:
async def scenario() -> int:
"""Run two blocking sync calls and report peak overlap."""
client = AsyncAppServerClient()
client = AsyncCodexClient()
active = 0
max_active = 0
@@ -40,7 +40,7 @@ def test_async_client_turn_notification_methods_delegate_to_sync_client() -> Non
async def scenario() -> tuple[list[tuple[str, str]], Notification, str]:
"""Record the sync-client calls made by async turn notification wrappers."""
client = AsyncAppServerClient()
client = AsyncCodexClient()
event = Notification(
method="unknown/direct",
payload=UnknownNotification(params={"turnId": "turn-1"}),
+9 -9
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
from pathlib import Path
from openai_codex.client import AppServerClient, _params_dict
from openai_codex.client import CodexClient, _params_dict
from openai_codex.generated.notification_registry import notification_turn_id
from openai_codex.generated.v2_all import (
AgentMessageDeltaNotification,
@@ -63,7 +63,7 @@ def test_thread_resume_response_accepts_auto_review_reviewer() -> None:
def test_notifications_are_typed_with_canonical_v2_methods() -> None:
client = AppServerClient()
client = CodexClient()
event = client._coerce_notification(
"thread/tokenUsage/updated",
{
@@ -94,7 +94,7 @@ def test_notifications_are_typed_with_canonical_v2_methods() -> None:
def test_unknown_notifications_fall_back_to_unknown_payloads() -> None:
client = AppServerClient()
client = CodexClient()
event = client._coerce_notification(
"unknown/notification",
{
@@ -110,7 +110,7 @@ def test_unknown_notifications_fall_back_to_unknown_payloads() -> None:
def test_invalid_notification_payload_falls_back_to_unknown() -> None:
client = AppServerClient()
client = CodexClient()
event = client._coerce_notification("thread/tokenUsage/updated", {"threadId": "missing"})
assert event.method == "thread/tokenUsage/updated"
@@ -144,7 +144,7 @@ def test_generated_notification_turn_id_handles_known_payload_shapes() -> None:
def test_turn_notification_router_demuxes_registered_turns() -> None:
"""The router should deliver out-of-order turn events to the matching queues."""
client = AppServerClient()
client = CodexClient()
client.register_turn_notifications("turn-1")
client.register_turn_notifications("turn-2")
@@ -187,7 +187,7 @@ def test_turn_notification_router_demuxes_registered_turns() -> None:
def test_client_reader_routes_interleaved_turn_notifications_by_turn_id() -> None:
"""Reader-loop routing should preserve order within each interleaved turn stream."""
client = AppServerClient()
client = CodexClient()
client.register_turn_notifications("turn-1")
client.register_turn_notifications("turn-2")
@@ -266,7 +266,7 @@ def test_client_reader_routes_interleaved_turn_notifications_by_turn_id() -> Non
def test_turn_notification_router_buffers_events_before_registration() -> None:
"""Early turn events should be replayed once their TurnHandle registers."""
client = AppServerClient()
client = CodexClient()
client._router.route_notification(
client._coerce_notification(
"item/agentMessage/delta",
@@ -291,7 +291,7 @@ def test_turn_notification_router_buffers_events_before_registration() -> None:
def test_turn_notification_router_clears_unregistered_turn_when_completed() -> None:
"""A completed unregistered turn should not leave a pending queue behind."""
client = AppServerClient()
client = CodexClient()
client._router.route_notification(
client._coerce_notification(
"item/agentMessage/delta",
@@ -318,7 +318,7 @@ def test_turn_notification_router_clears_unregistered_turn_when_completed() -> N
def test_turn_notification_router_routes_unknown_turn_notifications() -> None:
"""Unknown notifications should still route when their raw params carry a turn id."""
client = AppServerClient()
client = CodexClient()
client.register_turn_notifications("turn-1")
client.register_turn_notifications("turn-2")
@@ -52,7 +52,7 @@ def test_codex_init_failure_closes_client(monkeypatch: pytest.MonkeyPatch) -> No
self._closed = True
closed.append(True)
monkeypatch.setattr(public_api_module, "AppServerClient", FakeClient)
monkeypatch.setattr(public_api_module, "CodexClient", FakeClient)
with pytest.raises(RuntimeError, match="missing required metadata"):
Codex()
+12 -12
View File
@@ -11,11 +11,11 @@ import openai_codex
import openai_codex.types as public_types
from openai_codex import (
ApprovalMode,
AppServerConfig,
AsyncCodex,
AsyncThread,
AsyncTurnHandle,
Codex,
CodexConfig,
Sandbox,
Thread,
TurnHandle,
@@ -26,7 +26,7 @@ from openai_codex.types import InitializeResponse
EXPECTED_ROOT_EXPORTS = [
"__version__",
"AppServerConfig",
"CodexConfig",
"Codex",
"AsyncCodex",
"ApprovalMode",
@@ -49,10 +49,10 @@ EXPECTED_ROOT_EXPORTS = [
"SkillInput",
"MentionInput",
"retry_on_overload",
"AppServerError",
"CodexError",
"TransportClosedError",
"JsonRpcError",
"AppServerRpcError",
"CodexRpcError",
"ParseError",
"InvalidRequestError",
"MethodNotFoundError",
@@ -129,9 +129,9 @@ def _assert_no_any_annotations(fn: object) -> None:
raise AssertionError(f"{fn} has public return annotation typed as Any")
def test_root_exports_app_server_config() -> None:
def test_root_exports_codex_config() -> None:
"""The root package should expose the process configuration object."""
assert AppServerConfig.__name__ == "AppServerConfig"
assert CodexConfig.__name__ == "CodexConfig"
def test_root_exports_turn_result() -> None:
@@ -208,7 +208,7 @@ def test_package_and_default_client_versions_follow_project_version() -> None:
pyproject = tomllib.loads(pyproject_path.read_text())
assert openai_codex.__version__ == pyproject["project"]["version"]
assert AppServerConfig().client_version == openai_codex.__version__
assert CodexConfig().client_version == openai_codex.__version__
def test_package_includes_py_typed_marker() -> None:
@@ -224,16 +224,16 @@ def test_package_root_exports_only_public_api() -> None:
EXPECTED_ROOT_EXPORTS, True
)
assert {
"AppServerClient": hasattr(openai_codex, "AppServerClient"),
"AsyncAppServerClient": hasattr(openai_codex, "AsyncAppServerClient"),
"CodexClient": hasattr(openai_codex, "CodexClient"),
"AsyncCodexClient": hasattr(openai_codex, "AsyncCodexClient"),
"InitializeResponse": hasattr(openai_codex, "InitializeResponse"),
"ThreadStartParams": hasattr(openai_codex, "ThreadStartParams"),
"TurnStartParams": hasattr(openai_codex, "TurnStartParams"),
"TurnCompletedNotification": hasattr(openai_codex, "TurnCompletedNotification"),
"TurnStatus": hasattr(openai_codex, "TurnStatus"),
} == {
"AppServerClient": False,
"AsyncAppServerClient": False,
"CodexClient": False,
"AsyncCodexClient": False,
"InitializeResponse": False,
"ThreadStartParams": False,
"TurnStartParams": False,
@@ -252,7 +252,7 @@ def test_package_star_import_matches_public_api() -> None:
def test_types_module_exports_curated_public_types() -> None:
"""The public type module should be the supported place for app-server models."""
"""The public type module should expose Codex protocol models."""
assert public_types.__all__ == EXPECTED_TYPES_EXPORTS
assert {name: hasattr(public_types, name) for name in EXPECTED_TYPES_EXPORTS} == dict.fromkeys(
EXPECTED_TYPES_EXPORTS, True