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.
Files changed (40) hide show
  1. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/CHANGELOG.md +12 -0
  2. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/PKG-INFO +2 -2
  3. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/README.md +1 -1
  4. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/__init__.py +1 -1
  5. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/_models.py +27 -1
  6. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/resources/realtime.py +20 -6
  7. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/docs/backend-api.md +11 -4
  8. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/pyproject.toml +1 -1
  9. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_realtime_resource.py +28 -1
  10. codex_backend_sdk-0.3.7/uv.lock +792 -0
  11. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/.gitignore +0 -0
  12. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/LICENSE +0 -0
  13. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/_client.py +0 -0
  14. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/_streaming.py +0 -0
  15. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/_transport.py +0 -0
  16. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/_utils.py +0 -0
  17. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/codex_client.py +0 -0
  18. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/oauth.py +0 -0
  19. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/pkce.py +0 -0
  20. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/resources/__init__.py +0 -0
  21. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/resources/_responses_payloads.py +0 -0
  22. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/resources/codex.py +0 -0
  23. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/resources/files.py +0 -0
  24. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/resources/models.py +0 -0
  25. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/resources/openai_oauth.py +0 -0
  26. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/resources/responses.py +0 -0
  27. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/codex_backend_sdk/storage.py +0 -0
  28. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/examples/agent.py +0 -0
  29. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/conftest.py +0 -0
  30. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_account_info.py +0 -0
  31. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_basic.py +0 -0
  32. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_client_retry.py +0 -0
  33. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_codex_resources.py +0 -0
  34. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_conversation.py +0 -0
  35. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_files_resource.py +0 -0
  36. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_openai_oauth_resources.py +0 -0
  37. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_reasoning.py +0 -0
  38. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_responses_resource.py +0 -0
  39. {codex_backend_sdk-0.3.6 → codex_backend_sdk-0.3.7}/tests/test_structured_output.py +0 -0
  40. {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.6
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(...)` | OpenAI-shaped SDP call creation for realtime sessions. |
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(...)` | OpenAI-shaped SDP call creation for realtime sessions. |
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`. |
@@ -17,7 +17,7 @@ Quickstart:
17
17
  print(response.output_text)
18
18
  """
19
19
 
20
- __version__ = "0.3.6"
20
+ __version__ = "0.3.7"
21
21
 
22
22
  from .oauth import run_oauth_flow, refresh_access_token
23
23
  from .storage import load_tokens, save_tokens, TokenStore
@@ -304,4 +304,30 @@ class BinaryResponseContent:
304
304
 
305
305
 
306
306
  class RealtimeCallResponse(BinaryResponseContent):
307
- """Binary SDP response returned by ``client.realtime.calls.create``."""
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=extra_query,
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**: Supported. The SDK follows the official OpenAI SDK shape:
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 response is returned as binary SDP content with `.content`, `.text`,
274
- `.read()`, `.iter_bytes()`, and `.write_to_file(...)` helpers.
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
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "codex-backend-sdk"
7
- version = "0.3.6"
7
+ version = "0.3.7"
8
8
  description = "Unofficial Python SDK for the ChatGPT Codex backend API"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -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