codex-sdk-python 0.87.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,889 @@
1
+ Metadata-Version: 2.3
2
+ Name: codex-sdk-python
3
+ Version: 0.87.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.86.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
+ Text inputs may include `textElements` with `byteRange` to preserve UI annotations in history.
324
+ The SDK also accepts `text_elements`/`byte_range` and normalizes them to camelCase.
325
+
326
+ Codex 0.86.0+ supports optional `SKILL.toml` metadata alongside `SKILL.md`. When present,
327
+ `skills_list` responses include an `interface` object (display name, icons, brand color,
328
+ default prompt) for richer UI integrations.
329
+
330
+ #### App-server convenience methods
331
+
332
+ The SDK also exposes helpers for most app-server endpoints:
333
+
334
+ - Threads: `thread_list`, `thread_archive`, `thread_rollback`, `thread_loaded_list`
335
+ - Config: `config_read`, `config_value_write`, `config_batch_write`, `config_requirements_read`
336
+ - Skills: `skills_list`
337
+ - Turns/review: `turn_start`, `turn_interrupt`, `review_start`, `turn_session`
338
+ - Models: `model_list`
339
+ - One-off commands: `command_exec`
340
+ - MCP auth/status: `mcp_server_oauth_login`, `mcp_server_refresh`, `mcp_server_status_list`
341
+ - Account: `account_login_start`, `account_login_cancel`, `account_logout`,
342
+ `account_rate_limits_read`, `account_read`
343
+ - Feedback: `feedback_upload`
344
+
345
+ These map 1:1 to the Codex app-server protocol; see `codex/codex-rs/app-server/README.md`
346
+ for payload shapes and event semantics.
347
+
348
+ ### Observability (OTEL) and notify
349
+
350
+ Codex emits OTEL traces/logs/metrics when configured in `~/.codex/config.toml`.
351
+ For headless runs (`codex exec`), set `analytics.enabled=true` and provide OTEL exporters
352
+ in the config file. You can also pass overrides with `config_overrides`.
353
+
354
+ ```python
355
+ CodexOptions(
356
+ config_overrides={
357
+ "analytics.enabled": True,
358
+ "notify": ["python3", "/path/to/notify.py"],
359
+ }
360
+ )
361
+ ```
362
+
363
+ See `examples/notify_hook.py` for a ready-to-use notify script.
364
+
365
+ ### TurnOptions (per turn)
366
+
367
+ ```python
368
+ from codex_sdk import TurnOptions
369
+
370
+ TurnOptions(
371
+ output_schema={"type": "object", "properties": {"ok": {"type": "boolean"}}},
372
+ signal=controller.signal,
373
+ )
374
+ ```
375
+
376
+ - `output_schema` must be a JSON object (mapping). The SDK writes it to a temp file and passes `--output-schema`.
377
+ - `signal` is an `AbortSignal` for canceling an in-flight turn.
378
+
379
+ ### Bundled CLI binary and platform support
380
+
381
+ The SDK resolves a platform-specific Codex CLI binary under `src/codex_sdk/vendor/<target>/codex/`.
382
+ It selects the target triple based on OS and CPU and ensures the binary is executable on POSIX.
383
+
384
+ Supported target triples:
385
+ - Linux: `x86_64-unknown-linux-musl`, `aarch64-unknown-linux-musl`
386
+ - macOS: `x86_64-apple-darwin`, `aarch64-apple-darwin`
387
+ - Windows: `x86_64-pc-windows-msvc`, `aarch64-pc-windows-msvc`
388
+
389
+ If you are working from source and the vendor directory is missing, run `python scripts/setup_binary.py`
390
+ or follow `SETUP.md` to download the official npm package and copy the `vendor/` directory.
391
+
392
+ <a id="auth"></a>
393
+ ## ![Auth](https://img.shields.io/badge/Auth%20%26%20Credentials-Access-2563eb?style=for-the-badge&logo=gnubash&logoColor=white)
394
+
395
+ The SDK delegates authentication to the Codex CLI:
396
+ - Run `codex login` to create local credentials (stored under `~/.codex/` by the CLI).
397
+ - Or set `CODEX_API_KEY` (or pass `CodexOptions.api_key`) for headless use.
398
+ - `CodexOptions.base_url` sets `OPENAI_BASE_URL` to target an OpenAI-compatible endpoint.
399
+
400
+ <a id="usage"></a>
401
+ ## ![SDK Usage](https://img.shields.io/badge/SDK%20Usage-Core%20API-6366f1?style=for-the-badge&logo=python&logoColor=white)
402
+
403
+ ### Basic run
404
+
405
+ ```python
406
+ from codex_sdk import Codex
407
+
408
+ codex = Codex()
409
+ thread = codex.start_thread()
410
+ turn = await thread.run("Summarize the repository")
411
+ print(turn.final_response)
412
+ ```
413
+
414
+ ### Sync helpers (non-async)
415
+
416
+ ```python
417
+ from pydantic import BaseModel
418
+
419
+ class RepoStatus(BaseModel):
420
+ summary: str
421
+
422
+ turn = thread.run_sync("Summarize the repository")
423
+ parsed = thread.run_json_sync("Summarize", output_schema={"type": "object"})
424
+ validated = thread.run_pydantic_sync("Summarize", output_model=RepoStatus)
425
+ ```
426
+
427
+ Note: sync helpers raise `CodexError` if called from an active event loop.
428
+
429
+ ### Streaming events
430
+
431
+ ```python
432
+ result = await thread.run_streamed("Diagnose the test failure")
433
+ async for event in result.events:
434
+ if event.type == "item.completed":
435
+ print(event.item.type)
436
+ elif event.type == "turn.completed":
437
+ print(event.usage)
438
+ ```
439
+
440
+ To iterate directly without the wrapper:
441
+
442
+ ```python
443
+ async for event in thread.run_streamed_events("Diagnose the test failure"):
444
+ print(event.type)
445
+ ```
446
+
447
+ ### Hooks for streamed events
448
+
449
+ Use `ThreadHooks` to react to events without manually wiring an event loop.
450
+
451
+ ```python
452
+ from codex_sdk import ThreadHooks
453
+
454
+ hooks = ThreadHooks(
455
+ on_event=lambda event: print("event", event.type),
456
+ on_item_type={
457
+ "command_execution": lambda item: print("command", item.command),
458
+ },
459
+ )
460
+
461
+ turn = await thread.run_with_hooks("Run the tests and summarize failures.", hooks=hooks)
462
+ print(turn.final_response)
463
+ ```
464
+
465
+ ### Event types (ThreadEvent)
466
+
467
+ - `thread.started`
468
+ - `turn.started`
469
+ - `turn.completed` (includes token usage)
470
+ - `turn.failed`
471
+ - `item.started`
472
+ - `item.updated`
473
+ - `item.completed`
474
+ - `error`
475
+
476
+ ### Item types (ThreadItem)
477
+
478
+ - `agent_message`
479
+ - `reasoning`
480
+ - `command_execution`
481
+ - `file_change`
482
+ - `mcp_tool_call`
483
+ - `web_search`
484
+ - `todo_list`
485
+ - `error`
486
+
487
+ ### Structured output (JSON schema)
488
+
489
+ ```python
490
+ schema = {
491
+ "type": "object",
492
+ "properties": {
493
+ "summary": {"type": "string"},
494
+ "status": {"type": "string", "enum": ["ok", "action_required"]},
495
+ },
496
+ "required": ["summary", "status"],
497
+ "additionalProperties": False,
498
+ }
499
+
500
+ result = await thread.run_json("Summarize repository status", output_schema=schema)
501
+ print(result.output)
502
+ ```
503
+
504
+ ### Pydantic output validation
505
+
506
+ ```python
507
+ from pydantic import BaseModel
508
+
509
+ class RepoStatus(BaseModel):
510
+ summary: str
511
+ status: str
512
+
513
+ result = await thread.run_pydantic("Summarize repository status", output_model=RepoStatus)
514
+ print(result.output)
515
+ ```
516
+
517
+ ### Images + text
518
+
519
+ ```python
520
+ turn = await thread.run(
521
+ [
522
+ {"type": "text", "text": "Describe these screenshots"},
523
+ {"type": "local_image", "path": "./ui.png"},
524
+ {"type": "text", "text": "Focus on failures"},
525
+ {"type": "local_image", "path": "./diagram.jpg"},
526
+ ]
527
+ )
528
+ ```
529
+
530
+ ### Abort a running turn
531
+
532
+ ```python
533
+ import asyncio
534
+ from codex_sdk import AbortController, TurnOptions
535
+
536
+ controller = AbortController()
537
+ options = TurnOptions(signal=controller.signal)
538
+
539
+ task = asyncio.create_task(thread.run("Long task", options))
540
+ controller.abort("user requested cancel")
541
+ await task
542
+ ```
543
+
544
+ ### Thread resume helpers
545
+
546
+ ```python
547
+ from codex_sdk import Codex
548
+
549
+ codex = Codex()
550
+ thread = codex.resume_thread("<thread-id>")
551
+
552
+ # Or resume the most recent session (uses CODEX_HOME or ~/.codex)
553
+ last_thread = codex.resume_last_thread()
554
+ ```
555
+
556
+ ### Turn helpers
557
+
558
+ Each `Turn` provides convenience filters: `agent_messages()`, `reasoning()`, `commands()`,
559
+ `file_changes()`, `mcp_tool_calls()`, `web_searches()`, `todo_lists()`, and `errors()`.
560
+
561
+ <a id="api"></a>
562
+ ## ![API Reference](https://img.shields.io/badge/API-Reference-6366f1?style=for-the-badge&logo=python&logoColor=white)
563
+
564
+ Core classes:
565
+ - `Codex`: `start_thread()`, `resume_thread()`, `resume_last_thread()`.
566
+ - `Thread`: `run()`, `run_streamed()`, `run_streamed_events()`, `run_json()`, `run_pydantic()`,
567
+ plus `run_sync()`, `run_json_sync()`, `run_pydantic_sync()`.
568
+ - `Turn`: `items`, `final_response`, `usage`, and helper filters.
569
+ - `AppServerClient`, `AppServerTurnSession`, `ApprovalDecisions` for app-server integrations.
570
+ - `ThreadHooks` for event callbacks.
571
+ - `CodexOptions`, `ThreadOptions`, `TurnOptions`.
572
+ - `AbortController`, `AbortSignal`.
573
+
574
+ Exceptions:
575
+ - `CodexError`, `CodexCLIError`, `CodexParseError`, `CodexAbortError`, `TurnFailedError`.
576
+
577
+ Typed events and items:
578
+ - `ThreadEvent` union of `thread.*`, `turn.*`, `item.*`, and `error` events.
579
+ - `ThreadItem` union of `agent_message`, `reasoning`, `command_execution`, `file_change`,
580
+ `mcp_tool_call`, `web_search`, `todo_list`, `error`.
581
+
582
+ <a id="examples"></a>
583
+ ## ![Examples](https://img.shields.io/badge/Examples-Reference%20Scripts-6366f1?style=for-the-badge&logo=python&logoColor=white)
584
+
585
+ Example scripts under `examples/`:
586
+
587
+ - `basic_usage.py`: minimal `Codex` + `Thread` usage.
588
+ - `streaming_example.py`: live event streaming.
589
+ - `structured_output.py`: JSON schema output parsing.
590
+ - `thread_resume.py`: resume with `CODEX_THREAD_ID`.
591
+ - `permission_levels_example.py`: sandbox modes and working directory.
592
+ - `model_configuration_example.py`: model selection and endpoint config.
593
+ - `app_server_turn_session.py`: approval-handled turns over app-server.
594
+ - `hooks_streaming.py`: event hooks for streaming runs.
595
+ - `notify_hook.py`: notify script for CLI callbacks.
596
+ - `pydantic_ai_model_provider.py`: Codex as a PydanticAI model provider.
597
+ - `pydantic_ai_handoff.py`: Codex as a PydanticAI tool.
598
+
599
+ <a id="sandbox"></a>
600
+ ## ![Sandbox](https://img.shields.io/badge/Sandbox-Permissions%20%26%20Safety-1f2937?style=for-the-badge&logo=gnubash&logoColor=white)
601
+
602
+ The SDK forwards sandbox and approval controls directly to `codex exec`.
603
+
604
+ - `read-only`: can read files and run safe commands, no writes.
605
+ - `workspace-write`: can write inside the working directory and added directories.
606
+ - `danger-full-access`: unrestricted (use with caution).
607
+
608
+ Additional controls:
609
+ - `working_directory`: restricts where the CLI starts and what it can access.
610
+ - `additional_directories`: allowlist extra folders when using `workspace-write`.
611
+ - `approval_policy`: `never`, `on-request`, `on-failure`, `untrusted`.
612
+ - `network_access_enabled`: toggles network access in workspace-write sandbox.
613
+ - `web_search_mode`: toggles web search (`disabled`, `cached`, `live`).
614
+
615
+ <a id="pydantic-ai"></a>
616
+ ## ![PydanticAI](https://img.shields.io/badge/PydanticAI-Integrations-0b3b2e?style=for-the-badge&logo=pydantic&logoColor=white)
617
+
618
+ This SDK offers two ways to integrate with PydanticAI:
619
+
620
+ ### 1) Codex as a PydanticAI model provider
621
+
622
+ Use `CodexModel` to delegate tool-call planning and text generation to Codex, while PydanticAI executes tools and validates outputs.
623
+
624
+ ```python
625
+ from pydantic_ai import Agent, Tool
626
+
627
+ from codex_sdk.integrations.pydantic_ai_model import CodexModel
628
+ from codex_sdk.options import ThreadOptions
629
+
630
+ def add(a: int, b: int) -> int:
631
+ return a + b
632
+
633
+ model = CodexModel(
634
+ thread_options=ThreadOptions(
635
+ model="gpt-5",
636
+ sandbox_mode="read-only",
637
+ skip_git_repo_check=True,
638
+ )
639
+ )
640
+ agent = Agent(model, tools=[Tool(add)])
641
+
642
+ result = agent.run_sync("What's 19 + 23? Use the add tool.")
643
+ print(result.output)
644
+ ```
645
+
646
+ How it works:
647
+ - `CodexModel` builds a JSON schema envelope with `tool_calls` and `final`.
648
+ - Codex emits tool calls as JSON strings; PydanticAI runs them.
649
+ - If `allow_text_output` is true, Codex can place final text in `final`.
650
+ - Streaming APIs (`Agent.run_stream_events()`, `Agent.run_stream_sync()`) are supported; Codex
651
+ emits streamed responses as a single chunk once the turn completes.
652
+
653
+ Safety defaults (you can override with your own `ThreadOptions`):
654
+ - `sandbox_mode="read-only"`
655
+ - `skip_git_repo_check=True`
656
+ - `approval_policy="never"`
657
+ - `web_search_mode="disabled"`
658
+ - `network_access_enabled=False`
659
+
660
+ ### 2) Codex as a PydanticAI tool (handoff)
661
+
662
+ Register Codex as a tool and let a PydanticAI agent decide when to delegate tasks.
663
+
664
+ ```python
665
+ from pydantic_ai import Agent
666
+
667
+ from codex_sdk import ThreadOptions
668
+ from codex_sdk.integrations.pydantic_ai import codex_handoff_tool
669
+
670
+ tool = codex_handoff_tool(
671
+ thread_options=ThreadOptions(
672
+ sandbox_mode="workspace-write",
673
+ skip_git_repo_check=True,
674
+ working_directory=".",
675
+ ),
676
+ include_items=True,
677
+ items_limit=20,
678
+ )
679
+
680
+ agent = Agent(
681
+ "openai:gpt-5",
682
+ tools=[tool],
683
+ system_prompt=(
684
+ "You can delegate implementation details to the codex_handoff tool. "
685
+ "Use it for repository-aware edits, command execution, or patches."
686
+ ),
687
+ )
688
+
689
+ result = await agent.run(
690
+ "Use the codex_handoff tool to scan this repository and suggest one small DX improvement."
691
+ )
692
+ print(result.output)
693
+ ```
694
+
695
+ Handoff options:
696
+ - `persist_thread`: keep a single Codex thread across tool calls (default true).
697
+ - `include_items`: include a summarized item list in tool output.
698
+ - `items_limit`: cap the number of items returned.
699
+ - `include_usage`: include token usage.
700
+ - `timeout_seconds`: wrap the run in `asyncio.wait_for`.
701
+
702
+ <a id="telemetry"></a>
703
+ ## ![Telemetry](https://img.shields.io/badge/Telemetry-Logfire%20Spans-f97316?style=for-the-badge&logo=simpleicons&logoColor=white)
704
+
705
+ If `logfire` is installed and initialized, the SDK emits spans:
706
+ - `codex_sdk.exec`
707
+ - `codex_sdk.thread.turn`
708
+ - `codex_sdk.pydantic_ai.model_request`
709
+ - `codex_sdk.pydantic_ai.handoff`
710
+
711
+ If Logfire is missing or not initialized, the span context manager is a no-op.
712
+
713
+ <a id="architecture"></a>
714
+ <a id="acheature"></a>
715
+ ## ![Architecture](https://img.shields.io/badge/Architecture-Stack%20map-1f2937?style=for-the-badge&logo=serverless&logoColor=white)
716
+
717
+ ### System components
718
+
719
+ ```mermaid
720
+ flowchart LR
721
+ subgraph App[Your Python App]
722
+ U[User Code]
723
+ T[Thread API]
724
+ end
725
+
726
+ subgraph SDK[Codex SDK]
727
+ C[Codex]
728
+ E[CodexExec]
729
+ P[Event Parser]
730
+ end
731
+
732
+ subgraph CLI[Bundled Codex CLI]
733
+ X["codex exec --experimental-json"]
734
+ end
735
+
736
+ FS[(Filesystem)]
737
+ NET[(Network)]
738
+
739
+ U --> T --> C --> E --> X
740
+ X -->|JSONL events| P --> T
741
+ X --> FS
742
+ X --> NET
743
+ ```
744
+
745
+ ### Streaming event lifecycle
746
+
747
+ ```mermaid
748
+ sequenceDiagram
749
+ participant Dev as Developer
750
+ participant Thread as Thread.run_streamed()
751
+ participant Exec as CodexExec
752
+ participant CLI as codex exec
753
+
754
+ Dev->>Thread: run_streamed(prompt)
755
+ Thread->>Exec: spawn CLI with flags
756
+ Exec->>CLI: stdin prompt
757
+ CLI-->>Exec: JSONL line
758
+ Exec-->>Thread: raw line
759
+ Thread-->>Dev: ThreadEvent
760
+ CLI-->>Exec: JSONL line
761
+ Exec-->>Thread: raw line
762
+ Thread-->>Dev: ThreadEvent
763
+ CLI-->>Exec: exit code
764
+ Exec-->>Thread: completion
765
+ Thread-->>Dev: turn.completed / turn.failed
766
+ ```
767
+
768
+ ### PydanticAI model-provider loop
769
+
770
+ ```mermaid
771
+ sequenceDiagram
772
+ participant Agent as PydanticAI Agent
773
+ participant Model as CodexModel
774
+ participant SDK as Codex SDK
775
+ participant CLI as codex exec
776
+ participant Tools as User Tools
777
+
778
+ Agent->>Model: request(messages, tools)
779
+ Model->>SDK: start_thread + run_json(prompt, output_schema)
780
+ SDK->>CLI: codex exec --output-schema
781
+ CLI-->>SDK: JSON envelope {tool_calls, final}
782
+ SDK-->>Model: ParsedTurn
783
+ alt tool_calls present
784
+ Model-->>Agent: ToolCallPart(s)
785
+ Agent->>Tools: execute tool(s)
786
+ Tools-->>Agent: results
787
+ else final text allowed
788
+ Model-->>Agent: TextPart(final)
789
+ end
790
+ ```
791
+
792
+ ### PydanticAI handoff tool
793
+
794
+ ```mermaid
795
+ flowchart LR
796
+ Agent[PydanticAI Agent] --> Tool[codex_handoff_tool]
797
+ Tool --> SDK[Codex SDK Thread]
798
+ SDK --> CLI[Codex CLI]
799
+ CLI --> SDK
800
+ SDK --> Tool
801
+ Tool --> Agent
802
+ ```
803
+
804
+ <a id="testing"></a>
805
+ ## ![Testing](https://img.shields.io/badge/Testing-Pytest%20%26%20Coverage-2563eb?style=for-the-badge&logo=pytest&logoColor=white)
806
+
807
+ This repo uses unit tests with mocked CLI processes to keep the test suite fast and deterministic.
808
+
809
+ Test focus areas:
810
+ - `tests/test_exec.py`: CLI invocation, environment handling, config flags, abort behavior.
811
+ - `tests/test_thread.py`: parsing, streaming, JSON schema, Pydantic validation, input normalization.
812
+ - `tests/test_codex.py`: resume helpers and option wiring.
813
+ - `tests/test_abort.py`: abort signal semantics.
814
+ - `tests/test_telemetry.py`: Logfire span behavior.
815
+ - `tests/test_pydantic_ai_*`: PydanticAI model provider and handoff integration.
816
+
817
+ ### Run tests
818
+
819
+ ```bash
820
+ uv sync
821
+ uv run pytest
822
+ ```
823
+
824
+ Note: PydanticAI tests are skipped unless `pydantic-ai` is installed.
825
+
826
+ ### Coverage
827
+
828
+ ```bash
829
+ uv run pytest --cov=codex_sdk
830
+ ```
831
+
832
+ Coverage is configured in `pyproject.toml` with `fail_under = 95`.
833
+
834
+ ### Upgrade checklist
835
+
836
+ For SDK release updates, follow `UPGRADE_CHECKLIST.md`.
837
+
838
+ ### Format and lint
839
+
840
+ ```bash
841
+ uv run black src tests
842
+ uv run isort src tests
843
+ uv run flake8 src tests
844
+ ```
845
+
846
+ ### Type checking
847
+
848
+ ```bash
849
+ uv run mypy src
850
+ ```
851
+
852
+ <a id="ci-cd"></a>
853
+ ## ![CI/CD](https://img.shields.io/badge/CI%2FCD-Overview-1F4B99?style=for-the-badge&logo=gnubash&logoColor=white)
854
+
855
+ This repository includes GitHub Actions workflows under `.github/workflows/`.
856
+ The CI pipeline runs linting, type checks, and `pytest --cov=codex_sdk`.
857
+ Release automation creates GitHub releases from `CHANGELOG_SDK.md` when you push a
858
+ `vX.Y.Z` tag or manually dispatch the workflow, then the publish workflow uploads
859
+ the package to PyPI on release publish.
860
+
861
+ <a id="operations"></a>
862
+ ## ![Operations](https://img.shields.io/badge/Operations-Health%20%26%20Sessions-10b981?style=for-the-badge&logo=serverless&logoColor=white)
863
+
864
+ - Sessions are stored under `~/.codex/sessions` (or `CODEX_HOME`).
865
+ - Use `resume_thread(thread_id)` to continue a known session.
866
+ - Use `resume_last_thread()` to pick the most recent session automatically.
867
+ - Clean up stale sessions by removing old `rollout-*.jsonl` files if needed.
868
+
869
+ <a id="troubleshooting"></a>
870
+ ## ![Troubleshooting](https://img.shields.io/badge/Troubleshooting-Playbook-f97316?style=for-the-badge&logo=serverless&logoColor=white)
871
+
872
+ - **Codex CLI exited non-zero**: Catch `CodexCLIError` and inspect `.stderr`.
873
+ - **Unknown event type**: `CodexParseError` means the CLI emitted an unexpected JSONL entry.
874
+ - **Turn failed**: `TurnFailedError` indicates a `turn.failed` event.
875
+ - **Run canceled**: `CodexAbortError` indicates a triggered `AbortSignal`.
876
+ - **No thread id**: Ensure a `thread.started` event is emitted before resuming.
877
+
878
+ <a id="production"></a>
879
+ ## ![Production](https://img.shields.io/badge/Production-Readiness-0f766e?style=for-the-badge&logo=serverless&logoColor=white)
880
+
881
+ - Prefer `read-only` or `workspace-write` sandboxes in production.
882
+ - Set `working_directory` to a repo root and keep `skip_git_repo_check=False` where possible.
883
+ - Configure `approval_policy` for any tool execution requiring user consent.
884
+ - Disable `web_search_mode` and `network_access_enabled` unless explicitly needed.
885
+
886
+ <a id="license"></a>
887
+ ## ![License](https://img.shields.io/badge/License-Apache--2.0-0f766e?style=for-the-badge&logo=apache&logoColor=white)
888
+
889
+ Apache-2.0