codex-python 1.0.1__tar.gz → 1.114.1__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.
- codex_python-1.114.1/PKG-INFO +223 -0
- codex_python-1.114.1/README.md +200 -0
- codex_python-1.114.1/codex/__init__.py +32 -0
- {codex_python-1.0.1 → codex_python-1.114.1}/codex/_binary.py +5 -1
- codex_python-1.114.1/codex/_config_types.py +6 -0
- codex_python-1.114.1/codex/_file_utils.py +18 -0
- codex_python-1.114.1/codex/_runtime.py +122 -0
- codex_python-1.114.1/codex/_turn_options.py +28 -0
- codex_python-1.114.1/codex/app_server/__init__.py +51 -0
- codex_python-1.114.1/codex/app_server/_async_client.py +232 -0
- codex_python-1.114.1/codex/app_server/_async_services.py +377 -0
- codex_python-1.114.1/codex/app_server/_async_threads.py +503 -0
- codex_python-1.114.1/codex/app_server/_payloads.py +59 -0
- codex_python-1.114.1/codex/app_server/_protocol_helpers.py +201 -0
- codex_python-1.114.1/codex/app_server/_session.py +419 -0
- codex_python-1.114.1/codex/app_server/_sync_client.py +358 -0
- codex_python-1.114.1/codex/app_server/_sync_services.py +587 -0
- codex_python-1.114.1/codex/app_server/_sync_support.py +14 -0
- codex_python-1.114.1/codex/app_server/_sync_threads.py +329 -0
- codex_python-1.114.1/codex/app_server/_types.py +5 -0
- codex_python-1.114.1/codex/app_server/errors.py +43 -0
- codex_python-1.114.1/codex/app_server/models.py +260 -0
- codex_python-1.114.1/codex/app_server/options.py +246 -0
- codex_python-1.114.1/codex/app_server/transports.py +259 -0
- codex_python-1.114.1/codex/codex.py +175 -0
- {codex_python-1.0.1 → codex_python-1.114.1}/codex/errors.py +10 -0
- codex_python-1.114.1/codex/options.py +127 -0
- codex_python-1.114.1/codex/output_schema.py +36 -0
- {codex_python-1.0.1 → codex_python-1.114.1}/codex/output_schema_file.py +7 -6
- codex_python-1.114.1/codex/protocol/__init__.py +3 -0
- codex_python-1.114.1/codex/protocol/types.py +6670 -0
- codex_python-1.114.1/codex/thread.py +313 -0
- {codex_python-1.0.1 → codex_python-1.114.1}/crates/codex_native/Cargo.lock +15 -15
- {codex_python-1.0.1 → codex_python-1.114.1}/crates/codex_native/Cargo.toml +1 -1
- {codex_python-1.0.1 → codex_python-1.114.1}/pyproject.toml +21 -1
- codex_python-1.0.1/PKG-INFO +0 -154
- codex_python-1.0.1/README.md +0 -135
- codex_python-1.0.1/codex/__init__.py +0 -86
- codex_python-1.0.1/codex/codex.py +0 -26
- codex_python-1.0.1/codex/events.py +0 -66
- codex_python-1.0.1/codex/exec.py +0 -313
- codex_python-1.0.1/codex/items.py +0 -110
- codex_python-1.0.1/codex/options.py +0 -57
- codex_python-1.0.1/codex/thread.py +0 -170
- {codex_python-1.0.1 → codex_python-1.114.1}/LICENSE +0 -0
- {codex_python-1.0.1 → codex_python-1.114.1}/codex/py.typed +0 -0
- {codex_python-1.0.1 → codex_python-1.114.1}/codex/vendor/.gitkeep +0 -0
- {codex_python-1.0.1 → codex_python-1.114.1}/crates/codex_native/codex/__init__.py +0 -0
- {codex_python-1.0.1 → codex_python-1.114.1}/crates/codex_native/src/lib.rs +0 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codex-python
|
|
3
|
+
Version: 1.114.1
|
|
4
|
+
Classifier: Programming Language :: Python :: 3
|
|
5
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
6
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Typing :: Typed
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Dist: pydantic>=2.11.7,<3
|
|
12
|
+
Requires-Dist: websockets>=15.0.1,<16 ; extra == 'websocket'
|
|
13
|
+
Provides-Extra: websocket
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Summary: Python SDK for Codex CLI with bundled platform binaries
|
|
16
|
+
Keywords: codex,sdk,cli,automation
|
|
17
|
+
Requires-Python: >=3.12
|
|
18
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
19
|
+
Project-URL: Homepage, https://github.com/gersmann/codex-python
|
|
20
|
+
Project-URL: Issues, https://github.com/gersmann/codex-python/issues
|
|
21
|
+
Project-URL: Repository, https://github.com/gersmann/codex-python
|
|
22
|
+
|
|
23
|
+
# codex-python
|
|
24
|
+
|
|
25
|
+
Python SDK for Codex with bundled `codex` binaries inside platform wheels.
|
|
26
|
+
|
|
27
|
+
This package exposes two supported APIs:
|
|
28
|
+
|
|
29
|
+
- `Codex`: a simple, local convenience interface backed by a private stdio app-server session
|
|
30
|
+
- `AppServerClient`: a richer app-server client for thread management, streaming events, approvals, and typed protocol access
|
|
31
|
+
|
|
32
|
+
Canonical import paths:
|
|
33
|
+
|
|
34
|
+
- use `from codex import ...` for the high-level `Codex` facade
|
|
35
|
+
- use `from codex.app_server import ...` for the raw app-server client and app-server option types
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install codex-python
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Which API should I use?
|
|
44
|
+
|
|
45
|
+
### `Codex`
|
|
46
|
+
|
|
47
|
+
Use `Codex` when you want the smallest surface area for local automation:
|
|
48
|
+
|
|
49
|
+
- one private local app-server session per `Codex` instance
|
|
50
|
+
- stateless `run*()` convenience (fresh internal thread per call)
|
|
51
|
+
- stateful thread workflows when needed via `start_thread()` / `resume_thread()`
|
|
52
|
+
- simple request/response usage
|
|
53
|
+
- optional streaming over the exec event stream
|
|
54
|
+
- structured output via `TurnOptions(output_schema=...)`
|
|
55
|
+
|
|
56
|
+
### `AppServerClient`
|
|
57
|
+
|
|
58
|
+
Use `AppServerClient` when you want a deeper integration:
|
|
59
|
+
|
|
60
|
+
- persistent app-server connection
|
|
61
|
+
- thread objects and turn streams
|
|
62
|
+
- protocol-native notifications
|
|
63
|
+
- server-driven requests such as tool callbacks and approvals
|
|
64
|
+
- typed protocol models and raw JSON-RPC access when needed
|
|
65
|
+
|
|
66
|
+
## Quickstart: `Codex`
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from codex import Codex
|
|
70
|
+
|
|
71
|
+
client = Codex()
|
|
72
|
+
summary = client.run_text("Diagnose the failing tests and propose a fix")
|
|
73
|
+
print(summary)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
More `Codex` examples: [docs/exec_api.md](docs/exec_api.md)
|
|
77
|
+
|
|
78
|
+
## Quickstart: `AppServerClient`
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from codex.app_server import AppServerClient, AppServerClientInfo, AppServerInitializeOptions
|
|
82
|
+
|
|
83
|
+
initialize_options = AppServerInitializeOptions(
|
|
84
|
+
client_info=AppServerClientInfo(
|
|
85
|
+
name="my_integration",
|
|
86
|
+
title="My Integration",
|
|
87
|
+
version="0.1.0",
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
with AppServerClient.connect_stdio(initialize_options=initialize_options) as client:
|
|
92
|
+
thread = client.start_thread()
|
|
93
|
+
summary = thread.run_text("Briefly summarize this repository's purpose.")
|
|
94
|
+
print(summary)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
More app-server examples: [docs/app_server.md](docs/app_server.md)
|
|
98
|
+
For websocket transport, install the optional extra: `pip install "codex-python[websocket]"`.
|
|
99
|
+
|
|
100
|
+
## Structured output
|
|
101
|
+
|
|
102
|
+
### `Codex`
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from codex import Codex, TurnOptions
|
|
106
|
+
|
|
107
|
+
schema = {
|
|
108
|
+
"type": "object",
|
|
109
|
+
"properties": {"summary": {"type": "string"}},
|
|
110
|
+
"required": ["summary"],
|
|
111
|
+
"additionalProperties": False,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
client = Codex()
|
|
115
|
+
payload = client.run_json("Summarize repository status", TurnOptions(output_schema=schema))
|
|
116
|
+
print(payload["summary"])
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### `AppServerClient`
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
from pydantic import BaseModel
|
|
123
|
+
|
|
124
|
+
from codex.app_server import AppServerClient, AppServerTurnOptions
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class Summary(BaseModel):
|
|
128
|
+
summary: str
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
with AppServerClient.connect_stdio() as client:
|
|
132
|
+
thread = client.start_thread()
|
|
133
|
+
result = thread.run_model(
|
|
134
|
+
"Summarize repository status",
|
|
135
|
+
Summary,
|
|
136
|
+
)
|
|
137
|
+
print(result.summary)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`run_model()` uses `Summary` both as the validation model and, by default, as the output schema sent
|
|
141
|
+
to Codex. If you want JSON back without validation, you can also pass the model class directly to
|
|
142
|
+
`output_schema`, for example `thread.run_json(..., AppServerTurnOptions(output_schema=Summary))`.
|
|
143
|
+
|
|
144
|
+
## Streaming
|
|
145
|
+
|
|
146
|
+
### `Codex` stream
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from codex import Codex
|
|
150
|
+
from codex.protocol import types as protocol
|
|
151
|
+
|
|
152
|
+
client = Codex()
|
|
153
|
+
stream = client.run("Investigate this bug")
|
|
154
|
+
for event in stream:
|
|
155
|
+
if isinstance(event, protocol.ItemAgentMessageDeltaNotification):
|
|
156
|
+
print(event.params.delta, end="", flush=True)
|
|
157
|
+
|
|
158
|
+
print()
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`Codex.run*()` starts a fresh internal thread for each call. Use
|
|
162
|
+
`start_thread()` or `resume_thread()` when you want later runs to share context.
|
|
163
|
+
|
|
164
|
+
High-level `Codex` helpers raise `ThreadRunError` on failed or interrupted terminal turns and
|
|
165
|
+
preserve the final turn metadata on the exception for debugging and UI handling.
|
|
166
|
+
|
|
167
|
+
### App-server stream
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from codex.app_server import AppServerClient
|
|
171
|
+
from codex.protocol import types as protocol
|
|
172
|
+
|
|
173
|
+
with AppServerClient.connect_stdio() as client:
|
|
174
|
+
thread = client.start_thread()
|
|
175
|
+
stream = thread.run("Investigate this bug")
|
|
176
|
+
|
|
177
|
+
for event in stream:
|
|
178
|
+
if isinstance(event, protocol.ItemAgentMessageDeltaNotification):
|
|
179
|
+
print(event.params.delta, end="", flush=True)
|
|
180
|
+
|
|
181
|
+
print()
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
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)
|
|
185
|
+
|
|
186
|
+
## Examples
|
|
187
|
+
|
|
188
|
+
- [examples/basic_conversation.py](examples/basic_conversation.py): minimal `Codex` flow
|
|
189
|
+
- [examples/app_server_conversation.py](examples/app_server_conversation.py): minimal app-server flow
|
|
190
|
+
- [examples/app_server_websocket_conversation.py](examples/app_server_websocket_conversation.py): minimal websocket app-server flow
|
|
191
|
+
- [examples/app_server_stream_events.py](examples/app_server_stream_events.py): protocol-native app-server streaming
|
|
192
|
+
- [examples/app_server_tool_handler.py](examples/app_server_tool_handler.py): typed app-server request handling
|
|
193
|
+
|
|
194
|
+
## Bundled binary behavior
|
|
195
|
+
|
|
196
|
+
By default, the SDK resolves the bundled binary at:
|
|
197
|
+
|
|
198
|
+
`codex/vendor/<target-triple>/codex/{codex|codex.exe}`
|
|
199
|
+
|
|
200
|
+
If the bundled binary is not present, for example in a source checkout, the SDK falls back to
|
|
201
|
+
`codex` on `PATH`.
|
|
202
|
+
|
|
203
|
+
You can override the executable path with:
|
|
204
|
+
|
|
205
|
+
- `CodexOptions(codex_path_override=...)`
|
|
206
|
+
- `codex.app_server.AppServerProcessOptions(codex_path_override=...)`
|
|
207
|
+
|
|
208
|
+
## Development
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
make lint
|
|
212
|
+
make test
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
`make test` emits a terminal coverage report, writes `coverage.xml`, and enforces the repository
|
|
216
|
+
coverage gate.
|
|
217
|
+
|
|
218
|
+
If you want to test vendored-binary behavior locally, fetch binaries into `codex/vendor`:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
python scripts/fetch_codex_binary.py --target-triple x86_64-unknown-linux-musl
|
|
222
|
+
```
|
|
223
|
+
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# codex-python
|
|
2
|
+
|
|
3
|
+
Python SDK for Codex with bundled `codex` binaries inside platform wheels.
|
|
4
|
+
|
|
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
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install codex-python
|
|
19
|
+
```
|
|
20
|
+
|
|
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`
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from codex import Codex
|
|
48
|
+
|
|
49
|
+
client = Codex()
|
|
50
|
+
summary = client.run_text("Diagnose the failing tests and propose a fix")
|
|
51
|
+
print(summary)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
More `Codex` examples: [docs/exec_api.md](docs/exec_api.md)
|
|
55
|
+
|
|
56
|
+
## Quickstart: `AppServerClient`
|
|
57
|
+
|
|
58
|
+
```python
|
|
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
|
+
)
|
|
68
|
+
|
|
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)
|
|
73
|
+
```
|
|
74
|
+
|
|
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
|
+
|
|
78
|
+
## Structured output
|
|
79
|
+
|
|
80
|
+
### `Codex`
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from codex import Codex, TurnOptions
|
|
84
|
+
|
|
85
|
+
schema = {
|
|
86
|
+
"type": "object",
|
|
87
|
+
"properties": {"summary": {"type": "string"}},
|
|
88
|
+
"required": ["summary"],
|
|
89
|
+
"additionalProperties": False,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
client = Codex()
|
|
93
|
+
payload = client.run_json("Summarize repository status", TurnOptions(output_schema=schema))
|
|
94
|
+
print(payload["summary"])
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### `AppServerClient`
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from pydantic import BaseModel
|
|
101
|
+
|
|
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)
|
|
116
|
+
```
|
|
117
|
+
|
|
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
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from codex import Codex
|
|
128
|
+
from codex.protocol import types as protocol
|
|
129
|
+
|
|
130
|
+
client = Codex()
|
|
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()
|
|
137
|
+
```
|
|
138
|
+
|
|
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.
|
|
141
|
+
|
|
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.
|
|
144
|
+
|
|
145
|
+
### App-server stream
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from codex.app_server import AppServerClient
|
|
149
|
+
from codex.protocol import types as protocol
|
|
150
|
+
|
|
151
|
+
with AppServerClient.connect_stdio() as client:
|
|
152
|
+
thread = client.start_thread()
|
|
153
|
+
stream = thread.run("Investigate this bug")
|
|
154
|
+
|
|
155
|
+
for event in stream:
|
|
156
|
+
if isinstance(event, protocol.ItemAgentMessageDeltaNotification):
|
|
157
|
+
print(event.params.delta, end="", flush=True)
|
|
158
|
+
|
|
159
|
+
print()
|
|
160
|
+
```
|
|
161
|
+
|
|
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
|
+
|
|
172
|
+
## Bundled binary behavior
|
|
173
|
+
|
|
174
|
+
By default, the SDK resolves the bundled binary at:
|
|
175
|
+
|
|
176
|
+
`codex/vendor/<target-triple>/codex/{codex|codex.exe}`
|
|
177
|
+
|
|
178
|
+
If the bundled binary is not present, for example in a source checkout, the SDK falls back to
|
|
179
|
+
`codex` on `PATH`.
|
|
180
|
+
|
|
181
|
+
You can override the executable path with:
|
|
182
|
+
|
|
183
|
+
- `CodexOptions(codex_path_override=...)`
|
|
184
|
+
- `codex.app_server.AppServerProcessOptions(codex_path_override=...)`
|
|
185
|
+
|
|
186
|
+
## Development
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
make lint
|
|
190
|
+
make test
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
`make test` emits a terminal coverage report, writes `coverage.xml`, and enforces the repository
|
|
194
|
+
coverage gate.
|
|
195
|
+
|
|
196
|
+
If you want to test vendored-binary behavior locally, fetch binaries into `codex/vendor`:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
python scripts/fetch_codex_binary.py --target-triple x86_64-unknown-linux-musl
|
|
200
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Python SDK for embedding Codex via the bundled CLI binary."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from codex.codex import Codex
|
|
6
|
+
from codex.errors import CodexError, CodexExecError, CodexParseError, ThreadRunError
|
|
7
|
+
from codex.options import (
|
|
8
|
+
CancelSignal,
|
|
9
|
+
CodexConfigObject,
|
|
10
|
+
CodexConfigValue,
|
|
11
|
+
CodexOptions,
|
|
12
|
+
ThreadResumeOptions,
|
|
13
|
+
ThreadStartOptions,
|
|
14
|
+
TurnOptions,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__version__ = "1.114.1"
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"Codex",
|
|
21
|
+
"CodexError",
|
|
22
|
+
"CodexExecError",
|
|
23
|
+
"CodexParseError",
|
|
24
|
+
"ThreadRunError",
|
|
25
|
+
"CodexOptions",
|
|
26
|
+
"ThreadStartOptions",
|
|
27
|
+
"ThreadResumeOptions",
|
|
28
|
+
"TurnOptions",
|
|
29
|
+
"CodexConfigValue",
|
|
30
|
+
"CodexConfigObject",
|
|
31
|
+
"CancelSignal",
|
|
32
|
+
]
|
|
@@ -6,6 +6,10 @@ from pathlib import Path
|
|
|
6
6
|
from codex.errors import CodexExecError
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
class BundledCodexNotFoundError(CodexExecError):
|
|
10
|
+
"""The platform wheel does not contain a bundled Codex binary."""
|
|
11
|
+
|
|
12
|
+
|
|
9
13
|
def resolve_target_triple(system_name: str | None = None, machine_name: str | None = None) -> str:
|
|
10
14
|
system = (system_name or platform.system()).lower()
|
|
11
15
|
machine = (machine_name or platform.machine()).lower()
|
|
@@ -35,7 +39,7 @@ def bundled_codex_path(target_triple: str | None = None) -> Path:
|
|
|
35
39
|
binary_name = "codex.exe" if "windows" in triple else "codex"
|
|
36
40
|
binary_path = package_root / "vendor" / triple / "codex" / binary_name
|
|
37
41
|
if not binary_path.exists():
|
|
38
|
-
raise
|
|
42
|
+
raise BundledCodexNotFoundError(
|
|
39
43
|
"Bundled codex binary not found at "
|
|
40
44
|
f"{binary_path}. Install a platform wheel or provide codex_path_override."
|
|
41
45
|
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import tempfile
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def atomic_write_text(path: Path, text: str, *, encoding: str = "utf-8") -> None:
|
|
9
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
10
|
+
fd, temp_path_str = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
|
|
11
|
+
temp_path = Path(temp_path_str)
|
|
12
|
+
try:
|
|
13
|
+
with os.fdopen(fd, "w", encoding=encoding) as handle:
|
|
14
|
+
handle.write(text)
|
|
15
|
+
temp_path.replace(path)
|
|
16
|
+
except Exception:
|
|
17
|
+
temp_path.unlink(missing_ok=True)
|
|
18
|
+
raise
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import math
|
|
5
|
+
import re
|
|
6
|
+
from collections.abc import Callable, Mapping
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from codex._binary import BundledCodexNotFoundError
|
|
10
|
+
from codex._config_types import CodexConfigObject, CodexConfigValue
|
|
11
|
+
|
|
12
|
+
INTERNAL_ORIGINATOR_ENV = "CODEX_INTERNAL_ORIGINATOR_OVERRIDE"
|
|
13
|
+
PYTHON_SDK_ORIGINATOR = "codex_sdk_py"
|
|
14
|
+
TOML_BARE_KEY = re.compile(r"^[A-Za-z0-9_-]+$")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def build_child_env(
|
|
18
|
+
env_override: Mapping[str, str] | None,
|
|
19
|
+
*,
|
|
20
|
+
base_url: str | None = None,
|
|
21
|
+
api_key: str | None = None,
|
|
22
|
+
) -> dict[str, str]:
|
|
23
|
+
env = {} if env_override is None else dict(env_override)
|
|
24
|
+
if INTERNAL_ORIGINATOR_ENV not in env:
|
|
25
|
+
env[INTERNAL_ORIGINATOR_ENV] = PYTHON_SDK_ORIGINATOR
|
|
26
|
+
if base_url is not None:
|
|
27
|
+
env["OPENAI_BASE_URL"] = base_url
|
|
28
|
+
if api_key is not None:
|
|
29
|
+
env["CODEX_API_KEY"] = api_key
|
|
30
|
+
return env
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def resolve_codex_path(
|
|
34
|
+
executable_path: str | None,
|
|
35
|
+
*,
|
|
36
|
+
bundled_path: Callable[[], Path],
|
|
37
|
+
which: Callable[[str], str | None],
|
|
38
|
+
error_type: type[Exception],
|
|
39
|
+
) -> str:
|
|
40
|
+
if executable_path is not None:
|
|
41
|
+
return str(Path(executable_path))
|
|
42
|
+
try:
|
|
43
|
+
return str(bundled_path())
|
|
44
|
+
except BundledCodexNotFoundError as bundled_error:
|
|
45
|
+
system_codex = which("codex")
|
|
46
|
+
if system_codex is None:
|
|
47
|
+
raise error_type(
|
|
48
|
+
f"{bundled_error} Also failed to find `codex` on PATH."
|
|
49
|
+
) from bundled_error
|
|
50
|
+
return system_codex
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def serialize_config_overrides(config_overrides: CodexConfigObject) -> list[str]:
|
|
54
|
+
overrides: list[str] = []
|
|
55
|
+
_flatten_config_overrides(config_overrides, "", overrides)
|
|
56
|
+
return overrides
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _flatten_config_overrides(
|
|
60
|
+
value: CodexConfigValue | CodexConfigObject,
|
|
61
|
+
prefix: str,
|
|
62
|
+
overrides: list[str],
|
|
63
|
+
) -> None:
|
|
64
|
+
if not isinstance(value, dict):
|
|
65
|
+
if prefix == "":
|
|
66
|
+
raise ValueError("Codex config overrides must be a plain object")
|
|
67
|
+
overrides.append(f"{prefix}={_to_toml_value(value, prefix)}")
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
entries = list(value.items())
|
|
71
|
+
if prefix == "" and not entries:
|
|
72
|
+
return
|
|
73
|
+
if prefix != "" and not entries:
|
|
74
|
+
overrides.append(f"{prefix}={{}}")
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
for key, child in entries:
|
|
78
|
+
if not isinstance(key, str) or key == "":
|
|
79
|
+
raise ValueError("Codex config override keys must be non-empty strings")
|
|
80
|
+
path = f"{prefix}.{key}" if prefix else key
|
|
81
|
+
if isinstance(child, dict):
|
|
82
|
+
_flatten_config_overrides(child, path, overrides)
|
|
83
|
+
else:
|
|
84
|
+
overrides.append(f"{path}={_to_toml_value(child, path)}")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _to_toml_value(value: CodexConfigValue, path: str) -> str:
|
|
88
|
+
if isinstance(value, str):
|
|
89
|
+
return json.dumps(value)
|
|
90
|
+
if isinstance(value, bool):
|
|
91
|
+
return format_toml_bool(value)
|
|
92
|
+
if isinstance(value, int):
|
|
93
|
+
return f"{value}"
|
|
94
|
+
if isinstance(value, float):
|
|
95
|
+
if not math.isfinite(value):
|
|
96
|
+
raise ValueError(f"Codex config override at {path} must be a finite number")
|
|
97
|
+
return f"{value}"
|
|
98
|
+
if isinstance(value, list):
|
|
99
|
+
rendered_items = [
|
|
100
|
+
_to_toml_value(item, f"{path}[{index}]") for index, item in enumerate(value)
|
|
101
|
+
]
|
|
102
|
+
return f"[{', '.join(rendered_items)}]"
|
|
103
|
+
if isinstance(value, dict):
|
|
104
|
+
parts: list[str] = []
|
|
105
|
+
for key, child in value.items():
|
|
106
|
+
if not isinstance(key, str) or key == "":
|
|
107
|
+
raise ValueError("Codex config override keys must be non-empty strings")
|
|
108
|
+
parts.append(f"{format_toml_key(key)} = {_to_toml_value(child, f'{path}.{key}')}")
|
|
109
|
+
return f"{{{', '.join(parts)}}}"
|
|
110
|
+
if value is None:
|
|
111
|
+
raise ValueError(f"Codex config override at {path} cannot be null")
|
|
112
|
+
raise ValueError(f"Unsupported Codex config override value at {path}: {type(value).__name__}")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def format_toml_key(key: str) -> str:
|
|
116
|
+
if TOML_BARE_KEY.match(key):
|
|
117
|
+
return key
|
|
118
|
+
return json.dumps(key)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def format_toml_bool(value: bool) -> str:
|
|
122
|
+
return "true" if value else "false"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Shared turn-option helpers for structured-output runs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from codex.app_server.options import AppServerTurnOptions
|
|
8
|
+
from codex.output_schema import resolve_model_output_schema
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def with_model_output_schema(
|
|
12
|
+
options: AppServerTurnOptions | None,
|
|
13
|
+
model_type: type[BaseModel],
|
|
14
|
+
*,
|
|
15
|
+
owner: str,
|
|
16
|
+
option_model: type[AppServerTurnOptions] = AppServerTurnOptions,
|
|
17
|
+
) -> AppServerTurnOptions:
|
|
18
|
+
if options is None:
|
|
19
|
+
return option_model(output_schema=model_type)
|
|
20
|
+
return options.model_copy(
|
|
21
|
+
update={
|
|
22
|
+
"output_schema": resolve_model_output_schema(
|
|
23
|
+
options.output_schema,
|
|
24
|
+
model_type,
|
|
25
|
+
owner=owner,
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
)
|