codex-backend-sdk 0.3.0__tar.gz → 0.3.2__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_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/PKG-INFO +43 -5
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/README.md +42 -4
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/codex_backend_sdk/__init__.py +11 -1
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/codex_backend_sdk/_client.py +103 -12
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/codex_backend_sdk/_models.py +29 -0
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/codex_backend_sdk/codex_client.py +10 -0
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/codex_backend_sdk/resources/codex.py +30 -0
- codex_backend_sdk-0.3.2/codex_backend_sdk/resources/files.py +116 -0
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/codex_backend_sdk/resources/models.py +19 -4
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/codex_backend_sdk/resources/realtime.py +5 -11
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/codex_backend_sdk/resources/responses.py +19 -2
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/docs/backend-api.md +57 -3
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/pyproject.toml +1 -1
- codex_backend_sdk-0.3.2/tests/test_client_retry.py +97 -0
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/tests/test_codex_resources.py +66 -1
- codex_backend_sdk-0.3.2/tests/test_files_resource.py +114 -0
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/tests/test_realtime_resource.py +6 -4
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/tests/test_responses_resource.py +89 -16
- codex_backend_sdk-0.3.0/tools/probe_v1_endpoints.py +0 -269
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/.gitignore +0 -0
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/LICENSE +0 -0
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/codex_backend_sdk/_streaming.py +0 -0
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/codex_backend_sdk/_utils.py +0 -0
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/codex_backend_sdk/oauth.py +0 -0
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/codex_backend_sdk/pkce.py +0 -0
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/codex_backend_sdk/resources/__init__.py +0 -0
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/codex_backend_sdk/resources/openai_oauth.py +0 -0
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/codex_backend_sdk/storage.py +0 -0
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/examples/agent.py +0 -0
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/tests/conftest.py +0 -0
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/tests/test_basic.py +0 -0
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/tests/test_conversation.py +0 -0
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/tests/test_openai_oauth_resources.py +0 -0
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/tests/test_reasoning.py +0 -0
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/tests/test_structured_output.py +0 -0
- {codex_backend_sdk-0.3.0 → codex_backend_sdk-0.3.2}/tests/test_tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-backend-sdk
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Unofficial Python SDK for the ChatGPT Codex backend API
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -184,17 +184,20 @@ resources (`responses`, `models`, `realtime`) or Codex-only resources (`codex`).
|
|
|
184
184
|
|---|---|---|
|
|
185
185
|
| `POST /backend-api/codex/responses` | `client.responses.create(...)` | Stream-only backend; non-streaming SDK calls are collected from SSE events. |
|
|
186
186
|
| `POST /backend-api/codex/responses/compact` | `client.responses.compact(...)` | Codex-specific helper for encrypted context compaction. |
|
|
187
|
+
| `POST /backend-api/codex/memories/trace_summarize` | `client.codex.memories.trace_summarize(...)` | Raw Codex memory trace summarization helper. |
|
|
187
188
|
| `GET /backend-api/codex/models` | `client.models.list()` / `client.models.retrieve(...)` | OpenAI-shaped model objects with Codex metadata preserved as extra fields. |
|
|
188
189
|
| `POST /backend-api/codex/realtime/calls` | `client.realtime.calls.create(...)` | OpenAI-shaped SDP call creation for realtime sessions. |
|
|
189
190
|
| `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
191
|
| `POST /v1/embeddings` | `client.embeddings.create(...)` | Uses the Codex OAuth access token against `api.openai.com`; verified with `text-embedding-3-small`. |
|
|
191
192
|
| `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
193
|
| `GET /backend-api/wham/usage` | `client.codex.usage()` | Codex/ChatGPT quota and rate-limit status. |
|
|
194
|
+
| `GET /backend-api/wham/config/requirements` | `client.codex.config.requirements()` | Raw managed requirements/config payload for the authenticated account. |
|
|
193
195
|
| `GET /backend-api/wham/tasks/list` | `client.codex.tasks.list(...)` | Raw Codex cloud task listing. |
|
|
194
196
|
| `GET /backend-api/wham/tasks/{task_id}` | `client.codex.tasks.retrieve(task_id)` | Raw Codex cloud task detail. |
|
|
195
197
|
| `GET /backend-api/wham/tasks/{task_id}/turns` | `client.codex.tasks.turns.list(task_id)` | Raw task turn mapping. |
|
|
196
198
|
| `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
199
|
| `GET /backend-api/wham/environments` | `client.codex.environments.list()` | Raw Codex cloud environment list. |
|
|
200
|
+
| `POST /backend-api/files` + signed upload | `client.files.upload(...)` | Uploads local files for Codex Apps/MCP file parameters and returns `sediment://...` metadata. |
|
|
198
201
|
| `GET /backend-api/memories` | `client.codex.memories.list()` | Raw ChatGPT memory payload for the authenticated account. |
|
|
199
202
|
| `GET /backend-api/user_system_messages` | `client.codex.user_system_messages.retrieve()` | Raw ChatGPT customization/system-message payload. |
|
|
200
203
|
|
|
@@ -267,10 +270,11 @@ as opaque backend state.
|
|
|
267
270
|
|
|
268
271
|
`client.models.list()` and `client.models.retrieve(model)` mirror the official
|
|
269
272
|
OpenAI models resource, while preserving Codex-specific metadata as extra
|
|
270
|
-
Pydantic fields.
|
|
273
|
+
Pydantic fields. The returned page also exposes the backend `ETag` when present.
|
|
271
274
|
|
|
272
275
|
```python
|
|
273
276
|
models = client.models.list()
|
|
277
|
+
print(models.etag)
|
|
274
278
|
for model in models:
|
|
275
279
|
print(
|
|
276
280
|
model.id,
|
|
@@ -403,18 +407,52 @@ not part of the official OpenAI SDK.
|
|
|
403
407
|
```python
|
|
404
408
|
memories = client.codex.memories.list()
|
|
405
409
|
customization = client.codex.user_system_messages.retrieve()
|
|
410
|
+
requirements = client.codex.config.requirements()
|
|
406
411
|
```
|
|
407
412
|
|
|
408
|
-
|
|
413
|
+
These methods return raw backend dictionaries because these payloads can contain
|
|
409
414
|
personal account-specific fields and may change without notice.
|
|
410
415
|
|
|
416
|
+
`client.codex.memories.trace_summarize(...)` exposes the Codex memory
|
|
417
|
+
summarization endpoint used by the official client. It accepts dictionaries or
|
|
418
|
+
`RawMemory` objects and returns a typed `MemorySummarizeResponse`:
|
|
419
|
+
|
|
420
|
+
```python
|
|
421
|
+
from codex_backend_sdk import RawMemory
|
|
422
|
+
|
|
423
|
+
summary = client.codex.memories.trace_summarize(
|
|
424
|
+
model="gpt-5.4",
|
|
425
|
+
traces=[
|
|
426
|
+
RawMemory(
|
|
427
|
+
id="trace_1",
|
|
428
|
+
metadata={"source_path": "memory.jsonl"},
|
|
429
|
+
items=[{"type": "message", "content": "Remember this"}],
|
|
430
|
+
)
|
|
431
|
+
],
|
|
432
|
+
reasoning={"effort": "low"},
|
|
433
|
+
)
|
|
434
|
+
print(summary.output[0].memory_summary)
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
Transient HTTP failures (`429`, `5xx`, timeouts, and connection errors) are
|
|
438
|
+
retried by default. Configure this with `OpenAI(max_retries=..., retry_base_delay=...)`.
|
|
439
|
+
|
|
440
|
+
### File Uploads
|
|
441
|
+
|
|
442
|
+
`client.files.upload(...)` follows the official Codex file flow for Apps/MCP
|
|
443
|
+
file parameters: create file metadata under ChatGPT, upload bytes to the signed
|
|
444
|
+
URL, then finalize the upload.
|
|
445
|
+
|
|
446
|
+
```python
|
|
447
|
+
uploaded = client.files.upload("report.csv")
|
|
448
|
+
print(uploaded.uri) # sediment://file_...
|
|
449
|
+
```
|
|
450
|
+
|
|
411
451
|
### Observed But Not Exposed
|
|
412
452
|
|
|
413
453
|
The reverse-engineering notes in `docs/backend-api.md` include additional
|
|
414
454
|
observed endpoints. They are not exposed as SDK resources yet because they are
|
|
415
455
|
plan-gated, unavailable on `chatgpt.com`, or not stable enough:
|
|
416
456
|
|
|
417
|
-
- `POST /backend-api/codex/memories/trace_summarize`
|
|
418
|
-
- `GET /backend-api/wham/config/requirements`
|
|
419
457
|
- `POST /v1/audio/speech` (auth reaches the endpoint, but Pro OAuth lacks
|
|
420
458
|
`api.model.audio.request` in current tests)
|
|
@@ -158,17 +158,20 @@ resources (`responses`, `models`, `realtime`) or Codex-only resources (`codex`).
|
|
|
158
158
|
|---|---|---|
|
|
159
159
|
| `POST /backend-api/codex/responses` | `client.responses.create(...)` | Stream-only backend; non-streaming SDK calls are collected from SSE events. |
|
|
160
160
|
| `POST /backend-api/codex/responses/compact` | `client.responses.compact(...)` | Codex-specific helper for encrypted context compaction. |
|
|
161
|
+
| `POST /backend-api/codex/memories/trace_summarize` | `client.codex.memories.trace_summarize(...)` | Raw Codex memory trace summarization helper. |
|
|
161
162
|
| `GET /backend-api/codex/models` | `client.models.list()` / `client.models.retrieve(...)` | OpenAI-shaped model objects with Codex metadata preserved as extra fields. |
|
|
162
163
|
| `POST /backend-api/codex/realtime/calls` | `client.realtime.calls.create(...)` | OpenAI-shaped SDP call creation for realtime sessions. |
|
|
163
164
|
| `wss://api.openai.com/v1/realtime?model=...` | `client.realtime_websocket_url(...)` / `client.realtime_websocket_headers(...)` | Helper surface used by codex-agent's realtime plugin. |
|
|
164
165
|
| `POST /v1/embeddings` | `client.embeddings.create(...)` | Uses the Codex OAuth access token against `api.openai.com`; verified with `text-embedding-3-small`. |
|
|
165
166
|
| `POST /v1/audio/transcriptions` | `client.audio.transcriptions.create(...)` | Uses the Codex OAuth access token against `api.openai.com`; verified with `gpt-4o-mini-transcribe`. |
|
|
166
167
|
| `GET /backend-api/wham/usage` | `client.codex.usage()` | Codex/ChatGPT quota and rate-limit status. |
|
|
168
|
+
| `GET /backend-api/wham/config/requirements` | `client.codex.config.requirements()` | Raw managed requirements/config payload for the authenticated account. |
|
|
167
169
|
| `GET /backend-api/wham/tasks/list` | `client.codex.tasks.list(...)` | Raw Codex cloud task listing. |
|
|
168
170
|
| `GET /backend-api/wham/tasks/{task_id}` | `client.codex.tasks.retrieve(task_id)` | Raw Codex cloud task detail. |
|
|
169
171
|
| `GET /backend-api/wham/tasks/{task_id}/turns` | `client.codex.tasks.turns.list(task_id)` | Raw task turn mapping. |
|
|
170
172
|
| `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. |
|
|
171
173
|
| `GET /backend-api/wham/environments` | `client.codex.environments.list()` | Raw Codex cloud environment list. |
|
|
174
|
+
| `POST /backend-api/files` + signed upload | `client.files.upload(...)` | Uploads local files for Codex Apps/MCP file parameters and returns `sediment://...` metadata. |
|
|
172
175
|
| `GET /backend-api/memories` | `client.codex.memories.list()` | Raw ChatGPT memory payload for the authenticated account. |
|
|
173
176
|
| `GET /backend-api/user_system_messages` | `client.codex.user_system_messages.retrieve()` | Raw ChatGPT customization/system-message payload. |
|
|
174
177
|
|
|
@@ -241,10 +244,11 @@ as opaque backend state.
|
|
|
241
244
|
|
|
242
245
|
`client.models.list()` and `client.models.retrieve(model)` mirror the official
|
|
243
246
|
OpenAI models resource, while preserving Codex-specific metadata as extra
|
|
244
|
-
Pydantic fields.
|
|
247
|
+
Pydantic fields. The returned page also exposes the backend `ETag` when present.
|
|
245
248
|
|
|
246
249
|
```python
|
|
247
250
|
models = client.models.list()
|
|
251
|
+
print(models.etag)
|
|
248
252
|
for model in models:
|
|
249
253
|
print(
|
|
250
254
|
model.id,
|
|
@@ -377,18 +381,52 @@ not part of the official OpenAI SDK.
|
|
|
377
381
|
```python
|
|
378
382
|
memories = client.codex.memories.list()
|
|
379
383
|
customization = client.codex.user_system_messages.retrieve()
|
|
384
|
+
requirements = client.codex.config.requirements()
|
|
380
385
|
```
|
|
381
386
|
|
|
382
|
-
|
|
387
|
+
These methods return raw backend dictionaries because these payloads can contain
|
|
383
388
|
personal account-specific fields and may change without notice.
|
|
384
389
|
|
|
390
|
+
`client.codex.memories.trace_summarize(...)` exposes the Codex memory
|
|
391
|
+
summarization endpoint used by the official client. It accepts dictionaries or
|
|
392
|
+
`RawMemory` objects and returns a typed `MemorySummarizeResponse`:
|
|
393
|
+
|
|
394
|
+
```python
|
|
395
|
+
from codex_backend_sdk import RawMemory
|
|
396
|
+
|
|
397
|
+
summary = client.codex.memories.trace_summarize(
|
|
398
|
+
model="gpt-5.4",
|
|
399
|
+
traces=[
|
|
400
|
+
RawMemory(
|
|
401
|
+
id="trace_1",
|
|
402
|
+
metadata={"source_path": "memory.jsonl"},
|
|
403
|
+
items=[{"type": "message", "content": "Remember this"}],
|
|
404
|
+
)
|
|
405
|
+
],
|
|
406
|
+
reasoning={"effort": "low"},
|
|
407
|
+
)
|
|
408
|
+
print(summary.output[0].memory_summary)
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Transient HTTP failures (`429`, `5xx`, timeouts, and connection errors) are
|
|
412
|
+
retried by default. Configure this with `OpenAI(max_retries=..., retry_base_delay=...)`.
|
|
413
|
+
|
|
414
|
+
### File Uploads
|
|
415
|
+
|
|
416
|
+
`client.files.upload(...)` follows the official Codex file flow for Apps/MCP
|
|
417
|
+
file parameters: create file metadata under ChatGPT, upload bytes to the signed
|
|
418
|
+
URL, then finalize the upload.
|
|
419
|
+
|
|
420
|
+
```python
|
|
421
|
+
uploaded = client.files.upload("report.csv")
|
|
422
|
+
print(uploaded.uri) # sediment://file_...
|
|
423
|
+
```
|
|
424
|
+
|
|
385
425
|
### Observed But Not Exposed
|
|
386
426
|
|
|
387
427
|
The reverse-engineering notes in `docs/backend-api.md` include additional
|
|
388
428
|
observed endpoints. They are not exposed as SDK resources yet because they are
|
|
389
429
|
plan-gated, unavailable on `chatgpt.com`, or not stable enough:
|
|
390
430
|
|
|
391
|
-
- `POST /backend-api/codex/memories/trace_summarize`
|
|
392
|
-
- `GET /backend-api/wham/config/requirements`
|
|
393
431
|
- `POST /v1/audio/speech` (auth reaches the endpoint, but Pro OAuth lacks
|
|
394
432
|
`api.model.audio.request` in current tests)
|
|
@@ -17,7 +17,7 @@ Quickstart:
|
|
|
17
17
|
print(response.output_text)
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
-
__version__ = "0.3.
|
|
20
|
+
__version__ = "0.3.2"
|
|
21
21
|
|
|
22
22
|
from .oauth import run_oauth_flow, refresh_access_token, obtain_api_key
|
|
23
23
|
from .storage import load_tokens, save_tokens, TokenStore
|
|
@@ -33,7 +33,11 @@ from .codex_client import (
|
|
|
33
33
|
Verbosity,
|
|
34
34
|
CodexClient,
|
|
35
35
|
OpenAI,
|
|
36
|
+
MemorySummarizeOutput,
|
|
37
|
+
MemorySummarizeResponse,
|
|
36
38
|
Model,
|
|
39
|
+
RawMemory,
|
|
40
|
+
RawMemoryMetadata,
|
|
37
41
|
Response,
|
|
38
42
|
ResponseStreamEvent,
|
|
39
43
|
ResponseUsage,
|
|
@@ -41,6 +45,7 @@ from .codex_client import (
|
|
|
41
45
|
SyncPage,
|
|
42
46
|
CompactedResponse,
|
|
43
47
|
Transcription,
|
|
48
|
+
UploadedFile,
|
|
44
49
|
image_url,
|
|
45
50
|
image_b64,
|
|
46
51
|
)
|
|
@@ -53,7 +58,11 @@ __all__ = [
|
|
|
53
58
|
"CreateEmbeddingResponse",
|
|
54
59
|
"Embedding",
|
|
55
60
|
"EmbeddingUsage",
|
|
61
|
+
"MemorySummarizeOutput",
|
|
62
|
+
"MemorySummarizeResponse",
|
|
56
63
|
"Model",
|
|
64
|
+
"RawMemory",
|
|
65
|
+
"RawMemoryMetadata",
|
|
57
66
|
"Response",
|
|
58
67
|
"ResponseStreamEvent",
|
|
59
68
|
"ResponseUsage",
|
|
@@ -61,6 +70,7 @@ __all__ = [
|
|
|
61
70
|
"SyncPage",
|
|
62
71
|
"CompactedResponse",
|
|
63
72
|
"Transcription",
|
|
73
|
+
"UploadedFile",
|
|
64
74
|
"ReasoningEffort",
|
|
65
75
|
"ReasoningSummary",
|
|
66
76
|
"ServiceTier",
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import urllib.parse
|
|
6
|
+
import time
|
|
6
7
|
from typing import Any, Optional
|
|
7
8
|
|
|
8
9
|
import requests
|
|
@@ -32,15 +33,20 @@ class CodexClient:
|
|
|
32
33
|
model: str = "gpt-5.4",
|
|
33
34
|
instructions: Optional[str] = None,
|
|
34
35
|
timeout: float = 120,
|
|
36
|
+
max_retries: int = 2,
|
|
37
|
+
retry_base_delay: float = 0.25,
|
|
35
38
|
) -> None:
|
|
36
39
|
from .resources.codex import CodexResources
|
|
37
40
|
from .resources.models import Models
|
|
38
41
|
from .resources.openai_oauth import Audio, Embeddings
|
|
42
|
+
from .resources.files import Files
|
|
39
43
|
from .resources.realtime import Realtime
|
|
40
44
|
from .resources.responses import Responses
|
|
41
45
|
|
|
42
46
|
self._store = store
|
|
43
47
|
self._timeout = timeout
|
|
48
|
+
self._max_retries = max_retries
|
|
49
|
+
self._retry_base_delay = retry_base_delay
|
|
44
50
|
self._session = requests.Session()
|
|
45
51
|
self._defaults = {
|
|
46
52
|
"model": model,
|
|
@@ -53,6 +59,7 @@ class CodexClient:
|
|
|
53
59
|
self.realtime = Realtime(self)
|
|
54
60
|
self.embeddings = Embeddings(self)
|
|
55
61
|
self.audio = Audio(self)
|
|
62
|
+
self.files = Files(self)
|
|
56
63
|
self.codex = CodexResources(self)
|
|
57
64
|
|
|
58
65
|
def authenticate(self, *, request_api_key: bool = True) -> "CodexClient":
|
|
@@ -119,14 +126,30 @@ class CodexClient:
|
|
|
119
126
|
return False
|
|
120
127
|
|
|
121
128
|
def _get(self, path: str, *, params: Optional[dict[str, Any]] = None) -> dict[str, Any]:
|
|
129
|
+
return self._get_raw(path, params=params).json()
|
|
130
|
+
|
|
131
|
+
def _get_raw(
|
|
132
|
+
self,
|
|
133
|
+
path: str,
|
|
134
|
+
*,
|
|
135
|
+
params: Optional[dict[str, Any]] = None,
|
|
136
|
+
headers: Optional[dict[str, str]] = None,
|
|
137
|
+
timeout: Any = _UNSET,
|
|
138
|
+
) -> requests.Response:
|
|
122
139
|
self._ensure_auth()
|
|
123
|
-
response = self.
|
|
124
|
-
|
|
125
|
-
|
|
140
|
+
response = self._request_with_retries(
|
|
141
|
+
"GET",
|
|
142
|
+
f"{BASE_URL}{path}",
|
|
143
|
+
params=params,
|
|
144
|
+
headers=headers,
|
|
145
|
+
timeout=self._timeout if not _is_given(timeout) else timeout,
|
|
146
|
+
)
|
|
147
|
+
return response
|
|
126
148
|
|
|
127
149
|
def _post(self, path: str, *, body: dict[str, Any], stream: bool = False) -> requests.Response:
|
|
128
150
|
self._ensure_auth()
|
|
129
|
-
response = self.
|
|
151
|
+
response = self._request_with_retries(
|
|
152
|
+
"POST",
|
|
130
153
|
f"{BASE_URL}{path}",
|
|
131
154
|
json=body,
|
|
132
155
|
headers={"Accept": "text/event-stream"} if stream else None,
|
|
@@ -140,6 +163,7 @@ class CodexClient:
|
|
|
140
163
|
self,
|
|
141
164
|
path: str,
|
|
142
165
|
*,
|
|
166
|
+
body: Optional[dict[str, Any]] = None,
|
|
143
167
|
content: Optional[bytes] = None,
|
|
144
168
|
files: Any = None,
|
|
145
169
|
data: Any = None,
|
|
@@ -148,9 +172,11 @@ class CodexClient:
|
|
|
148
172
|
timeout: Any = _UNSET,
|
|
149
173
|
) -> requests.Response:
|
|
150
174
|
self._ensure_auth()
|
|
151
|
-
response = self.
|
|
175
|
+
response = self._request_with_retries(
|
|
176
|
+
"POST",
|
|
152
177
|
f"{BASE_URL}{path}",
|
|
153
|
-
|
|
178
|
+
json=body if files is None and data is None and content is None else None,
|
|
179
|
+
data=content if content is not None else data,
|
|
154
180
|
files=files,
|
|
155
181
|
headers=headers,
|
|
156
182
|
params=params,
|
|
@@ -174,12 +200,14 @@ class CodexClient:
|
|
|
174
200
|
params: Optional[dict[str, Any]] = None,
|
|
175
201
|
timeout: Any = _UNSET,
|
|
176
202
|
) -> dict[str, Any]:
|
|
177
|
-
response =
|
|
203
|
+
response = self._request_with_retries(
|
|
204
|
+
"POST",
|
|
178
205
|
f"{OPENAI_BASE_URL}{path}",
|
|
179
206
|
json=body,
|
|
180
207
|
headers=self._openai_headers(headers),
|
|
181
208
|
params=params,
|
|
182
209
|
timeout=self._timeout if not _is_given(timeout) else timeout,
|
|
210
|
+
_use_session=False,
|
|
183
211
|
)
|
|
184
212
|
response.raise_for_status()
|
|
185
213
|
return response.json()
|
|
@@ -196,7 +224,8 @@ class CodexClient:
|
|
|
196
224
|
timeout: Any = _UNSET,
|
|
197
225
|
stream: bool = False,
|
|
198
226
|
) -> requests.Response:
|
|
199
|
-
response =
|
|
227
|
+
response = self._request_with_retries(
|
|
228
|
+
"POST",
|
|
200
229
|
f"{OPENAI_BASE_URL}{path}",
|
|
201
230
|
json=body if files is None and data is None else None,
|
|
202
231
|
data=data,
|
|
@@ -205,22 +234,75 @@ class CodexClient:
|
|
|
205
234
|
params=params,
|
|
206
235
|
stream=stream,
|
|
207
236
|
timeout=self._timeout if not _is_given(timeout) else timeout,
|
|
237
|
+
_use_session=False,
|
|
208
238
|
)
|
|
209
239
|
response.raise_for_status()
|
|
210
240
|
return response
|
|
211
241
|
|
|
212
242
|
def _get_wham(self, path: str, *, params: Optional[dict[str, Any]] = None) -> dict[str, Any]:
|
|
213
243
|
self._ensure_auth()
|
|
214
|
-
response = self.
|
|
215
|
-
|
|
244
|
+
response = self._request_with_retries(
|
|
245
|
+
"GET",
|
|
246
|
+
f"{WHAM_BASE_URL}{path}",
|
|
247
|
+
params=params,
|
|
248
|
+
timeout=30,
|
|
249
|
+
)
|
|
216
250
|
return response.json()
|
|
217
251
|
|
|
218
252
|
def _get_chatgpt(self, path: str) -> dict[str, Any]:
|
|
219
253
|
self._ensure_auth()
|
|
220
|
-
response = self.
|
|
221
|
-
response.
|
|
254
|
+
response = self._request_with_retries("GET", f"{CHATGPT_BASE_URL}{path}", timeout=30)
|
|
255
|
+
return response.json()
|
|
256
|
+
|
|
257
|
+
def _post_chatgpt(
|
|
258
|
+
self,
|
|
259
|
+
path: str,
|
|
260
|
+
*,
|
|
261
|
+
body: dict[str, Any],
|
|
262
|
+
timeout: Any = _UNSET,
|
|
263
|
+
) -> dict[str, Any]:
|
|
264
|
+
self._ensure_auth()
|
|
265
|
+
response = self._request_with_retries(
|
|
266
|
+
"POST",
|
|
267
|
+
f"{CHATGPT_BASE_URL}{path}",
|
|
268
|
+
json=body,
|
|
269
|
+
timeout=self._timeout if not _is_given(timeout) else timeout,
|
|
270
|
+
)
|
|
222
271
|
return response.json()
|
|
223
272
|
|
|
273
|
+
def _request_with_retries(self, method: str, url: str, **kwargs: Any) -> requests.Response:
|
|
274
|
+
last_error: requests.RequestException | None = None
|
|
275
|
+
use_session = kwargs.pop("_use_session", True)
|
|
276
|
+
for attempt in range(self._max_retries + 1):
|
|
277
|
+
try:
|
|
278
|
+
request = self._session.request if use_session else requests.request
|
|
279
|
+
response = request(method, url, **kwargs)
|
|
280
|
+
if self._should_retry_response(response, attempt):
|
|
281
|
+
self._sleep_before_retry(response, attempt)
|
|
282
|
+
continue
|
|
283
|
+
response.raise_for_status()
|
|
284
|
+
return response
|
|
285
|
+
except (requests.Timeout, requests.ConnectionError) as exc:
|
|
286
|
+
last_error = exc
|
|
287
|
+
if attempt >= self._max_retries:
|
|
288
|
+
raise
|
|
289
|
+
self._sleep_before_retry(None, attempt)
|
|
290
|
+
if last_error is not None:
|
|
291
|
+
raise last_error
|
|
292
|
+
raise RuntimeError("Request retry loop exhausted")
|
|
293
|
+
|
|
294
|
+
def _should_retry_response(self, response: requests.Response, attempt: int) -> bool:
|
|
295
|
+
if attempt >= self._max_retries:
|
|
296
|
+
return False
|
|
297
|
+
return response.status_code == 429 or 500 <= response.status_code < 600
|
|
298
|
+
|
|
299
|
+
def _sleep_before_retry(self, response: requests.Response | None, attempt: int) -> None:
|
|
300
|
+
retry_after = response.headers.get("Retry-After") if response is not None else None
|
|
301
|
+
delay = _parse_retry_after(retry_after)
|
|
302
|
+
if delay is None:
|
|
303
|
+
delay = self._retry_base_delay * (2 ** attempt)
|
|
304
|
+
time.sleep(delay)
|
|
305
|
+
|
|
224
306
|
def realtime_websocket_url(self, *, model: str) -> str:
|
|
225
307
|
"""Return the official OpenAI Realtime WebSocket URL for Codex plugins."""
|
|
226
308
|
if not model:
|
|
@@ -242,3 +324,12 @@ class CodexClient:
|
|
|
242
324
|
|
|
243
325
|
|
|
244
326
|
OpenAI = CodexClient
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _parse_retry_after(value: str | None) -> float | None:
|
|
330
|
+
if not value:
|
|
331
|
+
return None
|
|
332
|
+
try:
|
|
333
|
+
return max(0.0, float(value))
|
|
334
|
+
except ValueError:
|
|
335
|
+
return None
|
|
@@ -173,6 +173,35 @@ class Transcription(CodexBaseModel):
|
|
|
173
173
|
text: str = ""
|
|
174
174
|
|
|
175
175
|
|
|
176
|
+
class RawMemoryMetadata(CodexBaseModel):
|
|
177
|
+
source_path: str
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class RawMemory(CodexBaseModel):
|
|
181
|
+
id: str
|
|
182
|
+
metadata: RawMemoryMetadata
|
|
183
|
+
items: list[Any] = Field(default_factory=list)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class MemorySummarizeOutput(CodexBaseModel):
|
|
187
|
+
raw_memory: str = Field(default="", alias="trace_summary")
|
|
188
|
+
memory_summary: str = ""
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class MemorySummarizeResponse(CodexBaseModel):
|
|
192
|
+
output: list[MemorySummarizeOutput] = Field(default_factory=list)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class UploadedFile(CodexBaseModel):
|
|
196
|
+
file_id: str
|
|
197
|
+
uri: str
|
|
198
|
+
download_url: str
|
|
199
|
+
file_name: str
|
|
200
|
+
file_size_bytes: int
|
|
201
|
+
mime_type: Optional[str] = None
|
|
202
|
+
path: str
|
|
203
|
+
|
|
204
|
+
|
|
176
205
|
class CompactedResponse(CodexBaseModel):
|
|
177
206
|
id: str
|
|
178
207
|
object: str = "response.compacted"
|
|
@@ -14,6 +14,10 @@ from ._models import (
|
|
|
14
14
|
Embedding,
|
|
15
15
|
EmbeddingUsage,
|
|
16
16
|
Model,
|
|
17
|
+
MemorySummarizeOutput,
|
|
18
|
+
MemorySummarizeResponse,
|
|
19
|
+
RawMemory,
|
|
20
|
+
RawMemoryMetadata,
|
|
17
21
|
ReasoningEffort,
|
|
18
22
|
ReasoningSummary,
|
|
19
23
|
RealtimeCallResponse,
|
|
@@ -24,6 +28,7 @@ from ._models import (
|
|
|
24
28
|
SyncPage,
|
|
25
29
|
TokenDetails,
|
|
26
30
|
Transcription,
|
|
31
|
+
UploadedFile,
|
|
27
32
|
Verbosity,
|
|
28
33
|
)
|
|
29
34
|
from ._utils import CodexBackendUnsupportedParameterError, image_b64, image_url
|
|
@@ -36,8 +41,12 @@ __all__ = [
|
|
|
36
41
|
"Embedding",
|
|
37
42
|
"EmbeddingUsage",
|
|
38
43
|
"CompactedResponse",
|
|
44
|
+
"MemorySummarizeOutput",
|
|
45
|
+
"MemorySummarizeResponse",
|
|
39
46
|
"Model",
|
|
40
47
|
"OpenAI",
|
|
48
|
+
"RawMemory",
|
|
49
|
+
"RawMemoryMetadata",
|
|
41
50
|
"ReasoningEffort",
|
|
42
51
|
"ReasoningSummary",
|
|
43
52
|
"Response",
|
|
@@ -48,6 +57,7 @@ __all__ = [
|
|
|
48
57
|
"SyncPage",
|
|
49
58
|
"TokenDetails",
|
|
50
59
|
"Transcription",
|
|
60
|
+
"UploadedFile",
|
|
51
61
|
"Verbosity",
|
|
52
62
|
"image_b64",
|
|
53
63
|
"image_url",
|
|
@@ -4,6 +4,9 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import Any, TYPE_CHECKING
|
|
6
6
|
|
|
7
|
+
from .._models import MemorySummarizeResponse
|
|
8
|
+
from .._utils import _jsonable
|
|
9
|
+
|
|
7
10
|
if TYPE_CHECKING:
|
|
8
11
|
from .._client import CodexClient
|
|
9
12
|
|
|
@@ -16,6 +19,7 @@ class CodexResources:
|
|
|
16
19
|
self.memories = CodexMemories(client)
|
|
17
20
|
self.tasks = CodexTasks(client)
|
|
18
21
|
self.environments = CodexEnvironments(client)
|
|
22
|
+
self.config = CodexConfig(client)
|
|
19
23
|
self.user_system_messages = CodexUserSystemMessages(client)
|
|
20
24
|
|
|
21
25
|
def usage(self) -> dict[str, Any]:
|
|
@@ -31,6 +35,32 @@ class CodexMemories:
|
|
|
31
35
|
def list(self) -> dict[str, Any]:
|
|
32
36
|
return self._client._get_chatgpt("/memories")
|
|
33
37
|
|
|
38
|
+
def trace_summarize(
|
|
39
|
+
self,
|
|
40
|
+
*,
|
|
41
|
+
model: str,
|
|
42
|
+
traces: list[Any],
|
|
43
|
+
reasoning: Any = None,
|
|
44
|
+
) -> MemorySummarizeResponse:
|
|
45
|
+
if not model:
|
|
46
|
+
raise ValueError(f"Expected a non-empty value for `model` but received {model!r}")
|
|
47
|
+
payload: dict[str, Any] = {"model": model, "traces": _jsonable(traces)}
|
|
48
|
+
if reasoning is not None:
|
|
49
|
+
payload["reasoning"] = _jsonable(reasoning)
|
|
50
|
+
return MemorySummarizeResponse.model_validate(
|
|
51
|
+
self._client._post("/memories/trace_summarize", body=payload).json()
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class CodexConfig:
|
|
56
|
+
"""Codex WHAM account configuration resources."""
|
|
57
|
+
|
|
58
|
+
def __init__(self, client: CodexClient) -> None:
|
|
59
|
+
self._client = client
|
|
60
|
+
|
|
61
|
+
def requirements(self) -> dict[str, Any]:
|
|
62
|
+
return self._client._get_wham("/wham/config/requirements")
|
|
63
|
+
|
|
34
64
|
|
|
35
65
|
class CodexTasks:
|
|
36
66
|
"""Codex cloud task data for the authenticated account."""
|