Narrow Python SDK root exports

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Ahmed Ibrahim
2026-05-09 10:35:44 +03:00
parent 8b8e868140
commit 692c08faf9
7 changed files with 126 additions and 55 deletions

View File

@@ -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-servers 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`.

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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()

View File

@@ -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",

View File

@@ -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 = {