22
33Python SDK for Codex with bundled ` codex ` binaries inside platform wheels.
44
5- The SDK mirrors the TypeScript SDK behavior:
6- - Spawns ` codex exec --experimental-json `
7- - Streams JSONL events
8- - Supports thread resume, structured output schemas, images, sandbox/model options
5+ This package exposes two supported APIs:
6+
7+ - ` Codex ` : a simple, local convenience interface backed by a private stdio app-server session
8+ - ` AppServerClient ` : a richer app-server client for thread management, streaming events, approvals, and typed protocol access
9+
10+ Canonical import paths:
11+
12+ - use ` from codex import ... ` for the high-level ` Codex ` facade
13+ - use ` from codex.app_server import ... ` for the raw app-server client and app-server option types
914
1015## Install
1116
1217``` bash
1318pip install codex-python
1419```
1520
16- ## Quickstart
21+ ## Which API should I use?
22+
23+ ### ` Codex `
24+
25+ Use ` Codex ` when you want the smallest surface area for local automation:
26+
27+ - one private local app-server session per ` Codex ` instance
28+ - stateless ` run*() ` convenience (fresh internal thread per call)
29+ - stateful thread workflows when needed via ` start_thread() ` / ` resume_thread() `
30+ - simple request/response usage
31+ - optional streaming over the exec event stream
32+ - structured output via ` TurnOptions(output_schema=...) `
33+
34+ ### ` AppServerClient `
35+
36+ Use ` AppServerClient ` when you want a deeper integration:
37+
38+ - persistent app-server connection
39+ - thread objects and turn streams
40+ - protocol-native notifications
41+ - server-driven requests such as tool callbacks and approvals
42+ - typed protocol models and raw JSON-RPC access when needed
43+
44+ ## Quickstart: ` Codex `
1745
1846``` python
1947from codex import Codex
2048
2149client = Codex()
22- thread = client.start_thread()
23-
24- result = thread.run(" Diagnose the failing tests and propose a fix" )
25- print (result.final_response)
26- print (result.items)
50+ summary = client.run_text(" Diagnose the failing tests and propose a fix" )
51+ print (summary)
2752```
2853
29- ## Streaming
54+ More ` Codex ` examples: [ docs/exec_api.md] ( docs/exec_api.md )
55+
56+ ## Quickstart: ` AppServerClient `
3057
3158``` python
32- from codex import Codex
59+ from codex.app_server import AppServerClient, AppServerClientInfo, AppServerInitializeOptions
60+
61+ initialize_options = AppServerInitializeOptions(
62+ client_info = AppServerClientInfo(
63+ name = " my_integration" ,
64+ title = " My Integration" ,
65+ version = " 0.1.0" ,
66+ )
67+ )
3368
34- client = Codex()
35- thread = client.start_thread()
36-
37- stream = thread.run_streamed(" Investigate this bug" )
38- for event in stream.events:
39- if event[" type" ] == " item.completed" :
40- print (event[" item" ])
41- elif event[" type" ] == " turn.completed" :
42- print (event[" usage" ])
69+ with AppServerClient.connect_stdio(initialize_options = initialize_options) as client:
70+ thread = client.start_thread()
71+ summary = thread.run_text(" Briefly summarize this repository's purpose." )
72+ print (summary)
4373```
4474
75+ More app-server examples: [ docs/app_server.md] ( docs/app_server.md )
76+ For websocket transport, install the optional extra: ` pip install "codex-python[websocket]" ` .
77+
4578## Structured output
4679
80+ ### ` Codex `
81+
4782``` python
4883from codex import Codex, TurnOptions
4984
@@ -55,71 +90,98 @@ schema = {
5590}
5691
5792client = Codex()
58- thread = client.start_thread()
59- result = thread.run(" Summarize repository status" , TurnOptions(output_schema = schema))
60- print (result.final_response)
93+ payload = client.run_json(" Summarize repository status" , TurnOptions(output_schema = schema))
94+ print (payload[" summary" ])
6195```
6296
63- ## Input with local images
97+ ### ` AppServerClient `
6498
6599``` python
66- from codex import Codex
100+ from pydantic import BaseModel
67101
68- client = Codex()
69- thread = client.start_thread()
70- result = thread.run(
71- [
72- {" type" : " text" , " text" : " Describe these screenshots" },
73- {" type" : " local_image" , " path" : " ./ui.png" },
74- {" type" : " local_image" , " path" : " ./diagram.jpg" },
75- ]
76- )
102+ from codex.app_server import AppServerClient, AppServerTurnOptions
103+
104+
105+ class Summary (BaseModel ):
106+ summary: str
107+
108+
109+ with AppServerClient.connect_stdio() as client:
110+ thread = client.start_thread()
111+ result = thread.run_model(
112+ " Summarize repository status" ,
113+ Summary,
114+ )
115+ print (result.summary)
77116```
78117
79- ## Resume a thread
118+ ` run_model() ` uses ` Summary ` both as the validation model and, by default, as the output schema sent
119+ to Codex. If you want JSON back without validation, you can also pass the model class directly to
120+ ` output_schema ` , for example ` thread.run_json(..., AppServerTurnOptions(output_schema=Summary)) ` .
121+
122+ ## Streaming
123+
124+ ### ` Codex ` stream
80125
81126``` python
82127from codex import Codex
128+ from codex.protocol import types as protocol
83129
84130client = Codex()
85- thread = client.resume_thread(" thread_123" )
86- thread.run(" Continue from previous context" )
131+ stream = client.run(" Investigate this bug" )
132+ for event in stream:
133+ if isinstance (event, protocol.ItemAgentMessageDeltaNotification):
134+ print (event.params.delta, end = " " , flush = True )
135+
136+ print ()
87137```
88138
89- ## Options
139+ ` Codex.run*() ` starts a fresh internal thread for each call. Use
140+ ` start_thread() ` or ` resume_thread() ` when you want later runs to share context.
90141
91- - ` CodexOptions ` : ` codex_path_override ` , ` base_url ` , ` api_key ` , ` config ` , ` env `
92- - ` ThreadOptions ` : ` model ` , ` sandbox_mode ` , ` working_directory ` , ` skip_git_repo_check ` , ` model_reasoning_effort ` , ` network_access_enabled ` , ` web_search_mode ` , ` web_search_enabled ` , ` approval_policy ` , ` additional_directories `
93- - ` TurnOptions ` : ` output_schema ` , ` signal `
142+ High-level ` Codex ` helpers raise ` ThreadRunError ` on failed or interrupted terminal turns and
143+ preserve the final turn metadata on the exception for debugging and UI handling.
94144
95- ## Cancellation
145+ ### App-server stream
96146
97147``` python
98- import threading
148+ from codex.app_server import AppServerClient
149+ from codex.protocol import types as protocol
99150
100- from codex import Codex, TurnOptions
151+ with AppServerClient.connect_stdio() as client:
152+ thread = client.start_thread()
153+ stream = thread.run(" Investigate this bug" )
101154
102- cancel = threading.Event()
155+ for event in stream:
156+ if isinstance (event, protocol.ItemAgentMessageDeltaNotification):
157+ print (event.params.delta, end = " " , flush = True )
103158
104- client = Codex()
105- thread = client.start_thread()
106- stream = thread.run_streamed(" Long running task" , TurnOptions(signal = cancel))
107-
108- cancel.set()
109- for event in stream.events:
110- print (event)
159+ print ()
111160```
112161
162+ Advanced app-server usage, including typed stable RPC domains such as ` client.models ` and the raw ` client.rpc ` fallback: [ docs/app_server_advanced.md] ( docs/app_server_advanced.md )
163+
164+ ## Examples
165+
166+ - [ examples/basic_conversation.py] ( examples/basic_conversation.py ) : minimal ` Codex ` flow
167+ - [ examples/app_server_conversation.py] ( examples/app_server_conversation.py ) : minimal app-server flow
168+ - [ examples/app_server_websocket_conversation.py] ( examples/app_server_websocket_conversation.py ) : minimal websocket app-server flow
169+ - [ examples/app_server_stream_events.py] ( examples/app_server_stream_events.py ) : protocol-native app-server streaming
170+ - [ examples/app_server_tool_handler.py] ( examples/app_server_tool_handler.py ) : typed app-server request handling
171+
113172## Bundled binary behavior
114173
115174By default, the SDK resolves the bundled binary at:
116175
117176` codex/vendor/<target-triple>/codex/{codex|codex.exe} `
118177
119- If the bundled binary is not present ( for example in a source checkout) , the SDK falls back to
178+ If the bundled binary is not present, for example in a source checkout, the SDK falls back to
120179` codex ` on ` PATH ` .
121180
122- You can always override with ` CodexOptions(codex_path_override=...) ` .
181+ You can override the executable path with:
182+
183+ - ` CodexOptions(codex_path_override=...) `
184+ - ` codex.app_server.AppServerProcessOptions(codex_path_override=...) `
123185
124186## Development
125187
@@ -128,6 +190,9 @@ make lint
128190make test
129191```
130192
193+ ` make test ` emits a terminal coverage report, writes ` coverage.xml ` , and enforces the repository
194+ coverage gate.
195+
131196If you want to test vendored-binary behavior locally, fetch binaries into ` codex/vendor ` :
132197
133198``` bash
0 commit comments