codex-sdk-python 0.85.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,882 @@
1
+ Metadata-Version: 2.3
2
+ Name: codex-sdk-python
3
+ Version: 0.85.0
4
+ Summary: Python SDK for the Codex CLI agent with async threads, streaming events, and structured outputs
5
+ Keywords: codex,sdk,python,api,cli,agent,async,streaming
6
+ Author: Vectorfy Co
7
+ Author-email: Vectorfy Co <git@vectorfy.co>
8
+ License: Apache-2.0
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: Apache Software License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Topic :: Software Development :: Build Tools
21
+ Requires-Dist: logfire ; extra == 'logfire'
22
+ Requires-Dist: pydantic>=2 ; extra == 'pydantic'
23
+ Requires-Dist: pydantic-ai ; python_full_version >= '3.10' and extra == 'pydantic-ai'
24
+ Maintainer: Vectorfy Co
25
+ Maintainer-email: Vectorfy Co <git@vectorfy.co>
26
+ Requires-Python: >=3.8
27
+ Project-URL: Homepage, https://vectorfy.co
28
+ Project-URL: Repository, https://github.com/vectorfy-co/codex-sdk-python
29
+ Project-URL: Documentation, https://github.com/vectorfy-co/codex-sdk-python#readme
30
+ Project-URL: Issues, https://github.com/vectorfy-co/codex-sdk-python/issues
31
+ Project-URL: Changelog, https://github.com/vectorfy-co/codex-sdk-python/blob/main/CHANGELOG_SDK.md
32
+ Provides-Extra: logfire
33
+ Provides-Extra: pydantic
34
+ Provides-Extra: pydantic-ai
35
+ Description-Content-Type: text/markdown
36
+
37
+ # ![Codex SDK Python](https://img.shields.io/badge/Codex%20SDK-Python-1D4ED8?style=for-the-badge&logo=python&logoColor=white)
38
+
39
+ Embed the Codex agent in Python workflows. This SDK wraps the bundled `codex` CLI, streams JSONL events over stdin/stdout, and exposes structured, typed results.
40
+
41
+ <div align="left">
42
+ <table>
43
+ <tr>
44
+ <td><strong>Lifecycle</strong></td>
45
+ <td>
46
+ <a href="#ci-cd"><img src="https://img.shields.io/badge/CI%2FCD-Active-16a34a?style=flat&logo=githubactions&logoColor=white" alt="CI/CD badge" /></a>
47
+ <img src="https://img.shields.io/badge/Release-0.85.0-6b7280?style=flat&logo=pypi&logoColor=white" alt="Release badge" />
48
+ <a href="#license"><img src="https://img.shields.io/badge/License-Apache--2.0-0f766e?style=flat&logo=apache&logoColor=white" alt="License badge" /></a>
49
+ </td>
50
+ </tr>
51
+ <tr>
52
+ <td><strong>Core Stack</strong></td>
53
+ <td>
54
+ <img src="https://img.shields.io/badge/Python-3.8%2B-3776AB?style=flat&logo=python&logoColor=white" alt="Python badge" />
55
+ <img src="https://img.shields.io/badge/Codex-CLI-111827?style=flat&logo=gnubash&logoColor=white" alt="Codex CLI badge" />
56
+ <img src="https://img.shields.io/badge/JSONL-Events-0ea5e9?style=flat&logo=json&logoColor=white" alt="JSONL badge" />
57
+ <img src="https://img.shields.io/badge/Pydantic-v2-0b3b2e?style=flat&logo=pydantic&logoColor=white" alt="Pydantic badge" />
58
+ <img src="https://img.shields.io/badge/PydanticAI-Integrations-0b3b2e?style=flat&logo=pydantic&logoColor=white" alt="PydanticAI badge" />
59
+ </td>
60
+ </tr>
61
+ <tr>
62
+ <td><strong>Navigation</strong></td>
63
+ <td>
64
+ <a href="#quick-start"><img src="https://img.shields.io/badge/Local%20Setup-Quick%20Start-059669?style=flat&logo=serverless&logoColor=white" alt="Quick start" /></a>
65
+ <a href="#features"><img src="https://img.shields.io/badge/Overview-Features-7c3aed?style=flat&logo=simpleicons&logoColor=white" alt="Features" /></a>
66
+ <a href="#configuration"><img src="https://img.shields.io/badge/Config-Options%20%26%20Env-0ea5e9?style=flat&logo=json&logoColor=white" alt="Config" /></a>
67
+ <a href="#pydantic-ai"><img src="https://img.shields.io/badge/Integrations-PydanticAI-0b3b2e?style=flat&logo=pydantic&logoColor=white" alt="PydanticAI" /></a>
68
+ <a href="#architecture"><img src="https://img.shields.io/badge/Design-Architecture-1f2937?style=flat&logo=serverless&logoColor=white" alt="Architecture" /></a>
69
+ <a href="#testing"><img src="https://img.shields.io/badge/Quality-Testing-2563eb?style=flat&logo=pytest&logoColor=white" alt="Testing" /></a>
70
+ </td>
71
+ </tr>
72
+ </table>
73
+ </div>
74
+
75
+ - Runtime dependency-free: uses only the Python standard library.
76
+ - Codex CLI binaries are downloaded separately; use `scripts/setup_binary.py` from the repo or install the Codex CLI and set `codex_path_override`.
77
+ - Async-first API with sync helpers, streaming events, and structured output.
78
+ - Python 3.8/3.9 support is deprecated and will be removed in a future release; use Python 3.10+.
79
+
80
+ <a id="quick-start"></a>
81
+ ## ![Quick Start](https://img.shields.io/badge/Quick%20Start-4%20steps-059669?style=for-the-badge&logo=serverless&logoColor=white)
82
+
83
+ 1. Install the SDK:
84
+
85
+ ```bash
86
+ uv add codex-sdk-python
87
+ ```
88
+
89
+ 2. Ensure a `codex` binary is available (required for local runs):
90
+
91
+ ```bash
92
+ # From the repo source (downloads vendor binaries)
93
+ python scripts/setup_binary.py
94
+ ```
95
+
96
+ If you installed from PyPI, install the Codex CLI separately and either add it to your PATH
97
+ or pass `CodexOptions.codex_path_override`.
98
+
99
+ 3. Authenticate with Codex:
100
+
101
+ ```bash
102
+ codex login
103
+ ```
104
+
105
+ Or export an API key:
106
+
107
+ ```bash
108
+ export CODEX_API_KEY="<your-api-key>"
109
+ ```
110
+
111
+ 4. Run a first turn:
112
+
113
+ ```python
114
+ import asyncio
115
+ from codex_sdk import Codex
116
+
117
+ async def main() -> None:
118
+ codex = Codex()
119
+ thread = codex.start_thread()
120
+ turn = await thread.run("Diagnose the test failure and propose a fix")
121
+ print(turn.final_response)
122
+ print(turn.items)
123
+
124
+ if __name__ == "__main__":
125
+ asyncio.run(main())
126
+ ```
127
+
128
+ For single-turn sessions with approval handling, use the turn session wrapper:
129
+
130
+ ```python
131
+ import asyncio
132
+ from codex_sdk import AppServerClient, AppServerOptions, ApprovalDecisions
133
+
134
+ async def main() -> None:
135
+ async with AppServerClient(AppServerOptions()) as app:
136
+ thread = await app.thread_start(model="gpt-5-codex-high", cwd=".")
137
+ thread_id = thread["thread"]["id"]
138
+ session = await app.turn_session(
139
+ thread_id,
140
+ "Run tests and summarize failures.",
141
+ approvals=ApprovalDecisions(command_execution="accept"),
142
+ )
143
+
144
+ async for notification in session.notifications():
145
+ print(notification.method)
146
+
147
+ final_turn = await session.wait()
148
+ print(final_turn)
149
+
150
+ if __name__ == "__main__":
151
+ asyncio.run(main())
152
+ ```
153
+
154
+ ### Examples
155
+
156
+ Try the examples under `examples/`:
157
+
158
+ ```bash
159
+ python examples/basic_usage.py
160
+ python examples/streaming_example.py
161
+ python examples/thread_resume.py
162
+ python examples/app_server_basic.py
163
+ python examples/app_server_fork.py
164
+ python examples/app_server_requirements.py
165
+ python examples/app_server_skill_input.py
166
+ python examples/app_server_approvals.py
167
+ python examples/app_server_turn_session.py
168
+ python examples/config_overrides.py
169
+ python examples/hooks_streaming.py
170
+ python examples/notify_hook.py
171
+ ```
172
+
173
+ <a id="features"></a>
174
+ ## ![Features](https://img.shields.io/badge/Features-Core%20Capabilities-7c3aed?style=for-the-badge&logo=simpleicons&logoColor=white)
175
+
176
+ | Feature Badge | Details |
177
+ | ------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------- |
178
+ | ![Threaded](https://img.shields.io/badge/Threads-Persistent%20Sessions-2563EB?style=flat&logo=serverless&logoColor=white) | Each `Thread` keeps context; resume by thread id or last session. |
179
+ | ![Streaming](https://img.shields.io/badge/Streaming-JSONL%20Events-0ea5e9?style=flat&logo=json&logoColor=white) | `run_streamed()` yields structured events as they happen. |
180
+ | ![Hooks](https://img.shields.io/badge/Hooks-Event%20Callbacks-0f766e?style=flat&logo=codefactor&logoColor=white) | `ThreadHooks` lets you react to streamed events inline. |
181
+ | ![Structured](https://img.shields.io/badge/Structured%20Output-JSON%20Schema-22c55e?style=flat&logo=json&logoColor=white) | `run_json()` validates JSON output against a schema. |
182
+ | ![Pydantic](https://img.shields.io/badge/Pydantic-Model%20Validation-0b3b2e?style=flat&logo=pydantic&logoColor=white) | `run_pydantic()` derives schema and validates with Pydantic v2. |
183
+ | ![Sandbox](https://img.shields.io/badge/Sandbox-Read%2FWrite%20Controls-1f2937?style=flat&logo=gnubash&logoColor=white) | Thread options map to Codex CLI sandbox and approval policies. |
184
+ | ![PydanticAI](https://img.shields.io/badge/PydanticAI-Model%20Provider-0b3b2e?style=flat&logo=pydantic&logoColor=white) | Codex can act as a PydanticAI model or as a delegated tool. |
185
+ | ![Abort](https://img.shields.io/badge/Abort-Signals-ef4444?style=flat&logo=gnubash&logoColor=white) | Cancel running turns via `AbortController` and `AbortSignal`. |
186
+ | ![Telemetry](https://img.shields.io/badge/Telemetry-Logfire%20Spans-f97316?style=flat&logo=simpleicons&logoColor=white) | Optional spans if Logfire is installed and initialized. |
187
+
188
+ <a id="configuration"></a>
189
+ ## ![Configuration](https://img.shields.io/badge/Configuration-Options%20%26%20Env-0ea5e9?style=for-the-badge&logo=json&logoColor=white)
190
+
191
+ ### Installation extras
192
+
193
+ ```bash
194
+ uv add "codex-sdk-python[pydantic]" # Pydantic v2 schema helpers
195
+ uv add "codex-sdk-python[pydantic-ai]" # PydanticAI integrations
196
+ uv add "codex-sdk-python[logfire]" # Optional tracing
197
+ ```
198
+
199
+ ### Environment variables
200
+
201
+ ```bash
202
+ CODEX_API_KEY=<api-key>
203
+ OPENAI_BASE_URL=https://api.openai.com/v1
204
+ CODEX_HOME=~/.codex
205
+ ```
206
+
207
+ Notes:
208
+ - `CODEX_API_KEY` is forwarded to the `codex` process; `CodexOptions.api_key` overrides the environment.
209
+ - `OPENAI_BASE_URL` is set when `CodexOptions.base_url` is provided.
210
+ - `CODEX_HOME` controls where sessions are stored and where `resume_last_thread()` looks.
211
+
212
+ ### CodexOptions (client)
213
+
214
+ ```python
215
+ from codex_sdk import Codex, CodexOptions
216
+
217
+ codex = Codex(
218
+ CodexOptions(
219
+ codex_path_override="/path/to/codex",
220
+ base_url="https://api.openai.com/v1",
221
+ api_key="<key>",
222
+ env={"CUSTOM_ENV": "custom"},
223
+ config_overrides={
224
+ "analytics.enabled": True,
225
+ "notify": ["python3", "/path/to/notify.py"],
226
+ },
227
+ )
228
+ )
229
+ ```
230
+
231
+ - `codex_path_override`: use a custom CLI binary path.
232
+ - `base_url`: sets `OPENAI_BASE_URL` for the child process.
233
+ - `api_key`: sets `CODEX_API_KEY` for the child process.
234
+ - `env`: when set, replaces inherited environment variables; the SDK still injects required values.
235
+
236
+ ### ThreadOptions (per thread)
237
+
238
+ ```python
239
+ from codex_sdk import ThreadOptions
240
+
241
+ ThreadOptions(
242
+ model="gpt-5-codex-high",
243
+ sandbox_mode="workspace-write",
244
+ working_directory="/path/to/project",
245
+ skip_git_repo_check=True,
246
+ model_reasoning_effort="high",
247
+ network_access_enabled=True,
248
+ web_search_mode="cached",
249
+ shell_snapshot_enabled=True,
250
+ background_terminals_enabled=True,
251
+ apply_patch_freeform_enabled=False,
252
+ exec_policy_enabled=True,
253
+ remote_models_enabled=False,
254
+ request_compression_enabled=True,
255
+ approval_policy="on-request",
256
+ additional_directories=["../shared"],
257
+ config_overrides={"analytics.enabled": True},
258
+ )
259
+ ```
260
+
261
+ Important mappings to the Codex CLI:
262
+ - `sandbox_mode` maps to `--sandbox` (`read-only`, `workspace-write`, `danger-full-access`).
263
+ - `working_directory` maps to `--cd`.
264
+ - `additional_directories` maps to repeated `--add-dir`.
265
+ - `skip_git_repo_check` maps to `--skip-git-repo-check`.
266
+ - `model_reasoning_effort` maps to `--config model_reasoning_effort=...`.
267
+ - `network_access_enabled` maps to `--config sandbox_workspace_write.network_access=...`.
268
+ - `web_search_mode` maps to `--config web_search="disabled|cached|live"`.
269
+ - `web_search_enabled`/`web_search_cached_enabled` map to `--config web_search=...` for legacy
270
+ compatibility.
271
+ - `shell_snapshot_enabled` maps to `--config features.shell_snapshot=...`.
272
+ - `background_terminals_enabled` maps to `--config features.unified_exec=...`.
273
+ - `apply_patch_freeform_enabled` maps to `--config features.apply_patch_freeform=...`.
274
+ - `exec_policy_enabled` maps to `--config features.exec_policy=...`.
275
+ - `remote_models_enabled` maps to `--config features.remote_models=...`.
276
+ - `request_compression_enabled` maps to `--config features.enable_request_compression=...`.
277
+ - `feature_overrides` maps to `--config features.<key>=...` (explicit options take precedence).
278
+ - `approval_policy` maps to `--config approval_policy=...`.
279
+ - `config_overrides` maps to repeated `--config key=value` entries.
280
+
281
+ Note: `skills_enabled` is deprecated in Codex 0.80+ (skills are always enabled).
282
+
283
+ Feature overrides example:
284
+
285
+ ```python
286
+ ThreadOptions(
287
+ feature_overrides={
288
+ "web_search_cached": True,
289
+ "powershell_utf8": True,
290
+ }
291
+ )
292
+ ```
293
+
294
+ ### App server (JSON-RPC)
295
+
296
+ For richer integrations (thread fork, requirements, explicit skill input), use the app-server
297
+ protocol. The client handles the initialize/initialized handshake and gives you access to
298
+ JSON-RPC notifications.
299
+
300
+ ```python
301
+ import asyncio
302
+ from codex_sdk import AppServerClient, AppServerOptions
303
+
304
+ async def main() -> None:
305
+ async with AppServerClient(AppServerOptions()) as app:
306
+ thread = await app.thread_start(model="gpt-5-codex-high", cwd=".")
307
+ thread_id = thread["thread"]["id"]
308
+ await app.turn_start(
309
+ thread_id,
310
+ [
311
+ {"type": "text", "text": "Use $my-skill and summarize."},
312
+ {"type": "skill", "name": "my-skill", "path": "/path/to/SKILL.md"},
313
+ ],
314
+ )
315
+
316
+ async for notification in app.notifications():
317
+ print(notification.method, notification.params)
318
+
319
+ if __name__ == "__main__":
320
+ asyncio.run(main())
321
+ ```
322
+
323
+ #### App-server convenience methods
324
+
325
+ The SDK also exposes helpers for most app-server endpoints:
326
+
327
+ - Threads: `thread_list`, `thread_archive`, `thread_rollback`, `thread_loaded_list`
328
+ - Config: `config_read`, `config_value_write`, `config_batch_write`, `config_requirements_read`
329
+ - Skills: `skills_list`
330
+ - Turns/review: `turn_start`, `turn_interrupt`, `review_start`, `turn_session`
331
+ - Models: `model_list`
332
+ - One-off commands: `command_exec`
333
+ - MCP auth/status: `mcp_server_oauth_login`, `mcp_server_refresh`, `mcp_server_status_list`
334
+ - Account: `account_login_start`, `account_login_cancel`, `account_logout`,
335
+ `account_rate_limits_read`, `account_read`
336
+ - Feedback: `feedback_upload`
337
+
338
+ These map 1:1 to the Codex app-server protocol; see `codex/codex-rs/app-server/README.md`
339
+ for payload shapes and event semantics.
340
+
341
+ ### Observability (OTEL) and notify
342
+
343
+ Codex emits OTEL traces/logs/metrics when configured in `~/.codex/config.toml`.
344
+ For headless runs (`codex exec`), set `analytics.enabled=true` and provide OTEL exporters
345
+ in the config file. You can also pass overrides with `config_overrides`.
346
+
347
+ ```python
348
+ CodexOptions(
349
+ config_overrides={
350
+ "analytics.enabled": True,
351
+ "notify": ["python3", "/path/to/notify.py"],
352
+ }
353
+ )
354
+ ```
355
+
356
+ See `examples/notify_hook.py` for a ready-to-use notify script.
357
+
358
+ ### TurnOptions (per turn)
359
+
360
+ ```python
361
+ from codex_sdk import TurnOptions
362
+
363
+ TurnOptions(
364
+ output_schema={"type": "object", "properties": {"ok": {"type": "boolean"}}},
365
+ signal=controller.signal,
366
+ )
367
+ ```
368
+
369
+ - `output_schema` must be a JSON object (mapping). The SDK writes it to a temp file and passes `--output-schema`.
370
+ - `signal` is an `AbortSignal` for canceling an in-flight turn.
371
+
372
+ ### Bundled CLI binary and platform support
373
+
374
+ The SDK resolves a platform-specific Codex CLI binary under `src/codex_sdk/vendor/<target>/codex/`.
375
+ It selects the target triple based on OS and CPU and ensures the binary is executable on POSIX.
376
+
377
+ Supported target triples:
378
+ - Linux: `x86_64-unknown-linux-musl`, `aarch64-unknown-linux-musl`
379
+ - macOS: `x86_64-apple-darwin`, `aarch64-apple-darwin`
380
+ - Windows: `x86_64-pc-windows-msvc`, `aarch64-pc-windows-msvc`
381
+
382
+ If you are working from source and the vendor directory is missing, run `python scripts/setup_binary.py`
383
+ or follow `SETUP.md` to download the official npm package and copy the `vendor/` directory.
384
+
385
+ <a id="auth"></a>
386
+ ## ![Auth](https://img.shields.io/badge/Auth%20%26%20Credentials-Access-2563eb?style=for-the-badge&logo=gnubash&logoColor=white)
387
+
388
+ The SDK delegates authentication to the Codex CLI:
389
+ - Run `codex login` to create local credentials (stored under `~/.codex/` by the CLI).
390
+ - Or set `CODEX_API_KEY` (or pass `CodexOptions.api_key`) for headless use.
391
+ - `CodexOptions.base_url` sets `OPENAI_BASE_URL` to target an OpenAI-compatible endpoint.
392
+
393
+ <a id="usage"></a>
394
+ ## ![SDK Usage](https://img.shields.io/badge/SDK%20Usage-Core%20API-6366f1?style=for-the-badge&logo=python&logoColor=white)
395
+
396
+ ### Basic run
397
+
398
+ ```python
399
+ from codex_sdk import Codex
400
+
401
+ codex = Codex()
402
+ thread = codex.start_thread()
403
+ turn = await thread.run("Summarize the repository")
404
+ print(turn.final_response)
405
+ ```
406
+
407
+ ### Sync helpers (non-async)
408
+
409
+ ```python
410
+ from pydantic import BaseModel
411
+
412
+ class RepoStatus(BaseModel):
413
+ summary: str
414
+
415
+ turn = thread.run_sync("Summarize the repository")
416
+ parsed = thread.run_json_sync("Summarize", output_schema={"type": "object"})
417
+ validated = thread.run_pydantic_sync("Summarize", output_model=RepoStatus)
418
+ ```
419
+
420
+ Note: sync helpers raise `CodexError` if called from an active event loop.
421
+
422
+ ### Streaming events
423
+
424
+ ```python
425
+ result = await thread.run_streamed("Diagnose the test failure")
426
+ async for event in result.events:
427
+ if event.type == "item.completed":
428
+ print(event.item.type)
429
+ elif event.type == "turn.completed":
430
+ print(event.usage)
431
+ ```
432
+
433
+ To iterate directly without the wrapper:
434
+
435
+ ```python
436
+ async for event in thread.run_streamed_events("Diagnose the test failure"):
437
+ print(event.type)
438
+ ```
439
+
440
+ ### Hooks for streamed events
441
+
442
+ Use `ThreadHooks` to react to events without manually wiring an event loop.
443
+
444
+ ```python
445
+ from codex_sdk import ThreadHooks
446
+
447
+ hooks = ThreadHooks(
448
+ on_event=lambda event: print("event", event.type),
449
+ on_item_type={
450
+ "command_execution": lambda item: print("command", item.command),
451
+ },
452
+ )
453
+
454
+ turn = await thread.run_with_hooks("Run the tests and summarize failures.", hooks=hooks)
455
+ print(turn.final_response)
456
+ ```
457
+
458
+ ### Event types (ThreadEvent)
459
+
460
+ - `thread.started`
461
+ - `turn.started`
462
+ - `turn.completed` (includes token usage)
463
+ - `turn.failed`
464
+ - `item.started`
465
+ - `item.updated`
466
+ - `item.completed`
467
+ - `error`
468
+
469
+ ### Item types (ThreadItem)
470
+
471
+ - `agent_message`
472
+ - `reasoning`
473
+ - `command_execution`
474
+ - `file_change`
475
+ - `mcp_tool_call`
476
+ - `web_search`
477
+ - `todo_list`
478
+ - `error`
479
+
480
+ ### Structured output (JSON schema)
481
+
482
+ ```python
483
+ schema = {
484
+ "type": "object",
485
+ "properties": {
486
+ "summary": {"type": "string"},
487
+ "status": {"type": "string", "enum": ["ok", "action_required"]},
488
+ },
489
+ "required": ["summary", "status"],
490
+ "additionalProperties": False,
491
+ }
492
+
493
+ result = await thread.run_json("Summarize repository status", output_schema=schema)
494
+ print(result.output)
495
+ ```
496
+
497
+ ### Pydantic output validation
498
+
499
+ ```python
500
+ from pydantic import BaseModel
501
+
502
+ class RepoStatus(BaseModel):
503
+ summary: str
504
+ status: str
505
+
506
+ result = await thread.run_pydantic("Summarize repository status", output_model=RepoStatus)
507
+ print(result.output)
508
+ ```
509
+
510
+ ### Images + text
511
+
512
+ ```python
513
+ turn = await thread.run(
514
+ [
515
+ {"type": "text", "text": "Describe these screenshots"},
516
+ {"type": "local_image", "path": "./ui.png"},
517
+ {"type": "text", "text": "Focus on failures"},
518
+ {"type": "local_image", "path": "./diagram.jpg"},
519
+ ]
520
+ )
521
+ ```
522
+
523
+ ### Abort a running turn
524
+
525
+ ```python
526
+ import asyncio
527
+ from codex_sdk import AbortController, TurnOptions
528
+
529
+ controller = AbortController()
530
+ options = TurnOptions(signal=controller.signal)
531
+
532
+ task = asyncio.create_task(thread.run("Long task", options))
533
+ controller.abort("user requested cancel")
534
+ await task
535
+ ```
536
+
537
+ ### Thread resume helpers
538
+
539
+ ```python
540
+ from codex_sdk import Codex
541
+
542
+ codex = Codex()
543
+ thread = codex.resume_thread("<thread-id>")
544
+
545
+ # Or resume the most recent session (uses CODEX_HOME or ~/.codex)
546
+ last_thread = codex.resume_last_thread()
547
+ ```
548
+
549
+ ### Turn helpers
550
+
551
+ Each `Turn` provides convenience filters: `agent_messages()`, `reasoning()`, `commands()`,
552
+ `file_changes()`, `mcp_tool_calls()`, `web_searches()`, `todo_lists()`, and `errors()`.
553
+
554
+ <a id="api"></a>
555
+ ## ![API Reference](https://img.shields.io/badge/API-Reference-6366f1?style=for-the-badge&logo=python&logoColor=white)
556
+
557
+ Core classes:
558
+ - `Codex`: `start_thread()`, `resume_thread()`, `resume_last_thread()`.
559
+ - `Thread`: `run()`, `run_streamed()`, `run_streamed_events()`, `run_json()`, `run_pydantic()`,
560
+ plus `run_sync()`, `run_json_sync()`, `run_pydantic_sync()`.
561
+ - `Turn`: `items`, `final_response`, `usage`, and helper filters.
562
+ - `AppServerClient`, `AppServerTurnSession`, `ApprovalDecisions` for app-server integrations.
563
+ - `ThreadHooks` for event callbacks.
564
+ - `CodexOptions`, `ThreadOptions`, `TurnOptions`.
565
+ - `AbortController`, `AbortSignal`.
566
+
567
+ Exceptions:
568
+ - `CodexError`, `CodexCLIError`, `CodexParseError`, `CodexAbortError`, `TurnFailedError`.
569
+
570
+ Typed events and items:
571
+ - `ThreadEvent` union of `thread.*`, `turn.*`, `item.*`, and `error` events.
572
+ - `ThreadItem` union of `agent_message`, `reasoning`, `command_execution`, `file_change`,
573
+ `mcp_tool_call`, `web_search`, `todo_list`, `error`.
574
+
575
+ <a id="examples"></a>
576
+ ## ![Examples](https://img.shields.io/badge/Examples-Reference%20Scripts-6366f1?style=for-the-badge&logo=python&logoColor=white)
577
+
578
+ Example scripts under `examples/`:
579
+
580
+ - `basic_usage.py`: minimal `Codex` + `Thread` usage.
581
+ - `streaming_example.py`: live event streaming.
582
+ - `structured_output.py`: JSON schema output parsing.
583
+ - `thread_resume.py`: resume with `CODEX_THREAD_ID`.
584
+ - `permission_levels_example.py`: sandbox modes and working directory.
585
+ - `model_configuration_example.py`: model selection and endpoint config.
586
+ - `app_server_turn_session.py`: approval-handled turns over app-server.
587
+ - `hooks_streaming.py`: event hooks for streaming runs.
588
+ - `notify_hook.py`: notify script for CLI callbacks.
589
+ - `pydantic_ai_model_provider.py`: Codex as a PydanticAI model provider.
590
+ - `pydantic_ai_handoff.py`: Codex as a PydanticAI tool.
591
+
592
+ <a id="sandbox"></a>
593
+ ## ![Sandbox](https://img.shields.io/badge/Sandbox-Permissions%20%26%20Safety-1f2937?style=for-the-badge&logo=gnubash&logoColor=white)
594
+
595
+ The SDK forwards sandbox and approval controls directly to `codex exec`.
596
+
597
+ - `read-only`: can read files and run safe commands, no writes.
598
+ - `workspace-write`: can write inside the working directory and added directories.
599
+ - `danger-full-access`: unrestricted (use with caution).
600
+
601
+ Additional controls:
602
+ - `working_directory`: restricts where the CLI starts and what it can access.
603
+ - `additional_directories`: allowlist extra folders when using `workspace-write`.
604
+ - `approval_policy`: `never`, `on-request`, `on-failure`, `untrusted`.
605
+ - `network_access_enabled`: toggles network access in workspace-write sandbox.
606
+ - `web_search_mode`: toggles web search (`disabled`, `cached`, `live`).
607
+
608
+ <a id="pydantic-ai"></a>
609
+ ## ![PydanticAI](https://img.shields.io/badge/PydanticAI-Integrations-0b3b2e?style=for-the-badge&logo=pydantic&logoColor=white)
610
+
611
+ This SDK offers two ways to integrate with PydanticAI:
612
+
613
+ ### 1) Codex as a PydanticAI model provider
614
+
615
+ Use `CodexModel` to delegate tool-call planning and text generation to Codex, while PydanticAI executes tools and validates outputs.
616
+
617
+ ```python
618
+ from pydantic_ai import Agent, Tool
619
+
620
+ from codex_sdk.integrations.pydantic_ai_model import CodexModel
621
+ from codex_sdk.options import ThreadOptions
622
+
623
+ def add(a: int, b: int) -> int:
624
+ return a + b
625
+
626
+ model = CodexModel(
627
+ thread_options=ThreadOptions(
628
+ model="gpt-5",
629
+ sandbox_mode="read-only",
630
+ skip_git_repo_check=True,
631
+ )
632
+ )
633
+ agent = Agent(model, tools=[Tool(add)])
634
+
635
+ result = agent.run_sync("What's 19 + 23? Use the add tool.")
636
+ print(result.output)
637
+ ```
638
+
639
+ How it works:
640
+ - `CodexModel` builds a JSON schema envelope with `tool_calls` and `final`.
641
+ - Codex emits tool calls as JSON strings; PydanticAI runs them.
642
+ - If `allow_text_output` is true, Codex can place final text in `final`.
643
+ - Streaming APIs (`Agent.run_stream_events()`, `Agent.run_stream_sync()`) are supported; Codex
644
+ emits streamed responses as a single chunk once the turn completes.
645
+
646
+ Safety defaults (you can override with your own `ThreadOptions`):
647
+ - `sandbox_mode="read-only"`
648
+ - `skip_git_repo_check=True`
649
+ - `approval_policy="never"`
650
+ - `web_search_mode="disabled"`
651
+ - `network_access_enabled=False`
652
+
653
+ ### 2) Codex as a PydanticAI tool (handoff)
654
+
655
+ Register Codex as a tool and let a PydanticAI agent decide when to delegate tasks.
656
+
657
+ ```python
658
+ from pydantic_ai import Agent
659
+
660
+ from codex_sdk import ThreadOptions
661
+ from codex_sdk.integrations.pydantic_ai import codex_handoff_tool
662
+
663
+ tool = codex_handoff_tool(
664
+ thread_options=ThreadOptions(
665
+ sandbox_mode="workspace-write",
666
+ skip_git_repo_check=True,
667
+ working_directory=".",
668
+ ),
669
+ include_items=True,
670
+ items_limit=20,
671
+ )
672
+
673
+ agent = Agent(
674
+ "openai:gpt-5",
675
+ tools=[tool],
676
+ system_prompt=(
677
+ "You can delegate implementation details to the codex_handoff tool. "
678
+ "Use it for repository-aware edits, command execution, or patches."
679
+ ),
680
+ )
681
+
682
+ result = await agent.run(
683
+ "Use the codex_handoff tool to scan this repository and suggest one small DX improvement."
684
+ )
685
+ print(result.output)
686
+ ```
687
+
688
+ Handoff options:
689
+ - `persist_thread`: keep a single Codex thread across tool calls (default true).
690
+ - `include_items`: include a summarized item list in tool output.
691
+ - `items_limit`: cap the number of items returned.
692
+ - `include_usage`: include token usage.
693
+ - `timeout_seconds`: wrap the run in `asyncio.wait_for`.
694
+
695
+ <a id="telemetry"></a>
696
+ ## ![Telemetry](https://img.shields.io/badge/Telemetry-Logfire%20Spans-f97316?style=for-the-badge&logo=simpleicons&logoColor=white)
697
+
698
+ If `logfire` is installed and initialized, the SDK emits spans:
699
+ - `codex_sdk.exec`
700
+ - `codex_sdk.thread.turn`
701
+ - `codex_sdk.pydantic_ai.model_request`
702
+ - `codex_sdk.pydantic_ai.handoff`
703
+
704
+ If Logfire is missing or not initialized, the span context manager is a no-op.
705
+
706
+ <a id="architecture"></a>
707
+ <a id="acheature"></a>
708
+ ## ![Architecture](https://img.shields.io/badge/Architecture-Stack%20map-1f2937?style=for-the-badge&logo=serverless&logoColor=white)
709
+
710
+ ### System components
711
+
712
+ ```mermaid
713
+ flowchart LR
714
+ subgraph App[Your Python App]
715
+ U[User Code]
716
+ T[Thread API]
717
+ end
718
+
719
+ subgraph SDK[Codex SDK]
720
+ C[Codex]
721
+ E[CodexExec]
722
+ P[Event Parser]
723
+ end
724
+
725
+ subgraph CLI[Bundled Codex CLI]
726
+ X["codex exec --experimental-json"]
727
+ end
728
+
729
+ FS[(Filesystem)]
730
+ NET[(Network)]
731
+
732
+ U --> T --> C --> E --> X
733
+ X -->|JSONL events| P --> T
734
+ X --> FS
735
+ X --> NET
736
+ ```
737
+
738
+ ### Streaming event lifecycle
739
+
740
+ ```mermaid
741
+ sequenceDiagram
742
+ participant Dev as Developer
743
+ participant Thread as Thread.run_streamed()
744
+ participant Exec as CodexExec
745
+ participant CLI as codex exec
746
+
747
+ Dev->>Thread: run_streamed(prompt)
748
+ Thread->>Exec: spawn CLI with flags
749
+ Exec->>CLI: stdin prompt
750
+ CLI-->>Exec: JSONL line
751
+ Exec-->>Thread: raw line
752
+ Thread-->>Dev: ThreadEvent
753
+ CLI-->>Exec: JSONL line
754
+ Exec-->>Thread: raw line
755
+ Thread-->>Dev: ThreadEvent
756
+ CLI-->>Exec: exit code
757
+ Exec-->>Thread: completion
758
+ Thread-->>Dev: turn.completed / turn.failed
759
+ ```
760
+
761
+ ### PydanticAI model-provider loop
762
+
763
+ ```mermaid
764
+ sequenceDiagram
765
+ participant Agent as PydanticAI Agent
766
+ participant Model as CodexModel
767
+ participant SDK as Codex SDK
768
+ participant CLI as codex exec
769
+ participant Tools as User Tools
770
+
771
+ Agent->>Model: request(messages, tools)
772
+ Model->>SDK: start_thread + run_json(prompt, output_schema)
773
+ SDK->>CLI: codex exec --output-schema
774
+ CLI-->>SDK: JSON envelope {tool_calls, final}
775
+ SDK-->>Model: ParsedTurn
776
+ alt tool_calls present
777
+ Model-->>Agent: ToolCallPart(s)
778
+ Agent->>Tools: execute tool(s)
779
+ Tools-->>Agent: results
780
+ else final text allowed
781
+ Model-->>Agent: TextPart(final)
782
+ end
783
+ ```
784
+
785
+ ### PydanticAI handoff tool
786
+
787
+ ```mermaid
788
+ flowchart LR
789
+ Agent[PydanticAI Agent] --> Tool[codex_handoff_tool]
790
+ Tool --> SDK[Codex SDK Thread]
791
+ SDK --> CLI[Codex CLI]
792
+ CLI --> SDK
793
+ SDK --> Tool
794
+ Tool --> Agent
795
+ ```
796
+
797
+ <a id="testing"></a>
798
+ ## ![Testing](https://img.shields.io/badge/Testing-Pytest%20%26%20Coverage-2563eb?style=for-the-badge&logo=pytest&logoColor=white)
799
+
800
+ This repo uses unit tests with mocked CLI processes to keep the test suite fast and deterministic.
801
+
802
+ Test focus areas:
803
+ - `tests/test_exec.py`: CLI invocation, environment handling, config flags, abort behavior.
804
+ - `tests/test_thread.py`: parsing, streaming, JSON schema, Pydantic validation, input normalization.
805
+ - `tests/test_codex.py`: resume helpers and option wiring.
806
+ - `tests/test_abort.py`: abort signal semantics.
807
+ - `tests/test_telemetry.py`: Logfire span behavior.
808
+ - `tests/test_pydantic_ai_*`: PydanticAI model provider and handoff integration.
809
+
810
+ ### Run tests
811
+
812
+ ```bash
813
+ uv sync
814
+ uv run pytest
815
+ ```
816
+
817
+ Note: PydanticAI tests are skipped unless `pydantic-ai` is installed.
818
+
819
+ ### Coverage
820
+
821
+ ```bash
822
+ uv run pytest --cov=codex_sdk
823
+ ```
824
+
825
+ Coverage is configured in `pyproject.toml` with `fail_under = 95`.
826
+
827
+ ### Upgrade checklist
828
+
829
+ For SDK release updates, follow `UPGRADE_CHECKLIST.md`.
830
+
831
+ ### Format and lint
832
+
833
+ ```bash
834
+ uv run black src tests
835
+ uv run isort src tests
836
+ uv run flake8 src tests
837
+ ```
838
+
839
+ ### Type checking
840
+
841
+ ```bash
842
+ uv run mypy src
843
+ ```
844
+
845
+ <a id="ci-cd"></a>
846
+ ## ![CI/CD](https://img.shields.io/badge/CI%2FCD-Overview-1F4B99?style=for-the-badge&logo=gnubash&logoColor=white)
847
+
848
+ This repository includes GitHub Actions workflows under `.github/workflows/`.
849
+ The CI pipeline runs linting, type checks, and `pytest --cov=codex_sdk`.
850
+ Release automation creates GitHub releases from `CHANGELOG_SDK.md` when you push a
851
+ `vX.Y.Z` tag or manually dispatch the workflow, then the publish workflow uploads
852
+ the package to PyPI on release publish.
853
+
854
+ <a id="operations"></a>
855
+ ## ![Operations](https://img.shields.io/badge/Operations-Health%20%26%20Sessions-10b981?style=for-the-badge&logo=serverless&logoColor=white)
856
+
857
+ - Sessions are stored under `~/.codex/sessions` (or `CODEX_HOME`).
858
+ - Use `resume_thread(thread_id)` to continue a known session.
859
+ - Use `resume_last_thread()` to pick the most recent session automatically.
860
+ - Clean up stale sessions by removing old `rollout-*.jsonl` files if needed.
861
+
862
+ <a id="troubleshooting"></a>
863
+ ## ![Troubleshooting](https://img.shields.io/badge/Troubleshooting-Playbook-f97316?style=for-the-badge&logo=serverless&logoColor=white)
864
+
865
+ - **Codex CLI exited non-zero**: Catch `CodexCLIError` and inspect `.stderr`.
866
+ - **Unknown event type**: `CodexParseError` means the CLI emitted an unexpected JSONL entry.
867
+ - **Turn failed**: `TurnFailedError` indicates a `turn.failed` event.
868
+ - **Run canceled**: `CodexAbortError` indicates a triggered `AbortSignal`.
869
+ - **No thread id**: Ensure a `thread.started` event is emitted before resuming.
870
+
871
+ <a id="production"></a>
872
+ ## ![Production](https://img.shields.io/badge/Production-Readiness-0f766e?style=for-the-badge&logo=serverless&logoColor=white)
873
+
874
+ - Prefer `read-only` or `workspace-write` sandboxes in production.
875
+ - Set `working_directory` to a repo root and keep `skip_git_repo_check=False` where possible.
876
+ - Configure `approval_policy` for any tool execution requiring user consent.
877
+ - Disable `web_search_mode` and `network_access_enabled` unless explicitly needed.
878
+
879
+ <a id="license"></a>
880
+ ## ![License](https://img.shields.io/badge/License-Apache--2.0-0f766e?style=for-the-badge&logo=apache&logoColor=white)
881
+
882
+ Apache-2.0