From 692c08faf9345f5cd72db7f1c28407db9b779167 Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Sat, 9 May 2026 10:35:44 +0300 Subject: [PATCH] Narrow Python SDK root exports Co-authored-by: Codex --- sdk/python/README.md | 7 +- sdk/python/docs/api-reference.md | 22 +++-- sdk/python/docs/getting-started.md | 6 +- sdk/python/examples/11_cli_mini_app/async.py | 13 ++- sdk/python/examples/11_cli_mini_app/sync.py | 13 ++- sdk/python/src/codex_app_server/__init__.py | 33 ++----- .../tests/test_public_api_signatures.py | 87 ++++++++++++++++++- 7 files changed, 126 insertions(+), 55 deletions(-) diff --git a/sdk/python/README.md b/sdk/python/README.md index 0c5a8bd3a6..4f150bb692 100644 --- a/sdk/python/README.md +++ b/sdk/python/README.md @@ -3,8 +3,9 @@ Experimental Python SDK for `codex app-server` JSON-RPC v2 over stdio, with a small default surface optimized for real scripts and apps. The generated wire-model layer is sourced from the pinned `openai-codex-cli-bin` -runtime package and exposed as Pydantic models with snake_case Python fields -that serialize back to the app-server’s camelCase wire format. +runtime package. The package root exports the supported ergonomic SDK surface +plus selected public value types; generated transport modules are shipped for +runtime use but are not the supported public API. ## Install @@ -110,4 +111,4 @@ This supports the CI release flow: - Use context managers (`with Codex() as codex:`) to ensure shutdown. - Prefer `thread.run("...")` for the common case. Use `thread.turn(...)` when you need streaming, steering, or interrupt control. -- For transient overload, use `codex_app_server.retry.retry_on_overload`. +- For transient overload, import `retry_on_overload` from `codex_app_server`. diff --git a/sdk/python/docs/api-reference.md b/sdk/python/docs/api-reference.md index c7a763498d..8793929dcd 100644 --- a/sdk/python/docs/api-reference.md +++ b/sdk/python/docs/api-reference.md @@ -10,12 +10,12 @@ This SDK surface is experimental. Turn streams are routed by turn ID so one clie from codex_app_server import ( Codex, AsyncCodex, + AppServerConfig, RunResult, Thread, AsyncThread, TurnHandle, AsyncTurnHandle, - InitializeResponse, Input, InputItem, TextInput, @@ -24,13 +24,14 @@ from codex_app_server import ( SkillInput, MentionInput, TurnStatus, + retry_on_overload, ) -from codex_app_server.generated.v2_all import ThreadItem, ThreadTokenUsage ``` - Version: `codex_app_server.__version__` - Requires Python >= 3.10 -- Canonical generated app-server models live in `codex_app_server.generated.v2_all` +- The package root `__all__` is the supported public import surface; generated + transport modules are shipped for runtime use but are not re-exported. ## Codex (sync) @@ -165,20 +166,23 @@ InputItem = TextInput | ImageInput | LocalImageInput | SkillInput | MentionInput Input = list[InputItem] | InputItem ``` -## Generated Models +## Public Value Types -The SDK wrappers return and accept canonical generated app-server models wherever possible: +The package root exports the ergonomic SDK plus selected value types used by +public method signatures: ```python -from codex_app_server.generated.v2_all import ( +from codex_app_server import ( AskForApproval, - ThreadReadResponse, - Turn, - TurnStartParams, + ReasoningEffort, + SandboxPolicy, TurnStatus, ) ``` +Generated transport modules are shipped for SDK runtime use, but they are not +the supported root API. + ## Retry + errors ```python diff --git a/sdk/python/docs/getting-started.md b/sdk/python/docs/getting-started.md index 1794d39f70..6d5e4ab26c 100644 --- a/sdk/python/docs/getting-started.md +++ b/sdk/python/docs/getting-started.md @@ -95,12 +95,12 @@ with Codex() as codex: print(result.final_response) ``` -## 6) Generated models +## 6) Public value types -The convenience wrappers live at the package root, but the canonical app-server models live under: +The convenience wrappers and selected value types live at the package root: ```python -from codex_app_server.generated.v2_all import Turn, TurnStatus, ThreadReadResponse +from codex_app_server import AskForApproval, ReasoningEffort, SandboxPolicy, TurnStatus ``` ## 7) Next stops diff --git a/sdk/python/examples/11_cli_mini_app/async.py b/sdk/python/examples/11_cli_mini_app/async.py index 4216cf7820..2e0d2acba0 100644 --- a/sdk/python/examples/11_cli_mini_app/async.py +++ b/sdk/python/examples/11_cli_mini_app/async.py @@ -14,8 +14,6 @@ import asyncio from codex_app_server import ( AsyncCodex, TextInput, - ThreadTokenUsageUpdatedNotification, - TurnCompletedNotification, ) @@ -72,12 +70,13 @@ async def main() -> None: print(delta, end="", flush=True) printed_delta = True continue - if isinstance(payload, ThreadTokenUsageUpdatedNotification): - usage = payload.token_usage + if event.method == "thread/tokenUsage/updated": + usage = getattr(payload, "token_usage", None) continue - if isinstance(payload, TurnCompletedNotification): - status = payload.turn.status - error = payload.turn.error + if event.method == "turn/completed": + turn = getattr(payload, "turn", None) + status = getattr(turn, "status", None) + error = getattr(turn, "error", None) if printed_delta: print() diff --git a/sdk/python/examples/11_cli_mini_app/sync.py b/sdk/python/examples/11_cli_mini_app/sync.py index e961cfbcc3..7b4c37cb23 100644 --- a/sdk/python/examples/11_cli_mini_app/sync.py +++ b/sdk/python/examples/11_cli_mini_app/sync.py @@ -12,8 +12,6 @@ ensure_local_sdk_src() from codex_app_server import ( Codex, TextInput, - ThreadTokenUsageUpdatedNotification, - TurnCompletedNotification, ) print("Codex mini CLI. Type /exit to quit.") @@ -69,12 +67,13 @@ with Codex(config=runtime_config()) as codex: print(delta, end="", flush=True) printed_delta = True continue - if isinstance(payload, ThreadTokenUsageUpdatedNotification): - usage = payload.token_usage + if event.method == "thread/tokenUsage/updated": + usage = getattr(payload, "token_usage", None) continue - if isinstance(payload, TurnCompletedNotification): - status = payload.turn.status - error = payload.turn.error + if event.method == "turn/completed": + turn = getattr(payload, "turn", None) + status = getattr(turn, "status", None) + error = getattr(turn, "error", None) if printed_delta: print() diff --git a/sdk/python/src/codex_app_server/__init__.py b/sdk/python/src/codex_app_server/__init__.py index 281e9a472d..c950588325 100644 --- a/sdk/python/src/codex_app_server/__init__.py +++ b/sdk/python/src/codex_app_server/__init__.py @@ -1,5 +1,4 @@ -from .async_client import AsyncAppServerClient -from .client import AppServerClient, AppServerConfig +from .client import AppServerConfig from .errors import ( AppServerError, AppServerRpcError, @@ -15,6 +14,7 @@ from .errors import ( is_retryable_error, ) from .generated.v2_all import ( + ApprovalsReviewer, AskForApproval, Personality, PlanType, @@ -22,21 +22,14 @@ from .generated.v2_all import ( ReasoningSummary, SandboxMode, SandboxPolicy, - ThreadItem, - ThreadForkParams, - ThreadListParams, - ThreadResumeParams, + SortDirection, + ThreadListCwdFilter, ThreadSortKey, ThreadSource, ThreadSourceKind, - ThreadStartParams, - ThreadTokenUsageUpdatedNotification, - TurnCompletedNotification, - TurnStartParams, + ThreadStartSource, TurnStatus, - TurnSteerParams, ) -from .models import InitializeResponse from .api import ( AsyncCodex, AsyncThread, @@ -58,8 +51,6 @@ from ._version import __version__ __all__ = [ "__version__", - "AppServerClient", - "AsyncAppServerClient", "AppServerConfig", "Codex", "AsyncCodex", @@ -67,7 +58,6 @@ __all__ = [ "AsyncThread", "TurnHandle", "AsyncTurnHandle", - "InitializeResponse", "RunResult", "Input", "InputItem", @@ -76,9 +66,7 @@ __all__ = [ "LocalImageInput", "SkillInput", "MentionInput", - "ThreadItem", - "ThreadTokenUsageUpdatedNotification", - "TurnCompletedNotification", + "ApprovalsReviewer", "AskForApproval", "Personality", "PlanType", @@ -86,16 +74,13 @@ __all__ = [ "ReasoningSummary", "SandboxMode", "SandboxPolicy", - "ThreadStartParams", - "ThreadResumeParams", - "ThreadListParams", + "SortDirection", + "ThreadListCwdFilter", "ThreadSortKey", "ThreadSource", "ThreadSourceKind", - "ThreadForkParams", + "ThreadStartSource", "TurnStatus", - "TurnStartParams", - "TurnSteerParams", "retry_on_overload", "AppServerError", "TransportClosedError", diff --git a/sdk/python/tests/test_public_api_signatures.py b/sdk/python/tests/test_public_api_signatures.py index ddbcba0675..7ae5c706e9 100644 --- a/sdk/python/tests/test_public_api_signatures.py +++ b/sdk/python/tests/test_public_api_signatures.py @@ -7,9 +7,62 @@ from pathlib import Path from typing import Any import codex_app_server -from codex_app_server import AppServerConfig, RunResult +from codex_app_server import ( + AppServerConfig, + AsyncCodex, + AsyncThread, + Codex, + RunResult, + Thread, +) from codex_app_server.models import InitializeResponse -from codex_app_server.api import AsyncCodex, AsyncThread, Codex, Thread + +EXPECTED_ROOT_EXPORTS = [ + "__version__", + "AppServerConfig", + "Codex", + "AsyncCodex", + "Thread", + "AsyncThread", + "TurnHandle", + "AsyncTurnHandle", + "RunResult", + "Input", + "InputItem", + "TextInput", + "ImageInput", + "LocalImageInput", + "SkillInput", + "MentionInput", + "ApprovalsReviewer", + "AskForApproval", + "Personality", + "PlanType", + "ReasoningEffort", + "ReasoningSummary", + "SandboxMode", + "SandboxPolicy", + "SortDirection", + "ThreadListCwdFilter", + "ThreadSortKey", + "ThreadSource", + "ThreadSourceKind", + "ThreadStartSource", + "TurnStatus", + "retry_on_overload", + "AppServerError", + "TransportClosedError", + "JsonRpcError", + "AppServerRpcError", + "ParseError", + "InvalidRequestError", + "MethodNotFoundError", + "InvalidParamsError", + "InternalRpcError", + "ServerBusyError", + "RetryLimitExceededError", + "is_retryable_error", +] def _keyword_only_names(fn: object) -> list[str]: @@ -53,6 +106,36 @@ def test_package_includes_py_typed_marker() -> None: assert marker.is_file() +def test_package_root_exports_only_public_api() -> None: + """The package root should expose the supported SDK surface, not internals.""" + assert codex_app_server.__all__ == EXPECTED_ROOT_EXPORTS + assert { + name: hasattr(codex_app_server, name) for name in EXPECTED_ROOT_EXPORTS + } == {name: True for name in EXPECTED_ROOT_EXPORTS} + assert { + "AppServerClient": hasattr(codex_app_server, "AppServerClient"), + "AsyncAppServerClient": hasattr(codex_app_server, "AsyncAppServerClient"), + "InitializeResponse": hasattr(codex_app_server, "InitializeResponse"), + "ThreadStartParams": hasattr(codex_app_server, "ThreadStartParams"), + "TurnStartParams": hasattr(codex_app_server, "TurnStartParams"), + } == { + "AppServerClient": False, + "AsyncAppServerClient": False, + "InitializeResponse": False, + "ThreadStartParams": False, + "TurnStartParams": False, + } + + +def test_package_star_import_matches_public_api() -> None: + """Star imports should follow the same explicit public API list.""" + namespace: dict[str, object] = {} + exec("from codex_app_server import *", namespace) + + exported = set(namespace) - {"__builtins__"} + assert exported == set(EXPECTED_ROOT_EXPORTS) + + def test_generated_public_signatures_are_snake_case_and_typed() -> None: """Generated convenience methods should expose typed Pythonic keyword names.""" expected = {