codex-backend-sdk 0.3.6__tar.gz → 0.3.7__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.6 → codex_backend_sdk-0.3.7}/CHANGELOG.md +12 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/PKG-INFO +2 -2
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/README.md +1 -1
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/__init__.py +1 -1
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/_models.py +27 -1
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/resources/realtime.py +20 -6
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/docs/backend-api.md +11 -4
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/pyproject.toml +1 -1
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_realtime_resource.py +28 -1
- codex_backend_sdk-0.3.7/uv.lock +792 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/.gitignore +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/LICENSE +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/_client.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/_streaming.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/_transport.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/_utils.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/codex_client.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/oauth.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/pkce.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/resources/__init__.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/resources/_responses_payloads.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/resources/codex.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/resources/files.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/resources/models.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/resources/openai_oauth.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/resources/responses.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/storage.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/examples/agent.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/conftest.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_account_info.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_basic.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_client_retry.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_codex_resources.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_conversation.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_files_resource.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_openai_oauth_resources.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_reasoning.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_responses_resource.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_structured_output.py +0 -0
- {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_tools.py +0 -0
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.3.7] - 2026-07-17
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Added typed Realtime call results through `RealtimeCallResponse.answer_sdp` and `RealtimeCallResponse.call_id`.
|
|
9
|
+
- Added Codex AVAS session payload support, including automatic removal of the server-generated session `id` and the required `quicksilver` query parameters.
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
- Documented that the ChatGPT-authenticated Codex WebRTC route is experimental and rollout-dependent, while the public Realtime WebSocket route still requires a developer API key.
|
|
13
|
+
|
|
14
|
+
### Tests
|
|
15
|
+
- Added coverage for SDP response parsing, call ID validation, AVAS payload construction, and invalid session payloads.
|
|
16
|
+
|
|
5
17
|
## [0.3.6] - 2026-07-11
|
|
6
18
|
|
|
7
19
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-backend-sdk
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7
|
|
4
4
|
Summary: Unofficial Python SDK for the ChatGPT Codex backend API
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -186,7 +186,7 @@ resources (`responses`, `models`, `realtime`) or Codex-only resources (`codex`).
|
|
|
186
186
|
| `POST /backend-api/codex/responses/compact` | `client.responses.compact(...)` | Codex-specific helper for encrypted context compaction. |
|
|
187
187
|
| `POST /backend-api/codex/memories/trace_summarize` | `client.codex.memories.trace_summarize(...)` | Raw Codex memory trace summarization helper. |
|
|
188
188
|
| `GET /backend-api/codex/models` | `client.models.list()` / `client.models.retrieve(...)` | OpenAI-shaped model objects with Codex metadata preserved as extra fields. |
|
|
189
|
-
| `POST /backend-api/codex/realtime/calls` | `client.realtime.calls.create(...)` |
|
|
189
|
+
| `POST /backend-api/codex/realtime/calls` | `client.realtime.calls.create(...)` | Experimental SDP call creation. The protocol is implemented by Codex, but ChatGPT routing is rollout-dependent and may return `404 Not Found`. |
|
|
190
190
|
| `wss://api.openai.com/v1/realtime?model=...` | `client.realtime_websocket_url(...)` / `client.realtime.websocket_headers(...)` | Voice v2 helpers; requires a Realtime API key obtained during OAuth or supplied by the auth store. |
|
|
191
191
|
| `POST /v1/embeddings` | `client.embeddings.create(...)` | Uses the Codex OAuth access token against `api.openai.com`; verified with `text-embedding-3-small`. |
|
|
192
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`. |
|
|
@@ -160,7 +160,7 @@ resources (`responses`, `models`, `realtime`) or Codex-only resources (`codex`).
|
|
|
160
160
|
| `POST /backend-api/codex/responses/compact` | `client.responses.compact(...)` | Codex-specific helper for encrypted context compaction. |
|
|
161
161
|
| `POST /backend-api/codex/memories/trace_summarize` | `client.codex.memories.trace_summarize(...)` | Raw Codex memory trace summarization helper. |
|
|
162
162
|
| `GET /backend-api/codex/models` | `client.models.list()` / `client.models.retrieve(...)` | OpenAI-shaped model objects with Codex metadata preserved as extra fields. |
|
|
163
|
-
| `POST /backend-api/codex/realtime/calls` | `client.realtime.calls.create(...)` |
|
|
163
|
+
| `POST /backend-api/codex/realtime/calls` | `client.realtime.calls.create(...)` | Experimental SDP call creation. The protocol is implemented by Codex, but ChatGPT routing is rollout-dependent and may return `404 Not Found`. |
|
|
164
164
|
| `wss://api.openai.com/v1/realtime?model=...` | `client.realtime_websocket_url(...)` / `client.realtime.websocket_headers(...)` | Voice v2 helpers; requires a Realtime API key obtained during OAuth or supplied by the auth store. |
|
|
165
165
|
| `POST /v1/embeddings` | `client.embeddings.create(...)` | Uses the Codex OAuth access token against `api.openai.com`; verified with `text-embedding-3-small`. |
|
|
166
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`. |
|
|
@@ -304,4 +304,30 @@ class BinaryResponseContent:
|
|
|
304
304
|
|
|
305
305
|
|
|
306
306
|
class RealtimeCallResponse(BinaryResponseContent):
|
|
307
|
-
"""
|
|
307
|
+
"""SDP answer and call identifier returned by Realtime call creation."""
|
|
308
|
+
|
|
309
|
+
@property
|
|
310
|
+
def answer_sdp(self) -> str:
|
|
311
|
+
return self.text
|
|
312
|
+
|
|
313
|
+
@property
|
|
314
|
+
def call_id(self) -> str:
|
|
315
|
+
location = self.response.headers.get("Location")
|
|
316
|
+
if not location:
|
|
317
|
+
raise RuntimeError("Realtime call response is missing the Location header.")
|
|
318
|
+
path = location.split("?", 1)[0].rstrip("/")
|
|
319
|
+
call_id = path.rsplit("/", 1)[-1]
|
|
320
|
+
if call_id.startswith("rtc_") or _is_uuid(call_id):
|
|
321
|
+
return call_id
|
|
322
|
+
raise RuntimeError(
|
|
323
|
+
f"Realtime call Location does not contain a valid call id: {location}"
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _is_uuid(value: str) -> bool:
|
|
328
|
+
if len(value) != 36:
|
|
329
|
+
return False
|
|
330
|
+
return all(
|
|
331
|
+
char == "-" if index in {8, 13, 18, 23} else char in "0123456789abcdefABCDEF"
|
|
332
|
+
for index, char in enumerate(value)
|
|
333
|
+
)
|
|
@@ -35,6 +35,12 @@ class Realtime:
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
class RealtimeCalls:
|
|
38
|
+
"""Experimental Codex WebRTC call creation.
|
|
39
|
+
|
|
40
|
+
Availability depends on the Realtime call route enabled for the authenticated
|
|
41
|
+
ChatGPT account. A correctly authenticated request may still return 404 while
|
|
42
|
+
the backend is not rolled out.
|
|
43
|
+
"""
|
|
38
44
|
def __init__(self, client: CodexClient) -> None:
|
|
39
45
|
self._client = client
|
|
40
46
|
|
|
@@ -65,15 +71,23 @@ class RealtimeCalls:
|
|
|
65
71
|
)
|
|
66
72
|
return RealtimeCallResponse(response)
|
|
67
73
|
|
|
74
|
+
session_payload = _jsonable(session)
|
|
75
|
+
if not isinstance(session_payload, dict):
|
|
76
|
+
raise TypeError("Expected `session` to serialize to a JSON object.")
|
|
77
|
+
session_payload.pop("id", None)
|
|
78
|
+
body = {
|
|
79
|
+
"sdp": sdp,
|
|
80
|
+
"session": session_payload,
|
|
81
|
+
**(_jsonable(extra_body) if extra_body else {}),
|
|
82
|
+
}
|
|
83
|
+
query = {"intent": "quicksilver", "architecture": "avas"}
|
|
84
|
+
if extra_query:
|
|
85
|
+
query.update(extra_query)
|
|
68
86
|
response = self._client._post_raw(
|
|
69
87
|
"/realtime/calls",
|
|
70
|
-
body=
|
|
71
|
-
"sdp": sdp,
|
|
72
|
-
"session": _jsonable(session),
|
|
73
|
-
**(_jsonable(extra_body) if extra_body else {}),
|
|
74
|
-
},
|
|
88
|
+
body=body,
|
|
75
89
|
headers={"Accept": "application/sdp", **(extra_headers or {})},
|
|
76
|
-
params=
|
|
90
|
+
params=query,
|
|
77
91
|
timeout=timeout,
|
|
78
92
|
)
|
|
79
93
|
return RealtimeCallResponse(response)
|
|
@@ -264,14 +264,21 @@ Realtime audio/video call initiation.
|
|
|
264
264
|
|
|
265
265
|
**SDK method**: `client.realtime.calls.create(...)`
|
|
266
266
|
|
|
267
|
-
**Status**:
|
|
267
|
+
**Status**: Experimental and rollout-dependent. The SDK follows the Codex
|
|
268
|
+
client protocol:
|
|
268
269
|
|
|
269
270
|
- plain SDP offer: `client.realtime.calls.create(sdp=offer_sdp)`
|
|
270
|
-
- SDP offer plus session payload:
|
|
271
|
+
- AVAS SDP offer plus session payload:
|
|
271
272
|
`client.realtime.calls.create(sdp=offer_sdp, session={...})`
|
|
272
273
|
|
|
273
|
-
The
|
|
274
|
-
|
|
274
|
+
The OAuth-authenticated ChatGPT route is not enabled for every account and may
|
|
275
|
+
return `404 Not Found` until Codex supplies an experimental WebRTC call base URL.
|
|
276
|
+
This is distinct from the public Realtime WebSocket route, which currently
|
|
277
|
+
requires a developer API key.
|
|
278
|
+
|
|
279
|
+
The response exposes `.answer_sdp` and `.call_id`, while preserving the binary
|
|
280
|
+
helpers `.content`, `.text`, `.read()`, `.iter_bytes()`, and
|
|
281
|
+
`.write_to_file(...)`.
|
|
275
282
|
|
|
276
283
|
---
|
|
277
284
|
|
|
@@ -6,6 +6,7 @@ class FakeResponse:
|
|
|
6
6
|
content = b"answer-sdp"
|
|
7
7
|
text = "answer-sdp"
|
|
8
8
|
encoding = "utf-8"
|
|
9
|
+
headers = {"Location": "/v1/realtime/calls/calls/rtc_test"}
|
|
9
10
|
|
|
10
11
|
def json(self, **kwargs):
|
|
11
12
|
return {"ok": True}
|
|
@@ -43,6 +44,8 @@ def test_realtime_calls_create_posts_plain_sdp_like_official_sdk():
|
|
|
43
44
|
|
|
44
45
|
assert isinstance(response, RealtimeCallResponse)
|
|
45
46
|
assert response.read() == b"answer-sdp"
|
|
47
|
+
assert response.answer_sdp == "answer-sdp"
|
|
48
|
+
assert response.call_id == "rtc_test"
|
|
46
49
|
path, kwargs = client.raw_posts[0]
|
|
47
50
|
assert path == "/realtime/calls"
|
|
48
51
|
assert kwargs["content"] == b"offer-sdp"
|
|
@@ -55,7 +58,7 @@ def test_realtime_calls_create_posts_session_as_backend_json():
|
|
|
55
58
|
|
|
56
59
|
client.realtime.calls.create(
|
|
57
60
|
sdp="offer-sdp",
|
|
58
|
-
session={"type": "realtime", "model": "gpt-realtime-1.5"},
|
|
61
|
+
session={"id": "session-id", "type": "realtime", "model": "gpt-realtime-1.5"},
|
|
59
62
|
)
|
|
60
63
|
|
|
61
64
|
path, kwargs = client.raw_posts[0]
|
|
@@ -64,9 +67,33 @@ def test_realtime_calls_create_posts_session_as_backend_json():
|
|
|
64
67
|
"sdp": "offer-sdp",
|
|
65
68
|
"session": {"type": "realtime", "model": "gpt-realtime-1.5"},
|
|
66
69
|
}
|
|
70
|
+
assert kwargs["params"] == {"intent": "quicksilver", "architecture": "avas"}
|
|
67
71
|
assert kwargs["headers"]["Accept"] == "application/sdp"
|
|
68
72
|
|
|
69
73
|
|
|
74
|
+
def test_realtime_calls_create_rejects_non_object_session():
|
|
75
|
+
client = FakeRealtimeClient()
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
client.realtime.calls.create(sdp="offer-sdp", session=["invalid"])
|
|
79
|
+
except TypeError as exc:
|
|
80
|
+
assert str(exc) == "Expected `session` to serialize to a JSON object."
|
|
81
|
+
else:
|
|
82
|
+
raise AssertionError("Expected non-object session to fail")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_realtime_call_response_requires_valid_location():
|
|
86
|
+
response = FakeResponse()
|
|
87
|
+
response.headers = {"Location": "/v1/realtime/calls/not-a-call"}
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
RealtimeCallResponse(response).call_id
|
|
91
|
+
except RuntimeError as exc:
|
|
92
|
+
assert "does not contain a valid call id" in str(exc)
|
|
93
|
+
else:
|
|
94
|
+
raise AssertionError("Expected invalid Location to fail")
|
|
95
|
+
|
|
96
|
+
|
|
70
97
|
def test_realtime_websocket_uses_api_key_exchanged_during_oauth():
|
|
71
98
|
client = FakeRealtimeClient()
|
|
72
99
|
|