codex-backend-sdk 0.1.1__tar.gz → 0.3.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.
Files changed (46) hide show
  1. {codex_backend_sdk-0.1.1 → codex_backend_sdk-0.3.0}/.gitignore +1 -0
  2. codex_backend_sdk-0.3.0/PKG-INFO +420 -0
  3. codex_backend_sdk-0.3.0/README.md +394 -0
  4. {codex_backend_sdk-0.1.1 → codex_backend_sdk-0.3.0}/codex_backend_sdk/__init__.py +35 -31
  5. codex_backend_sdk-0.3.0/codex_backend_sdk/_client.py +244 -0
  6. codex_backend_sdk-0.3.0/codex_backend_sdk/_models.py +225 -0
  7. codex_backend_sdk-0.3.0/codex_backend_sdk/_streaming.py +50 -0
  8. codex_backend_sdk-0.3.0/codex_backend_sdk/_utils.py +75 -0
  9. codex_backend_sdk-0.3.0/codex_backend_sdk/codex_client.py +54 -0
  10. codex_backend_sdk-0.3.0/codex_backend_sdk/resources/__init__.py +1 -0
  11. codex_backend_sdk-0.3.0/codex_backend_sdk/resources/codex.py +104 -0
  12. codex_backend_sdk-0.3.0/codex_backend_sdk/resources/models.py +71 -0
  13. codex_backend_sdk-0.3.0/codex_backend_sdk/resources/openai_oauth.py +126 -0
  14. codex_backend_sdk-0.3.0/codex_backend_sdk/resources/realtime.py +69 -0
  15. codex_backend_sdk-0.3.0/codex_backend_sdk/resources/responses.py +361 -0
  16. {codex_backend_sdk-0.1.1 → codex_backend_sdk-0.3.0}/docs/backend-api.md +161 -15
  17. codex_backend_sdk-0.3.0/examples/agent.py +54 -0
  18. {codex_backend_sdk-0.1.1 → codex_backend_sdk-0.3.0}/pyproject.toml +3 -3
  19. codex_backend_sdk-0.3.0/tests/test_basic.py +35 -0
  20. codex_backend_sdk-0.3.0/tests/test_codex_resources.py +112 -0
  21. codex_backend_sdk-0.3.0/tests/test_conversation.py +33 -0
  22. codex_backend_sdk-0.3.0/tests/test_openai_oauth_resources.py +88 -0
  23. codex_backend_sdk-0.3.0/tests/test_realtime_resource.py +78 -0
  24. codex_backend_sdk-0.3.0/tests/test_reasoning.py +43 -0
  25. codex_backend_sdk-0.3.0/tests/test_responses_resource.py +141 -0
  26. codex_backend_sdk-0.3.0/tests/test_structured_output.py +57 -0
  27. codex_backend_sdk-0.3.0/tests/test_tools.py +66 -0
  28. codex_backend_sdk-0.3.0/tools/probe_v1_endpoints.py +269 -0
  29. codex_backend_sdk-0.1.1/CHANGELOG.md +0 -7
  30. codex_backend_sdk-0.1.1/PKG-INFO +0 -369
  31. codex_backend_sdk-0.1.1/README.md +0 -341
  32. codex_backend_sdk-0.1.1/codex_backend_sdk/codex_client.py +0 -1084
  33. codex_backend_sdk-0.1.1/examples/agent.py +0 -356
  34. codex_backend_sdk-0.1.1/tests/test_abort.py +0 -69
  35. codex_backend_sdk-0.1.1/tests/test_account_info.py +0 -63
  36. codex_backend_sdk-0.1.1/tests/test_basic.py +0 -65
  37. codex_backend_sdk-0.1.1/tests/test_conversation.py +0 -73
  38. codex_backend_sdk-0.1.1/tests/test_realtime.py +0 -81
  39. codex_backend_sdk-0.1.1/tests/test_reasoning.py +0 -80
  40. codex_backend_sdk-0.1.1/tests/test_structured_output.py +0 -68
  41. codex_backend_sdk-0.1.1/tests/test_tools.py +0 -117
  42. {codex_backend_sdk-0.1.1 → codex_backend_sdk-0.3.0}/LICENSE +0 -0
  43. {codex_backend_sdk-0.1.1 → codex_backend_sdk-0.3.0}/codex_backend_sdk/oauth.py +0 -0
  44. {codex_backend_sdk-0.1.1 → codex_backend_sdk-0.3.0}/codex_backend_sdk/pkce.py +0 -0
  45. {codex_backend_sdk-0.1.1 → codex_backend_sdk-0.3.0}/codex_backend_sdk/storage.py +0 -0
  46. {codex_backend_sdk-0.1.1 → codex_backend_sdk-0.3.0}/tests/conftest.py +0 -0
@@ -10,3 +10,4 @@ build/
10
10
  venv/
11
11
  .pytest_cache/
12
12
  .claude/
13
+ .vscode/
@@ -0,0 +1,420 @@
1
+ Metadata-Version: 2.4
2
+ Name: codex-backend-sdk
3
+ Version: 0.3.0
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: pydantic>=2.0
19
+ Requires-Dist: requests>=2.28
20
+ Provides-Extra: dev
21
+ Requires-Dist: openai>=1.0; extra == 'dev'
22
+ Requires-Dist: pytest>=7.0; extra == 'dev'
23
+ Provides-Extra: openai
24
+ Requires-Dist: openai>=1.0; extra == 'openai'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # codex-backend-sdk
28
+
29
+ Unofficial Python SDK for the ChatGPT Codex backend API
30
+ (`chatgpt.com/backend-api/codex`).
31
+
32
+ This package mirrors the official OpenAI Python SDK shape for the API surface
33
+ that the Codex backend exposes. Use `OpenAI`, `client.responses.create(...)`,
34
+ and `client.models.list()` just as you would with `openai-python`, with
35
+ Codex-specific authentication and backend limitations under the hood.
36
+
37
+ > **Requirements:** a ChatGPT Plus, Pro, or Enterprise subscription.
38
+ > Authentication goes through ChatGPT OAuth and stores tokens in
39
+ > `~/.codex/auth.json`.
40
+
41
+ > **Disclaimer:** This is an independent, community-maintained library that
42
+ > reverse-engineers undocumented endpoints of `chatgpt.com`. It is not
43
+ > affiliated with, endorsed by, or supported by OpenAI.
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ git clone https://github.com/B4PT0R/codex-backend-sdk.git
49
+ cd codex-backend-sdk
50
+ pip install -e .
51
+ ```
52
+
53
+ ## Basic Usage
54
+
55
+ ```python
56
+ from codex_backend_sdk import OpenAI
57
+
58
+ client = OpenAI().authenticate()
59
+
60
+ response = client.responses.create(
61
+ model="gpt-5.4",
62
+ input="Explain quicksort in one paragraph.",
63
+ )
64
+
65
+ print(response.output_text)
66
+ ```
67
+
68
+ ## Streaming
69
+
70
+ ```python
71
+ stream = client.responses.create(
72
+ model="gpt-5.4",
73
+ input="Say 'hi' five times.",
74
+ stream=True,
75
+ )
76
+
77
+ for event in stream:
78
+ if event.type in {"response.output_text.delta", "response.content_part.delta"}:
79
+ delta = event.delta
80
+ print(delta if isinstance(delta, str) else delta.get("text", ""), end="")
81
+ ```
82
+
83
+ ## Models
84
+
85
+ ```python
86
+ models = client.models.list()
87
+ for model in models:
88
+ print(model.id, model.display_name, model.context_window)
89
+
90
+ info = client.models.retrieve("gpt-5.4")
91
+ ```
92
+
93
+ ## Multi-Turn Input
94
+
95
+ The Codex backend does not expose `previous_response_id`, so pass prior
96
+ input/output items explicitly.
97
+
98
+ ```python
99
+ history = [
100
+ {"role": "user", "content": "My name is Alice. Say OK."},
101
+ ]
102
+
103
+ reply1 = client.responses.create(input=history).output_text
104
+ history.append({"role": "assistant", "content": reply1})
105
+ history.append({"role": "user", "content": "What is my name?"})
106
+
107
+ reply2 = client.responses.create(input=history).output_text
108
+ print(reply2)
109
+ ```
110
+
111
+ ## Function Calling
112
+
113
+ ```python
114
+ import json
115
+
116
+ tools = [{
117
+ "type": "function",
118
+ "name": "get_weather",
119
+ "description": "Get the current weather for a city.",
120
+ "parameters": {
121
+ "type": "object",
122
+ "properties": {"city": {"type": "string"}},
123
+ "required": ["city"],
124
+ "additionalProperties": False,
125
+ },
126
+ }]
127
+
128
+ first = client.responses.create(
129
+ input="What's the weather in Paris?",
130
+ tools=tools,
131
+ )
132
+
133
+ call = next(item for item in first.output if item["type"] == "function_call")
134
+ result = {"temperature": 18, "unit": "celsius", "condition": "cloudy"}
135
+
136
+ second = client.responses.create(
137
+ input=[
138
+ call,
139
+ {
140
+ "type": "function_call_output",
141
+ "call_id": call["call_id"],
142
+ "output": json.dumps(result),
143
+ },
144
+ ],
145
+ tools=tools,
146
+ )
147
+
148
+ print(second.output_text)
149
+ ```
150
+
151
+ ## Structured Output
152
+
153
+ ```python
154
+ schema = {
155
+ "title": "person",
156
+ "type": "object",
157
+ "properties": {
158
+ "name": {"type": "string"},
159
+ "age": {"type": "integer"},
160
+ },
161
+ "required": ["name", "age"],
162
+ "additionalProperties": False,
163
+ }
164
+
165
+ response = client.responses.create(
166
+ input="Extract: Bob is 42 years old.",
167
+ text={
168
+ "format": {
169
+ "type": "json_schema",
170
+ "name": "person",
171
+ "schema": schema,
172
+ "strict": True,
173
+ }
174
+ },
175
+ )
176
+ ```
177
+
178
+ ## Supported Backend Endpoints
179
+
180
+ The SDK exposes the supported backend endpoints through either OpenAI-shaped
181
+ resources (`responses`, `models`, `realtime`) or Codex-only resources (`codex`).
182
+
183
+ | Backend endpoint | SDK method | Notes |
184
+ |---|---|---|
185
+ | `POST /backend-api/codex/responses` | `client.responses.create(...)` | Stream-only backend; non-streaming SDK calls are collected from SSE events. |
186
+ | `POST /backend-api/codex/responses/compact` | `client.responses.compact(...)` | Codex-specific helper for encrypted context compaction. |
187
+ | `GET /backend-api/codex/models` | `client.models.list()` / `client.models.retrieve(...)` | OpenAI-shaped model objects with Codex metadata preserved as extra fields. |
188
+ | `POST /backend-api/codex/realtime/calls` | `client.realtime.calls.create(...)` | OpenAI-shaped SDP call creation for realtime sessions. |
189
+ | `wss://api.openai.com/v1/realtime?model=...` | `client.realtime_websocket_url(...)` / `client.realtime_websocket_headers(...)` | Helper surface used by codex-agent's realtime plugin. |
190
+ | `POST /v1/embeddings` | `client.embeddings.create(...)` | Uses the Codex OAuth access token against `api.openai.com`; verified with `text-embedding-3-small`. |
191
+ | `POST /v1/audio/transcriptions` | `client.audio.transcriptions.create(...)` | Uses the Codex OAuth access token against `api.openai.com`; verified with `gpt-4o-mini-transcribe`. |
192
+ | `GET /backend-api/wham/usage` | `client.codex.usage()` | Codex/ChatGPT quota and rate-limit status. |
193
+ | `GET /backend-api/wham/tasks/list` | `client.codex.tasks.list(...)` | Raw Codex cloud task listing. |
194
+ | `GET /backend-api/wham/tasks/{task_id}` | `client.codex.tasks.retrieve(task_id)` | Raw Codex cloud task detail. |
195
+ | `GET /backend-api/wham/tasks/{task_id}/turns` | `client.codex.tasks.turns.list(task_id)` | Raw task turn mapping. |
196
+ | `GET /backend-api/wham/tasks/{task_id}/turns/{turn_id}/sibling_turns` | `client.codex.tasks.turns.sibling_turns(task_id, turn_id)` | Raw sibling turn list. |
197
+ | `GET /backend-api/wham/environments` | `client.codex.environments.list()` | Raw Codex cloud environment list. |
198
+ | `GET /backend-api/memories` | `client.codex.memories.list()` | Raw ChatGPT memory payload for the authenticated account. |
199
+ | `GET /backend-api/user_system_messages` | `client.codex.user_system_messages.retrieve()` | Raw ChatGPT customization/system-message payload. |
200
+
201
+ ### Responses
202
+
203
+ `client.responses.create(...)` follows the official OpenAI Responses API where
204
+ the Codex backend overlaps with it.
205
+
206
+ Supported request fields:
207
+
208
+ - `model`
209
+ - `input`
210
+ - `instructions`
211
+ - `include`
212
+ - `parallel_tool_calls`
213
+ - `prompt_cache_key`
214
+ - `reasoning`
215
+ - `service_tier`
216
+ - `store=False`
217
+ - `stream`
218
+ - `text`
219
+ - `tool_choice`
220
+ - `tools`
221
+
222
+ The backend itself requires streaming. When `stream=True`, the SDK yields
223
+ `ResponseStreamEvent` objects directly. When `stream` is omitted or false, the
224
+ SDK consumes the SSE stream and returns a collected `Response`.
225
+
226
+ ```python
227
+ response = client.responses.create(
228
+ model="gpt-5.4",
229
+ instructions="Be concise.",
230
+ input=[
231
+ {"role": "user", "content": "Summarize this API shape."},
232
+ ],
233
+ reasoning={"effort": "medium", "summary": "auto"},
234
+ include=["reasoning.encrypted_content"],
235
+ text={"verbosity": "medium"},
236
+ prompt_cache_key="session-123",
237
+ )
238
+ ```
239
+
240
+ Unsupported official Responses parameters are rejected explicitly with
241
+ `CodexBackendUnsupportedParameterError`, including `temperature`, `top_p`,
242
+ `max_output_tokens`, `metadata`, `user`, `safety_identifier`, `truncation`,
243
+ `previous_response_id`, `conversation`, `background`, `prompt`,
244
+ `prompt_cache_retention`, and `stream_options`.
245
+
246
+ ### Context Compaction
247
+
248
+ `client.responses.compact(...)` is specific to the Codex backend. It compresses
249
+ a long Responses-style input list into an opaque encrypted compaction summary
250
+ that can be replayed in later `input` arrays.
251
+
252
+ ```python
253
+ compacted = client.responses.compact(
254
+ model="gpt-5.4",
255
+ instructions="Keep task-critical context.",
256
+ input=history,
257
+ )
258
+
259
+ history = compacted.output
260
+ ```
261
+
262
+ The returned `CompactedResponse.output` contains regular response items plus
263
+ one or more `{"type": "compaction_summary", ...}` items. Treat those summaries
264
+ as opaque backend state.
265
+
266
+ ### Models
267
+
268
+ `client.models.list()` and `client.models.retrieve(model)` mirror the official
269
+ OpenAI models resource, while preserving Codex-specific metadata as extra
270
+ Pydantic fields.
271
+
272
+ ```python
273
+ models = client.models.list()
274
+ for model in models:
275
+ print(
276
+ model.id,
277
+ model.context_window,
278
+ model.supported_in_api,
279
+ model.supports_reasoning_summaries,
280
+ )
281
+ ```
282
+
283
+ Common extra fields include:
284
+
285
+ - `display_name`
286
+ - `description`
287
+ - `context_window`
288
+ - `supported_in_api`
289
+ - `supports_reasoning_summaries`
290
+ - `support_verbosity`
291
+ - `default_verbosity`
292
+ - `default_reasoning_level`
293
+ - `supported_reasoning_levels`
294
+ - `auto_compact_token_limit`
295
+ - `prefer_websockets`
296
+ - `input_modalities`
297
+ - `available_in_plans`
298
+ - `base_instructions`
299
+ - `priority`
300
+ - `raw`
301
+
302
+ ### Realtime
303
+
304
+ The SDK keeps the realtime surface available for integrations that bridge Codex
305
+ auth with voice sessions.
306
+
307
+ `client.realtime.calls.create(...)` mirrors the official OpenAI SDK call shape:
308
+
309
+ ```python
310
+ answer = client.realtime.calls.create(
311
+ sdp=offer_sdp,
312
+ session={"type": "realtime", "model": "gpt-realtime-1.5"},
313
+ )
314
+
315
+ print(answer.text)
316
+ ```
317
+
318
+ For WebSocket-based plugins such as `codex-agent`, the client also exposes small
319
+ helpers that reuse the OpenAI API key stored by the Codex OAuth flow:
320
+
321
+ ```python
322
+ url = client.realtime_websocket_url(model="gpt-realtime-1.5")
323
+ headers = client.realtime_websocket_headers(session_id="voice-session")
324
+ ```
325
+
326
+ `realtime_websocket_headers(...)` requires `~/.codex/auth.json` to contain
327
+ `openai_api_key`. The default `authenticate(request_api_key=True)` flow stores
328
+ that key when available.
329
+
330
+ ### Embeddings
331
+
332
+ `client.embeddings.create(...)` mirrors the official OpenAI embeddings resource
333
+ and sends the Codex OAuth access token directly to `api.openai.com/v1`.
334
+
335
+ ```python
336
+ embedding = client.embeddings.create(
337
+ model="text-embedding-3-small",
338
+ input="Embed this sentence.",
339
+ dimensions=256,
340
+ )
341
+
342
+ print(embedding.data[0].embedding)
343
+ ```
344
+
345
+ ### Audio Transcriptions
346
+
347
+ `client.audio.transcriptions.create(...)` mirrors the official OpenAI
348
+ transcriptions resource for non-streaming calls.
349
+
350
+ ```python
351
+ with open("meeting.wav", "rb") as audio:
352
+ transcription = client.audio.transcriptions.create(
353
+ model="gpt-4o-mini-transcribe",
354
+ file=("meeting.wav", audio, "audio/wav"),
355
+ response_format="json",
356
+ )
357
+
358
+ print(transcription.text)
359
+ ```
360
+
361
+ ### Quota And Usage
362
+
363
+ `client.codex.usage()` calls the ChatGPT WHAM usage endpoint. It returns the raw
364
+ quota payload from the backend because the shape contains plan-specific fields.
365
+
366
+ ```python
367
+ quota = client.codex.usage()
368
+ primary = quota.get("rate_limit", {}).get("primary_window", {})
369
+ print(primary.get("used_percent"))
370
+ ```
371
+
372
+ Typical fields include:
373
+
374
+ - `plan_type`
375
+ - `rate_limit.allowed`
376
+ - `rate_limit.limit_reached`
377
+ - `rate_limit.primary_window`
378
+ - `rate_limit.secondary_window`
379
+ - `additional_rate_limits`
380
+ - `credits`
381
+ - `rate_limit_reached_type`
382
+
383
+ ### Codex Cloud Tasks
384
+
385
+ The `client.codex.tasks` and `client.codex.environments` namespaces expose
386
+ read-only WHAM cloud-task payloads as raw backend dictionaries.
387
+
388
+ ```python
389
+ tasks = client.codex.tasks.list(limit=10)
390
+ task = client.codex.tasks.retrieve(tasks["items"][0]["id"])
391
+ turns = client.codex.tasks.turns.list(task["task"]["id"])
392
+ environments = client.codex.environments.list()
393
+ ```
394
+
395
+ Supported task-list filters are `limit`, `cursor`, `task_filter`, and
396
+ `environment_id`.
397
+
398
+ ### ChatGPT Account Data
399
+
400
+ The `client.codex` namespace also exposes read-only ChatGPT account data that is
401
+ not part of the official OpenAI SDK.
402
+
403
+ ```python
404
+ memories = client.codex.memories.list()
405
+ customization = client.codex.user_system_messages.retrieve()
406
+ ```
407
+
408
+ Both methods return raw backend dictionaries because these payloads can contain
409
+ personal account-specific fields and may change without notice.
410
+
411
+ ### Observed But Not Exposed
412
+
413
+ The reverse-engineering notes in `docs/backend-api.md` include additional
414
+ observed endpoints. They are not exposed as SDK resources yet because they are
415
+ plan-gated, unavailable on `chatgpt.com`, or not stable enough:
416
+
417
+ - `POST /backend-api/codex/memories/trace_summarize`
418
+ - `GET /backend-api/wham/config/requirements`
419
+ - `POST /v1/audio/speech` (auth reaches the endpoint, but Pro OAuth lacks
420
+ `api.model.audio.request` in current tests)