codex-backend-sdk 0.1.1__py3-none-any.whl
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_backend_sdk/__init__.py +72 -0
- codex_backend_sdk/codex_client.py +1084 -0
- codex_backend_sdk/oauth.py +299 -0
- codex_backend_sdk/pkce.py +29 -0
- codex_backend_sdk/storage.py +166 -0
- codex_backend_sdk-0.1.1.dist-info/METADATA +369 -0
- codex_backend_sdk-0.1.1.dist-info/RECORD +9 -0
- codex_backend_sdk-0.1.1.dist-info/WHEEL +4 -0
- codex_backend_sdk-0.1.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codex-backend-sdk
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Unofficial Python SDK for the ChatGPT Codex backend API
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: chatgpt,codex,llm,openai,sdk
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Requires-Python: >=3.9
|
|
18
|
+
Requires-Dist: requests>=2.28
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: openai>=1.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: pyyaml>=6.0; extra == 'dev'
|
|
23
|
+
Provides-Extra: examples
|
|
24
|
+
Requires-Dist: pyyaml>=6.0; extra == 'examples'
|
|
25
|
+
Provides-Extra: openai
|
|
26
|
+
Requires-Dist: openai>=1.0; extra == 'openai'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# codex-backend-sdk
|
|
30
|
+
|
|
31
|
+
Unofficial Python SDK for the ChatGPT Codex backend API (`chatgpt.com/backend-api/codex`).
|
|
32
|
+
|
|
33
|
+
This is a **lower-level** alternative to the official Codex CLI/SDK. It gives direct access to the underlying HTTP API endpoints on which the CLI relies, so you can build your own agent loop from scratch without inheriting OpenAI's design choices.
|
|
34
|
+
|
|
35
|
+
> **Requirements:** a ChatGPT Plus, Pro, or Enterprise subscription. No OpenAI API key and no Codex CLI installation needed — authentication goes through ChatGPT OAuth directly from Python.
|
|
36
|
+
|
|
37
|
+
> [!WARNING]
|
|
38
|
+
> **Disclaimer:** This is an independent, community-maintained library that reverse-engineers undocumented endpoints of `chatgpt.com`. It is **not** affiliated with, endorsed by, or supported by OpenAI. Usage remains subject to [OpenAI's Terms of Use](https://openai.com/policies/terms-of-use). Endpoints may change or break without notice.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
git clone https://github.com/B4PT0R/codex-backend-sdk.git
|
|
46
|
+
cd codex-backend-sdk
|
|
47
|
+
pip install -e .
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
For the agent example (`examples/agent.py`), also install:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install pyyaml
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Authentication
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from codex_backend_sdk import CodexClient
|
|
62
|
+
|
|
63
|
+
client = CodexClient().authenticate()
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`authenticate()` handles everything automatically:
|
|
67
|
+
- **Tokens present and fresh** → used directly, no network call
|
|
68
|
+
- **Tokens stale** → silently refreshed in the background
|
|
69
|
+
- **No valid tokens available** → opens your browser for the OAuth flow (blocking, first run only)
|
|
70
|
+
|
|
71
|
+
Tokens are saved to `~/.codex/auth.json` (created if it doesn't exist). If the official Codex CLI is also installed, both share the same file.
|
|
72
|
+
|
|
73
|
+
All other methods (`stream()`, `respond()`, `list_models()`, …) raise immediately if `authenticate()` was not called — they never trigger the OAuth flow implicitly.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Basic usage
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from codex_backend_sdk import CodexClient, TextDelta, ResponseCompleted
|
|
81
|
+
|
|
82
|
+
client = CodexClient().authenticate()
|
|
83
|
+
|
|
84
|
+
for event in client.stream("Explain quicksort in one paragraph"):
|
|
85
|
+
if isinstance(event, TextDelta):
|
|
86
|
+
print(event.text, end="", flush=True)
|
|
87
|
+
elif isinstance(event, ResponseCompleted):
|
|
88
|
+
print(f"\n[tokens: in={event.input_tokens} out={event.output_tokens}]")
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Or collect the full response at once:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
text, completion = client.respond("Explain quicksort in one paragraph")
|
|
95
|
+
print(text)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Aborting a stream
|
|
101
|
+
|
|
102
|
+
`CodexClient.abort()` stops the currently active streaming response. The stream
|
|
103
|
+
iterator raises `ResponseAborted`, while captured output emitted before the abort
|
|
104
|
+
remains available to your caller.
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from codex_backend_sdk import CodexClient, ResponseAborted, TextDelta
|
|
108
|
+
|
|
109
|
+
client = CodexClient().authenticate()
|
|
110
|
+
events = client.stream("Write a long essay")
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
for event in events:
|
|
114
|
+
if isinstance(event, TextDelta):
|
|
115
|
+
print(event.text, end="", flush=True)
|
|
116
|
+
if should_stop():
|
|
117
|
+
client.abort()
|
|
118
|
+
except ResponseAborted:
|
|
119
|
+
pass
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Calling `abort()` when no stream is active is a no-op.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Models
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
models = client.list_models()
|
|
130
|
+
for m in models:
|
|
131
|
+
print(m.slug, m.display_name, m.context_window)
|
|
132
|
+
|
|
133
|
+
info = client.get_model("codex-mini-latest")
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Multi-turn conversation
|
|
139
|
+
|
|
140
|
+
Pass prior turns as `conversation_history`. Each turn is a raw dict in Responses API format.
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
history = []
|
|
144
|
+
|
|
145
|
+
def chat(user_input: str) -> str:
|
|
146
|
+
text, _ = client.respond(user_input, conversation_history=history)
|
|
147
|
+
history.append({
|
|
148
|
+
"type": "message", "role": "user",
|
|
149
|
+
"content": [{"type": "input_text", "text": user_input}],
|
|
150
|
+
})
|
|
151
|
+
history.append({
|
|
152
|
+
"type": "message", "role": "assistant",
|
|
153
|
+
"content": [{"type": "output_text", "text": text}],
|
|
154
|
+
})
|
|
155
|
+
return text
|
|
156
|
+
|
|
157
|
+
print(chat("My name is Alice."))
|
|
158
|
+
print(chat("What's my name?")) # model remembers
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Tool use
|
|
164
|
+
|
|
165
|
+
Tool definitions follow the same format as the official OpenAI SDK.
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
import json
|
|
169
|
+
from codex_backend_sdk import CodexClient, TextDelta, ToolCall, ResponseCompleted
|
|
170
|
+
|
|
171
|
+
client = CodexClient().authenticate()
|
|
172
|
+
|
|
173
|
+
tools = [
|
|
174
|
+
{
|
|
175
|
+
"type": "function",
|
|
176
|
+
"name": "get_weather",
|
|
177
|
+
"description": "Get the current temperature for a city.",
|
|
178
|
+
"parameters": {
|
|
179
|
+
"type": "object",
|
|
180
|
+
"properties": {
|
|
181
|
+
"city": {"type": "string", "description": "City name"},
|
|
182
|
+
},
|
|
183
|
+
"required": ["city"],
|
|
184
|
+
},
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
|
|
188
|
+
def get_weather(city: str) -> dict:
|
|
189
|
+
return {"city": city, "temperature": 22, "unit": "celsius"}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
history = []
|
|
193
|
+
|
|
194
|
+
# Turn 1 — model may emit a ToolCall
|
|
195
|
+
for event in client.stream("What's the weather in Paris?", tools=tools):
|
|
196
|
+
if isinstance(event, TextDelta):
|
|
197
|
+
print(event.text, end="", flush=True)
|
|
198
|
+
elif isinstance(event, ToolCall):
|
|
199
|
+
result = get_weather(**event.parsed_arguments())
|
|
200
|
+
history.append(event.as_history_item()) # the function_call item
|
|
201
|
+
history.append(event.to_tool_result(json.dumps(result))) # function_call_output
|
|
202
|
+
|
|
203
|
+
# Turn 2 — model sees the tool result and replies
|
|
204
|
+
for event in client.stream(None, conversation_history=history, tools=tools):
|
|
205
|
+
if isinstance(event, TextDelta):
|
|
206
|
+
print(event.text, end="", flush=True)
|
|
207
|
+
elif isinstance(event, ResponseCompleted):
|
|
208
|
+
print()
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
`tool_choice` defaults to `"auto"`. Other values: `"none"`, `"required"`, or `{"type": "function", "name": "..."}`.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Image input
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from codex_backend_sdk import image_url, image_b64
|
|
219
|
+
|
|
220
|
+
# From a URL
|
|
221
|
+
for event in client.stream(
|
|
222
|
+
["What's in this image?", image_url("https://example.com/photo.jpg")]
|
|
223
|
+
):
|
|
224
|
+
...
|
|
225
|
+
|
|
226
|
+
# From a local file (base64)
|
|
227
|
+
import base64
|
|
228
|
+
with open("photo.jpg", "rb") as f:
|
|
229
|
+
data = base64.b64encode(f.read()).decode()
|
|
230
|
+
|
|
231
|
+
for event in client.stream(
|
|
232
|
+
["Describe this image.", image_b64(data, "image/jpeg")]
|
|
233
|
+
):
|
|
234
|
+
...
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Reasoning
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
# Enable chain-of-thought (reasoning tokens are billed separately)
|
|
243
|
+
for event in client.stream(
|
|
244
|
+
"Solve: if 3x + 7 = 22, what is x?",
|
|
245
|
+
reasoning="medium",
|
|
246
|
+
reasoning_summary="concise", # "concise" | "detailed" | "auto"
|
|
247
|
+
):
|
|
248
|
+
...
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`reasoning` values: `"minimal"`, `"low"`, `"medium"`, `"high"`, `"xhigh"`.
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Structured output (JSON Schema)
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
import json
|
|
259
|
+
|
|
260
|
+
schema = {
|
|
261
|
+
"title": "person",
|
|
262
|
+
"type": "object",
|
|
263
|
+
"properties": {
|
|
264
|
+
"name": {"type": "string"},
|
|
265
|
+
"age": {"type": "integer"},
|
|
266
|
+
},
|
|
267
|
+
"required": ["name", "age"],
|
|
268
|
+
"additionalProperties": False,
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
text, _ = client.respond(
|
|
272
|
+
"Extract: Alice is 30 years old.",
|
|
273
|
+
output_schema=schema,
|
|
274
|
+
)
|
|
275
|
+
person = json.loads(text)
|
|
276
|
+
print(person) # {"name": "Alice", "age": 30}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Context compaction
|
|
282
|
+
|
|
283
|
+
When a conversation grows long, compact it into an encrypted summary the model can still read:
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
result = client.compact(history)
|
|
287
|
+
|
|
288
|
+
# result.output_items replaces the full history
|
|
289
|
+
for event in client.stream("Continue…", conversation_history=result.output_items):
|
|
290
|
+
...
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Usage / quota
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
quota = client.usage()
|
|
299
|
+
print(quota) # raw dict from /backend-api/wham/usage
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Stream events reference
|
|
305
|
+
|
|
306
|
+
| Type | Emitted when |
|
|
307
|
+
|---|---|
|
|
308
|
+
| `TextDelta` | incremental text chunk arrives |
|
|
309
|
+
| `ReasoningDelta` | reasoning summary chunk (requires `include_reasoning=True`) |
|
|
310
|
+
| `ToolCall` | model requests a function call |
|
|
311
|
+
| `OutputItem` | a non-tool output item completes (message, compaction_summary, …) |
|
|
312
|
+
| `ResponseCompleted` | stream ends successfully; carries full `TokenUsage` |
|
|
313
|
+
| `ResponseFailed` | stream ends with an error (`code`, `message`) |
|
|
314
|
+
| `ResponseAborted` | exception raised when the caller aborts an active stream |
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## `stream()` parameters
|
|
319
|
+
|
|
320
|
+
| Parameter | Type | Default | Description |
|
|
321
|
+
|---|---|---|---|
|
|
322
|
+
| `user_message` | `str \| list[dict] \| None` | — | User prompt. `None` to continue without a new message (e.g. after tool results). |
|
|
323
|
+
| `model` | `str` | `"gpt-5.4"` | Model slug. |
|
|
324
|
+
| `instructions` | `str` | `""` | System instructions. |
|
|
325
|
+
| `conversation_history` | `list[dict]` | `None` | Prior turns (ResponseItem format). |
|
|
326
|
+
| `tools` | `list[dict]` | `None` | Tool definitions (OpenAI function format). |
|
|
327
|
+
| `tool_choice` | `str \| dict` | `"auto"` | `"auto"`, `"none"`, `"required"`, or `{"type":"function","name":"..."}`. |
|
|
328
|
+
| `parallel_tool_calls` | `bool` | `False` | Allow multiple tool calls in one turn. |
|
|
329
|
+
| `reasoning` | `str` | `None` | `"minimal"` / `"low"` / `"medium"` / `"high"` / `"xhigh"`. |
|
|
330
|
+
| `reasoning_summary` | `str` | `None` | `"concise"` / `"detailed"` / `"auto"`. |
|
|
331
|
+
| `verbosity` | `str` | `None` | `"low"` / `"medium"` / `"high"`. Mutually exclusive with `output_schema`. |
|
|
332
|
+
| `output_schema` | `dict` | `None` | JSON Schema for structured output. |
|
|
333
|
+
| `include_reasoning` | `bool` | `False` | Emit `ReasoningDelta` events. |
|
|
334
|
+
| `web_search` | `str` | `None` | `"cached"` (OpenAI index), `"live"` (real-time fetch), or `"disabled"` / `None` (off). Incompatible with `reasoning="minimal"`. |
|
|
335
|
+
| `store` | `bool` | `False` | Persist the response server-side. |
|
|
336
|
+
| `service_tier` | `str` | `None` | `"flex"` (higher throughput) or `"fast"` (lower latency). |
|
|
337
|
+
| `prompt_cache_key` | `str` | `None` | UUID to share across calls with a common prefix — hits the server-side prompt cache. |
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## How it works
|
|
342
|
+
|
|
343
|
+
Authentication uses the same ChatGPT OAuth 2.0 + PKCE flow as the official Codex CLI (`codex-rs`). Tokens are stored in `~/.codex/auth.json` and can be refreshed without re-opening the browser.
|
|
344
|
+
|
|
345
|
+
The backend (`chatgpt.com/backend-api/codex`) is distinct from `api.openai.com` and is accessed via your ChatGPT subscription rather than an API key. All responses are streamed over SSE — the backend does not support non-streaming requests.
|
|
346
|
+
|
|
347
|
+
### Realtime WebRTC
|
|
348
|
+
|
|
349
|
+
`/backend-api/codex/realtime/calls` currently returns `404`, but the WebRTC call
|
|
350
|
+
creation route used by Codex works at `https://api.openai.com/v1/realtime/calls`
|
|
351
|
+
with the saved ChatGPT OAuth bearer token and `ChatGPT-Account-ID`.
|
|
352
|
+
|
|
353
|
+
Generate an SDP offer in a browser or WebView with an audio transceiver and the
|
|
354
|
+
`oai-events` data channel, then create the call:
|
|
355
|
+
|
|
356
|
+
```python
|
|
357
|
+
result = client.create_realtime_call(
|
|
358
|
+
offer_sdp,
|
|
359
|
+
instructions="You are concise.",
|
|
360
|
+
session_id="thread-or-session-id",
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
print(result.answer_sdp)
|
|
364
|
+
print(result.call_id)
|
|
365
|
+
print(result.sideband_url)
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
This path intentionally does not read `OPENAI_API_KEY`; it uses the ChatGPT auth
|
|
369
|
+
tokens loaded from `~/.codex/auth.json`.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
codex_backend_sdk/__init__.py,sha256=Sb8zCkdATd2tkEHaL51ttrzqW1iyZBM97RPFlas-Bbo,1727
|
|
2
|
+
codex_backend_sdk/codex_client.py,sha256=JkOuIRmlVhqnmDb3_Pa05IKC73rY-cMpxbgRX5ql8bQ,40808
|
|
3
|
+
codex_backend_sdk/oauth.py,sha256=5BP0L-W8mSlgt2RYN8U5viQjb1DcMlAxG5rY0EB4a5w,10416
|
|
4
|
+
codex_backend_sdk/pkce.py,sha256=-sl27_G0XkPiiC1UyUboc6ZIHeIAHJQx0uga7DO4Wro,825
|
|
5
|
+
codex_backend_sdk/storage.py,sha256=fND-TLcbwH0FLicN6Ebde8_lhfByBzag-l3cBg5iA_E,4968
|
|
6
|
+
codex_backend_sdk-0.1.1.dist-info/METADATA,sha256=aJrR-beCUo9c0W409AIm61YYYHhhr79--somCoUSIz0,11534
|
|
7
|
+
codex_backend_sdk-0.1.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
8
|
+
codex_backend_sdk-0.1.1.dist-info/licenses/LICENSE,sha256=hzeKvVWRrGcIwcw-tMbWx5lL8zrdPTcpe7VHHKgCp6M,1079
|
|
9
|
+
codex_backend_sdk-0.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 codex-sdk contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|