Files
codex/sdk/python/docs/api-reference.md
Ahmed Ibrahim 8afccec87a Expose Python SDK history selection and per-turn options (#44084)
## Why

Python callers need control over response history loading and a way to override the service tier for one turn. These options also need runtime compatibility checks to prevent older CLIs from silently ignoring them.

## What changed

- Add `include_turns` to sync and async thread resume/fork methods. Omission preserves server defaults; `False` skips response history loading without changing model context.
- Add `turn_service_tier` and `source` to sync and async `run()` and `turn()`, and generate both methods together to keep their options aligned.
- Require CLI `0.151.0` or newer when sending the new options, with lazy schema checks for unversioned local builds.
- Pin the bundled runtime dependency to `0.153.4` and reject unsupported runtime versions during SDK packaging.

## Testing

Add coverage for option forwarding, history flag omission and inversion, runtime version checks, cached schema probing, and packaging compatibility. Extend app-server and installed SDK smoke tests to exercise the new options.

GitOrigin-RevId: 4bcc9cff687b0651e67852e7c080df7fadac6d76
2026-09-09 06:40:56 +00:00

339 lines
13 KiB
Markdown

# OpenAI Codex Python SDK - API Reference
Public surface of `openai_codex` for Codex workflows.
Turn streams are routed by turn ID so one client can consume multiple active turns concurrently.
Thread starts default to `ApprovalMode.auto_review`; turn starts accept an optional `approval_mode` override.
## Package Entry
```python
from openai_codex import (
Codex,
AsyncCodex,
CodexConfig,
ApprovalMode,
Sandbox,
ChatgptLoginHandle,
DeviceCodeLoginHandle,
AsyncChatgptLoginHandle,
AsyncDeviceCodeLoginHandle,
Thread,
AsyncThread,
TurnHandle,
AsyncTurnHandle,
TurnResult,
Input,
InputItem,
RunInput,
TextInput,
ImageInput,
LocalImageInput,
SkillInput,
MentionInput,
)
from openai_codex.types import (
Account,
AccountLoginCompletedNotification,
CancelLoginAccountResponse,
CancelLoginAccountStatus,
GetAccountResponse,
InitializeResponse,
ThreadItem,
ThreadTokenUsage,
TurnError,
TurnStatus,
)
```
- Version: `openai_codex.__version__`
- Requires Python >= 3.10
- Public Codex protocol value and event types live in `openai_codex.types`
## Codex (sync)
```python
Codex(config: CodexConfig | None = None)
```
Properties/methods:
- `metadata -> InitializeResponse`
- `close() -> None`
- `login_api_key(api_key: str) -> None`
- `login_chatgpt() -> ChatgptLoginHandle`
- `login_chatgpt_device_code() -> DeviceCodeLoginHandle`
- `account(*, refresh_token: bool = False) -> GetAccountResponse`
- `logout() -> None`
- `thread_start(*, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, ephemeral=None, model=None, model_provider=None, personality=None, sandbox: Sandbox | None = None) -> Thread`
- `thread_list(*, archived=None, cursor=None, cwd=None, limit=None, model_providers=None, sort_key=None, source_kinds=None) -> ThreadListResponse`
- `thread_resume(thread_id: str, *, approval_mode=None, base_instructions=None, config=None, cwd=None, developer_instructions=None, include_turns: bool | None = None, model=None, model_provider=None, personality=None, sandbox: Sandbox | None = None, service_tier=None) -> Thread`
- `thread_fork(thread_id: str, *, approval_mode=None, base_instructions=None, config=None, cwd=None, developer_instructions=None, ephemeral=None, include_turns: bool | None = None, model=None, model_provider=None, sandbox: Sandbox | None = None, service_tier=None) -> Thread`
- `thread_archive(thread_id: str) -> ThreadArchiveResponse`
- `thread_unarchive(thread_id: str) -> Thread`
- `models(*, include_hidden: bool = False) -> ModelListResponse`
Context manager:
```python
with Codex() as codex:
...
```
`thread_resume(...)` and `thread_fork(...)` accept `include_turns` to control
whether the server loads turn history into its response. `False` skips that
work; `True` requests it. Omitting the option, or passing `None`, preserves the
server's default behavior. This does not remove history from the model's
context. Both methods return a thread handle; use `thread.read(include_turns=True)`
to retrieve its history.
## AsyncCodex (async parity)
```python
AsyncCodex(config: CodexConfig | None = None)
```
Preferred usage:
```python
async with AsyncCodex() as codex:
...
```
`AsyncCodex` initializes lazily. Context entry is the standard path because it
ensures startup and shutdown are paired explicitly.
Properties/methods:
- `metadata -> InitializeResponse`
- `close() -> Awaitable[None]`
- `login_api_key(api_key: str) -> Awaitable[None]`
- `login_chatgpt() -> Awaitable[AsyncChatgptLoginHandle]`
- `login_chatgpt_device_code() -> Awaitable[AsyncDeviceCodeLoginHandle]`
- `account(*, refresh_token: bool = False) -> Awaitable[GetAccountResponse]`
- `logout() -> Awaitable[None]`
- `thread_start(*, approval_mode=ApprovalMode.auto_review, base_instructions=None, config=None, cwd=None, developer_instructions=None, ephemeral=None, model=None, model_provider=None, personality=None, sandbox: Sandbox | None = None) -> Awaitable[AsyncThread]`
- `thread_list(*, archived=None, cursor=None, cwd=None, limit=None, model_providers=None, sort_key=None, source_kinds=None) -> Awaitable[ThreadListResponse]`
- `thread_resume(thread_id: str, *, approval_mode=None, base_instructions=None, config=None, cwd=None, developer_instructions=None, include_turns: bool | None = None, model=None, model_provider=None, personality=None, sandbox: Sandbox | None = None, service_tier=None) -> Awaitable[AsyncThread]`
- `thread_fork(thread_id: str, *, approval_mode=None, base_instructions=None, config=None, cwd=None, developer_instructions=None, ephemeral=None, include_turns: bool | None = None, model=None, model_provider=None, sandbox: Sandbox | None = None, service_tier=None) -> Awaitable[AsyncThread]`
- `thread_archive(thread_id: str) -> Awaitable[ThreadArchiveResponse]`
- `thread_unarchive(thread_id: str) -> Awaitable[AsyncThread]`
- `models(*, include_hidden: bool = False) -> Awaitable[ModelListResponse]`
Async context manager:
```python
async with AsyncCodex() as codex:
...
```
## Login handles
### ChatgptLoginHandle / AsyncChatgptLoginHandle
- `login_id: str`
- `auth_url: str`
- `wait() -> AccountLoginCompletedNotification`
- `cancel() -> CancelLoginAccountResponse`
Async handle methods return awaitables.
### DeviceCodeLoginHandle / AsyncDeviceCodeLoginHandle
- `login_id: str`
- `verification_url: str`
- `user_code: str`
- `wait() -> AccountLoginCompletedNotification`
- `cancel() -> CancelLoginAccountResponse`
Async handle methods return awaitables.
`wait()` consumes only the completion notification for its matching login
attempt. API-key login completes synchronously and does not return a handle.
## Thread / AsyncThread
`Thread` and `AsyncThread` share the same shape and intent.
### Thread
- `run(input: RunInput, *, approval_mode=None, cwd=None, effort=None, model=None, output_schema=None, personality=None, sandbox: Sandbox | None = None, service_tier=None, source=None, summary=None, turn_service_tier=None) -> TurnResult`
- `turn(input: RunInput, *, approval_mode=None, cwd=None, effort=None, model=None, output_schema=None, personality=None, sandbox: Sandbox | None = None, service_tier=None, source=None, summary=None, turn_service_tier=None) -> TurnHandle`
- `read(*, include_turns: bool = False) -> ThreadReadResponse`
- `set_name(name: str) -> ThreadSetNameResponse`
- `compact() -> ThreadCompactStartResponse`
### AsyncThread
- `run(input: RunInput, *, approval_mode=None, cwd=None, effort=None, model=None, output_schema=None, personality=None, sandbox: Sandbox | None = None, service_tier=None, source=None, summary=None, turn_service_tier=None) -> Awaitable[TurnResult]`
- `turn(input: RunInput, *, approval_mode=None, cwd=None, effort=None, model=None, output_schema=None, personality=None, sandbox: Sandbox | None = None, service_tier=None, source=None, summary=None, turn_service_tier=None) -> Awaitable[AsyncTurnHandle]`
- `read(*, include_turns: bool = False) -> Awaitable[ThreadReadResponse]`
- `set_name(name: str) -> Awaitable[ThreadSetNameResponse]`
- `compact() -> Awaitable[ThreadCompactStartResponse]`
`run(...)` is the common-case convenience path. It accepts the same input and
options as `turn(...)`, consumes notifications until completion, and returns a
small result object with:
- `id: str`
- `status: TurnStatus`
- `error: TurnError | None`
- `started_at: int | None`
- `completed_at: int | None`
- `duration_ms: int | None`
- `final_response: str | None`
- `items: list[ThreadItem]`
- `usage: ThreadTokenUsage | None`
`final_response` is `None` when the turn finishes without a final-answer or
phase-less assistant message item.
Use `turn(...)` when you need low-level turn control (`stream()`, `steer()`,
`interrupt()`) before collecting the turn result.
### Turn options
These options have the same behavior on sync and async `run(...)` and `turn(...)`:
| Option | Behavior |
| --- | --- |
| `service_tier: str | None = None` | Sets the thread's service tier for this and subsequent turns. |
| `turn_service_tier: str | None = None` | Overrides the tier for a newly started turn only. `None` inherits the thread setting; `"default"` selects standard speed. Does not change the thread default and is ignored when input joins an active turn. |
| `source: str | None = None` | Labels the caller that initiated a new turn, such as `"review_ui"`. This is metadata; it does not schedule work or grant authority. Ignored when input joins an active turn. |
`turn_service_tier`, `source`, and explicit `include_turns`
on resume/fork require Codex CLI 0.151.0 or newer. The SDK raises `CodexError`
before sending these options to an older runtime, which would otherwise ignore
them. Published SDK releases install a matching runtime automatically; when
using `CodexConfig.codex_bin`, choose a compatible executable. Unversioned local
builds are checked lazily against their experimental schema before these options
are sent. A custom `launch_args_override` must report a supported version.
## Sandbox
Use `sandbox=` consistently on thread lifecycle methods and turns:
```python
from openai_codex import Codex, Sandbox
with Codex() as codex:
thread = codex.thread_start(sandbox=Sandbox.workspace_write)
result = thread.run("Review the diff only.", sandbox=Sandbox.read_only)
```
Presets:
- `Sandbox.read_only`: read files without allowing writes.
- `Sandbox.workspace_write`: the normal default for projects with a recorded trust decision; read files and write inside the workspace and configured writable roots.
- `Sandbox.full_access`: run without filesystem access restrictions.
When `sandbox=` is omitted, Codex uses its configured default. A sandbox
passed to `run(...)` or `turn(...)` applies to that turn and subsequent turns.
## TurnHandle / AsyncTurnHandle
### TurnHandle
- `steer(input: str | Input) -> TurnSteerResponse`
- `interrupt() -> TurnInterruptResponse`
- `stream() -> Iterator[Notification]`
- `run() -> TurnResult`
Behavior notes:
- `stream()` and `run()` consume only notifications for their own turn ID
- one `Codex` instance can stream multiple active turns concurrently
### AsyncTurnHandle
- `steer(input: str | Input) -> Awaitable[TurnSteerResponse]`
- `interrupt() -> Awaitable[TurnInterruptResponse]`
- `stream() -> AsyncIterator[Notification]`
- `run() -> Awaitable[TurnResult]`
Behavior notes:
- `stream()` and `run()` consume only notifications for their own turn ID
- one `AsyncCodex` instance can stream multiple active turns concurrently
## Inputs
```python
@dataclass class TextInput: text: str
@dataclass class ImageInput: url: str
@dataclass class LocalImageInput: path: str
@dataclass class SkillInput: name: str; path: str
@dataclass class MentionInput: name: str; path: str
InputItem = TextInput | ImageInput | LocalImageInput | SkillInput | MentionInput
Input = list[InputItem] | InputItem
RunInput = Input | str
```
Use `ImageInput` with a base64-encoded `data:image/...` URL. HTTP and HTTPS image URLs are
deprecated; download remote images and pass their local paths with `LocalImageInput` instead.
Use a plain `str` as shorthand for `TextInput(...)` anywhere a turn input is accepted:
`thread.run("...")`, `thread.turn("...")`, and `turn.steer("...")`.
## Public Types
The SDK wrappers return and accept public Codex protocol models wherever possible:
```python
from openai_codex.types import (
Account,
AccountLoginCompletedNotification,
CancelLoginAccountResponse,
CancelLoginAccountStatus,
GetAccountResponse,
ThreadReadResponse,
Turn,
TurnStatus,
)
```
### Notifications and generated models
Known notifications have typed `Notification.payload` values, including
authentication recovery, thread queue/project changes, thread reversion, and
realtime item updates. The `Notification.payload` type covers every registered
event. Unknown methods and payloads that fail validation still produce
`UnknownNotification`, with the raw data in
`.params`. When an event gains a typed payload, read its named fields instead
of `.params`.
Returned models include the current CLI's thread metadata, richer turn errors,
and `functionCallOutput` history items. Code that imports generated
`HookMetadata` directly must access the handler through `.root`, inspect its
`handler_type`, and then read the fields for that handler. For example, only a
`"command"` handler has a `command` field. This reflects the app-server's
separate command, MCP tool, prompt, and agent hook variants.
## Retry + errors
```python
from openai_codex import (
retry_on_overload,
JsonRpcError,
MethodNotFoundError,
InvalidParamsError,
ServerBusyError,
is_retryable_error,
)
```
- `retry_on_overload(...)` retries transient overload errors with exponential backoff + jitter.
- `is_retryable_error(exc)` checks if an exception is transient/overload-like.
## Example
```python
from openai_codex import Codex
with Codex() as codex:
thread = codex.thread_start(model="gpt-5.4", config={"model_reasoning_effort": "high"})
result = thread.run("Say hello in one sentence.")
print(result.final_response)
```