codex-backend-sdk 0.5.2__tar.gz → 0.5.3__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.5.2 → codex_backend_sdk-0.5.3}/CHANGELOG.md +19 -1
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/PKG-INFO +39 -24
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/README.md +34 -19
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/__init__.py +9 -1
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/_client.py +3 -2
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/_models.py +26 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/codex_client.py +8 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/resources/chatgpt_plugins.py +101 -19
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/resources/codex.py +213 -23
- codex_backend_sdk-0.5.3/codex_backend_sdk/resources/realtime.py +437 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/docs/api-reference.md +25 -1
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/docs/backend-api.md +27 -13
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/docs/desktop-endpoint-inventory.md +53 -7
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/docs/endpoint-coverage.md +19 -8
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/docs/openai-compatibility.md +9 -2
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/pyproject.toml +5 -5
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/conftest.py +2 -1
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_basic.py +5 -2
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_chatgpt_plugins.py +59 -13
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_codex_resources.py +148 -22
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_openai_compatibility.py +36 -18
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_realtime_resource.py +72 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/uv.lock +80 -28
- codex_backend_sdk-0.5.2/codex_backend_sdk/resources/realtime.py +0 -171
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/.gitignore +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/LICENSE +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/_streaming.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/_transport.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/_utils.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/oauth.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/pkce.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/resources/__init__.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/resources/_responses_payloads.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/resources/chatgpt.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/resources/chatgpt_apps.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/resources/chatgpt_connectors.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/resources/chatgpt_plugin_bundles.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/resources/chatgpt_plugin_shares.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/resources/chatgpt_writing_blocks.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/resources/codex_web_search.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/resources/files.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/resources/images.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/resources/models.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/resources/openai_oauth.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/resources/remote_control.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/resources/responses.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/resources/responses_websocket.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/resources/worktree_snapshots.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/storage.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/examples/agent.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_account_info.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_chatgpt_apps.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_chatgpt_connectors.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_chatgpt_plugin_bundles.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_chatgpt_plugin_shares.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_chatgpt_projects_gizmos.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_chatgpt_resources.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_chatgpt_search_files.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_chatgpt_writing_blocks.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_client_retry.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_codex_web_search.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_conversation.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_files_resource.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_hosted_mcp.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_images.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_oauth.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_openai_oauth_resources.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_prompt_cache_usage.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_reasoning.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_remote_control_resource.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_responses_resource.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_responses_websocket.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_structured_output.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_tools.py +0 -0
- {codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/tests/test_worktree_snapshots.py +0 -0
|
@@ -2,7 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## [0.5.3] - 2026-09-18
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Added the OpenAI-shaped `client.live.create(...)` and
|
|
9
|
+
`client.live.sideband.connect(...)` surfaces over the existing Codex OAuth
|
|
10
|
+
Realtime v3 transport, including the public response shape and lazy
|
|
11
|
+
sideband context-manager idiom.
|
|
12
|
+
- Added current Codex workspace usage, task-usage, turn-estimate, plugin/skill
|
|
13
|
+
analytics, and reset-credit history resources discovered by the refreshed
|
|
14
|
+
source scan.
|
|
15
|
+
- Added the current Desktop plugin home feed and explicit plugin/skill
|
|
16
|
+
enable-disable mutations.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- Updated the compatibility baseline to `openai-python` 3.16.1 while retaining
|
|
20
|
+
the additive low-level `client.realtime` API.
|
|
21
|
+
- Reconciled the endpoint inventory with the refreshed Codex and official
|
|
22
|
+
Desktop checkouts, and tightened validation of plugin catalog responses and
|
|
23
|
+
pagination state.
|
|
6
24
|
|
|
7
25
|
## [0.5.1] - 2026-08-14
|
|
8
26
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: codex-backend-sdk
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: Unofficial Python SDK for the ChatGPT Codex backend API
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -19,12 +19,12 @@ Requires-Dist: pydantic>=2.0
|
|
|
19
19
|
Requires-Dist: requests>=2.28
|
|
20
20
|
Requires-Dist: websocket-client>=1.8
|
|
21
21
|
Provides-Extra: dev
|
|
22
|
-
Requires-Dist: openai<
|
|
23
|
-
Requires-Dist: openai>=
|
|
22
|
+
Requires-Dist: openai<3.0,>=1.0; (python_version < '3.10') and extra == 'dev'
|
|
23
|
+
Requires-Dist: openai>=3.16.1; (python_version >= '3.10') and extra == 'dev'
|
|
24
24
|
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
25
25
|
Provides-Extra: openai
|
|
26
|
-
Requires-Dist: openai<
|
|
27
|
-
Requires-Dist: openai>=
|
|
26
|
+
Requires-Dist: openai<3.0,>=1.0; (python_version < '3.10') and extra == 'openai'
|
|
27
|
+
Requires-Dist: openai>=3.16.1; (python_version >= '3.10') and extra == 'openai'
|
|
28
28
|
Description-Content-Type: text/markdown
|
|
29
29
|
|
|
30
30
|
# codex-backend-sdk
|
|
@@ -65,7 +65,7 @@ from codex_backend_sdk import OpenAI
|
|
|
65
65
|
client = OpenAI().authenticate()
|
|
66
66
|
|
|
67
67
|
response = client.responses.create(
|
|
68
|
-
model="gpt-5.
|
|
68
|
+
model="gpt-5.5",
|
|
69
69
|
input="Explain quicksort in one paragraph.",
|
|
70
70
|
)
|
|
71
71
|
|
|
@@ -89,7 +89,8 @@ client.files # upload files for Apps/MCP parameters
|
|
|
89
89
|
client.images # Codex-backed image generation and editing
|
|
90
90
|
client.audio # ChatGPT-backed transcription
|
|
91
91
|
client.embeddings # OpenAI embeddings endpoint through OAuth
|
|
92
|
-
client.
|
|
92
|
+
client.live # OpenAI-shaped GPT-Live creation and sideband attachment
|
|
93
|
+
client.realtime # additive low-level Codex Realtime v3 transport
|
|
93
94
|
```
|
|
94
95
|
|
|
95
96
|
These resources follow `openai-python` conventions where the backend overlaps
|
|
@@ -142,7 +143,7 @@ dictionaries or raw streams when imposing a stable model would be misleading.
|
|
|
142
143
|
|
|
143
144
|
```python
|
|
144
145
|
response = client.responses.create(
|
|
145
|
-
model="gpt-5.
|
|
146
|
+
model="gpt-5.5",
|
|
146
147
|
instructions="Be concise.",
|
|
147
148
|
input="Summarize the CAP theorem.",
|
|
148
149
|
reasoning={"effort": "medium", "summary": "auto"},
|
|
@@ -173,7 +174,7 @@ connection reusable across sequential turns:
|
|
|
173
174
|
|
|
174
175
|
```python
|
|
175
176
|
with client.responses.websocket.connect() as ws:
|
|
176
|
-
for event in ws.create({"model": "gpt-5.
|
|
177
|
+
for event in ws.create({"model": "gpt-5.5", "input": "Say hello."}):
|
|
177
178
|
if event.get("type") == "response.output_text.delta":
|
|
178
179
|
print(event.get("delta", ""), end="")
|
|
179
180
|
```
|
|
@@ -197,7 +198,7 @@ print(client.responses.create(input=history).output_text)
|
|
|
197
198
|
|
|
198
199
|
```python
|
|
199
200
|
compacted = client.responses.compact(
|
|
200
|
-
model="gpt-5.
|
|
201
|
+
model="gpt-5.5",
|
|
201
202
|
instructions="Preserve task-critical decisions and unresolved work.",
|
|
202
203
|
input=history,
|
|
203
204
|
)
|
|
@@ -268,7 +269,7 @@ print(result.output_parsed)
|
|
|
268
269
|
for model in client.models.list():
|
|
269
270
|
print(model.id, model.context_window, model.supported_reasoning_levels)
|
|
270
271
|
|
|
271
|
-
model = client.models.retrieve("gpt-5.
|
|
272
|
+
model = client.models.retrieve("gpt-5.5")
|
|
272
273
|
```
|
|
273
274
|
|
|
274
275
|
### Upload a file
|
|
@@ -340,6 +341,10 @@ sports, and time commands. See the API reference for command validation.
|
|
|
340
341
|
quota = client.codex.usage()
|
|
341
342
|
daily = client.codex.usage_details.daily_token_breakdown()
|
|
342
343
|
credits = client.codex.usage_details.credit_events()
|
|
344
|
+
plugin_metrics = client.codex.usage_details.plugin_usage_metrics(
|
|
345
|
+
start_date="2026-09-01",
|
|
346
|
+
end_date="2026-09-18",
|
|
347
|
+
)
|
|
343
348
|
|
|
344
349
|
print(quota.get("rate_limit", {}).get("primary_window"))
|
|
345
350
|
```
|
|
@@ -443,6 +448,7 @@ tool annotations and own user confirmation; the SDK never auto-invokes tools.
|
|
|
443
448
|
plugins = client.chatgpt.plugins.list_all(scope="GLOBAL")
|
|
444
449
|
installed = client.chatgpt.plugins.installed_all()
|
|
445
450
|
suggested = client.chatgpt.plugins.suggested()
|
|
451
|
+
home = client.chatgpt.plugins.home()
|
|
446
452
|
skill = client.chatgpt.plugins.skill("plugins~...", "skill-name")
|
|
447
453
|
|
|
448
454
|
checkout = client.chatgpt.plugins.bundles.extract_plugin(
|
|
@@ -491,33 +497,42 @@ return a typed in-memory object, bytes, `BytesIO`, a data URI, or a file path.
|
|
|
491
497
|
### Realtime voice
|
|
492
498
|
|
|
493
499
|
```python
|
|
494
|
-
|
|
495
|
-
sdp=offer_sdp,
|
|
500
|
+
live = client.live.create(
|
|
496
501
|
session={
|
|
497
502
|
"model": "gpt-live-1-codex",
|
|
498
503
|
"instructions": "Speak naturally and stay concise.",
|
|
504
|
+
"delegation": {"type": "client"},
|
|
499
505
|
},
|
|
506
|
+
transport={"type": "webrtc", "sdp": offer_sdp},
|
|
500
507
|
)
|
|
501
|
-
|
|
508
|
+
apply_remote_sdp(live.transport.sdp)
|
|
509
|
+
|
|
510
|
+
with client.live.sideband.connect(session_id=live.session.id) as sideband:
|
|
511
|
+
for event in sideband:
|
|
512
|
+
print(event.type)
|
|
502
513
|
```
|
|
503
514
|
|
|
504
515
|
Live probes confirmed `gpt-live-1-codex` and
|
|
505
516
|
`gpt-live-1-boulder-alpha`. They are Codex Realtime v3 snapshots, not aliases
|
|
506
|
-
for the public `gpt-
|
|
507
|
-
|
|
517
|
+
for the public `gpt-live-1` model. `client.live` follows the current
|
|
518
|
+
`openai-python` creation, response, and sideband idioms while adapting them to
|
|
519
|
+
the ChatGPT OAuth call transport.
|
|
520
|
+
|
|
521
|
+
The earlier low-level surface remains available for backend-specific code:
|
|
508
522
|
|
|
509
523
|
```python
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
524
|
+
answer = client.realtime.calls.create_v3(
|
|
525
|
+
sdp=offer_sdp,
|
|
526
|
+
session={"model": "gpt-live-1-codex"},
|
|
513
527
|
)
|
|
514
|
-
sideband.
|
|
515
|
-
|
|
516
|
-
sideband.close()
|
|
528
|
+
with client.realtime.sideband.connect(call_id=answer.call_id) as sideband:
|
|
529
|
+
sideband.send({"type": "session.close"})
|
|
517
530
|
```
|
|
518
531
|
|
|
519
|
-
The
|
|
520
|
-
|
|
532
|
+
The OAuth backend's event dialect is still experimental and is exposed
|
|
533
|
+
forward-compatibly through `LiveEvent` rather than rewritten into invented
|
|
534
|
+
public fields. The public primary `client.live.connect()` WebSocket is not
|
|
535
|
+
available through ChatGPT OAuth; use WebRTC plus the attached sideband.
|
|
521
536
|
|
|
522
537
|
### Embeddings
|
|
523
538
|
|
|
@@ -36,7 +36,7 @@ from codex_backend_sdk import OpenAI
|
|
|
36
36
|
client = OpenAI().authenticate()
|
|
37
37
|
|
|
38
38
|
response = client.responses.create(
|
|
39
|
-
model="gpt-5.
|
|
39
|
+
model="gpt-5.5",
|
|
40
40
|
input="Explain quicksort in one paragraph.",
|
|
41
41
|
)
|
|
42
42
|
|
|
@@ -60,7 +60,8 @@ client.files # upload files for Apps/MCP parameters
|
|
|
60
60
|
client.images # Codex-backed image generation and editing
|
|
61
61
|
client.audio # ChatGPT-backed transcription
|
|
62
62
|
client.embeddings # OpenAI embeddings endpoint through OAuth
|
|
63
|
-
client.
|
|
63
|
+
client.live # OpenAI-shaped GPT-Live creation and sideband attachment
|
|
64
|
+
client.realtime # additive low-level Codex Realtime v3 transport
|
|
64
65
|
```
|
|
65
66
|
|
|
66
67
|
These resources follow `openai-python` conventions where the backend overlaps
|
|
@@ -113,7 +114,7 @@ dictionaries or raw streams when imposing a stable model would be misleading.
|
|
|
113
114
|
|
|
114
115
|
```python
|
|
115
116
|
response = client.responses.create(
|
|
116
|
-
model="gpt-5.
|
|
117
|
+
model="gpt-5.5",
|
|
117
118
|
instructions="Be concise.",
|
|
118
119
|
input="Summarize the CAP theorem.",
|
|
119
120
|
reasoning={"effort": "medium", "summary": "auto"},
|
|
@@ -144,7 +145,7 @@ connection reusable across sequential turns:
|
|
|
144
145
|
|
|
145
146
|
```python
|
|
146
147
|
with client.responses.websocket.connect() as ws:
|
|
147
|
-
for event in ws.create({"model": "gpt-5.
|
|
148
|
+
for event in ws.create({"model": "gpt-5.5", "input": "Say hello."}):
|
|
148
149
|
if event.get("type") == "response.output_text.delta":
|
|
149
150
|
print(event.get("delta", ""), end="")
|
|
150
151
|
```
|
|
@@ -168,7 +169,7 @@ print(client.responses.create(input=history).output_text)
|
|
|
168
169
|
|
|
169
170
|
```python
|
|
170
171
|
compacted = client.responses.compact(
|
|
171
|
-
model="gpt-5.
|
|
172
|
+
model="gpt-5.5",
|
|
172
173
|
instructions="Preserve task-critical decisions and unresolved work.",
|
|
173
174
|
input=history,
|
|
174
175
|
)
|
|
@@ -239,7 +240,7 @@ print(result.output_parsed)
|
|
|
239
240
|
for model in client.models.list():
|
|
240
241
|
print(model.id, model.context_window, model.supported_reasoning_levels)
|
|
241
242
|
|
|
242
|
-
model = client.models.retrieve("gpt-5.
|
|
243
|
+
model = client.models.retrieve("gpt-5.5")
|
|
243
244
|
```
|
|
244
245
|
|
|
245
246
|
### Upload a file
|
|
@@ -311,6 +312,10 @@ sports, and time commands. See the API reference for command validation.
|
|
|
311
312
|
quota = client.codex.usage()
|
|
312
313
|
daily = client.codex.usage_details.daily_token_breakdown()
|
|
313
314
|
credits = client.codex.usage_details.credit_events()
|
|
315
|
+
plugin_metrics = client.codex.usage_details.plugin_usage_metrics(
|
|
316
|
+
start_date="2026-09-01",
|
|
317
|
+
end_date="2026-09-18",
|
|
318
|
+
)
|
|
314
319
|
|
|
315
320
|
print(quota.get("rate_limit", {}).get("primary_window"))
|
|
316
321
|
```
|
|
@@ -414,6 +419,7 @@ tool annotations and own user confirmation; the SDK never auto-invokes tools.
|
|
|
414
419
|
plugins = client.chatgpt.plugins.list_all(scope="GLOBAL")
|
|
415
420
|
installed = client.chatgpt.plugins.installed_all()
|
|
416
421
|
suggested = client.chatgpt.plugins.suggested()
|
|
422
|
+
home = client.chatgpt.plugins.home()
|
|
417
423
|
skill = client.chatgpt.plugins.skill("plugins~...", "skill-name")
|
|
418
424
|
|
|
419
425
|
checkout = client.chatgpt.plugins.bundles.extract_plugin(
|
|
@@ -462,33 +468,42 @@ return a typed in-memory object, bytes, `BytesIO`, a data URI, or a file path.
|
|
|
462
468
|
### Realtime voice
|
|
463
469
|
|
|
464
470
|
```python
|
|
465
|
-
|
|
466
|
-
sdp=offer_sdp,
|
|
471
|
+
live = client.live.create(
|
|
467
472
|
session={
|
|
468
473
|
"model": "gpt-live-1-codex",
|
|
469
474
|
"instructions": "Speak naturally and stay concise.",
|
|
475
|
+
"delegation": {"type": "client"},
|
|
470
476
|
},
|
|
477
|
+
transport={"type": "webrtc", "sdp": offer_sdp},
|
|
471
478
|
)
|
|
472
|
-
|
|
479
|
+
apply_remote_sdp(live.transport.sdp)
|
|
480
|
+
|
|
481
|
+
with client.live.sideband.connect(session_id=live.session.id) as sideband:
|
|
482
|
+
for event in sideband:
|
|
483
|
+
print(event.type)
|
|
473
484
|
```
|
|
474
485
|
|
|
475
486
|
Live probes confirmed `gpt-live-1-codex` and
|
|
476
487
|
`gpt-live-1-boulder-alpha`. They are Codex Realtime v3 snapshots, not aliases
|
|
477
|
-
for the public `gpt-
|
|
478
|
-
|
|
488
|
+
for the public `gpt-live-1` model. `client.live` follows the current
|
|
489
|
+
`openai-python` creation, response, and sideband idioms while adapting them to
|
|
490
|
+
the ChatGPT OAuth call transport.
|
|
491
|
+
|
|
492
|
+
The earlier low-level surface remains available for backend-specific code:
|
|
479
493
|
|
|
480
494
|
```python
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
495
|
+
answer = client.realtime.calls.create_v3(
|
|
496
|
+
sdp=offer_sdp,
|
|
497
|
+
session={"model": "gpt-live-1-codex"},
|
|
484
498
|
)
|
|
485
|
-
sideband.
|
|
486
|
-
|
|
487
|
-
sideband.close()
|
|
499
|
+
with client.realtime.sideband.connect(call_id=answer.call_id) as sideband:
|
|
500
|
+
sideband.send({"type": "session.close"})
|
|
488
501
|
```
|
|
489
502
|
|
|
490
|
-
The
|
|
491
|
-
|
|
503
|
+
The OAuth backend's event dialect is still experimental and is exposed
|
|
504
|
+
forward-compatibly through `LiveEvent` rather than rewritten into invented
|
|
505
|
+
public fields. The public primary `client.live.connect()` WebSocket is not
|
|
506
|
+
available through ChatGPT OAuth; use WebRTC plus the attached sideband.
|
|
492
507
|
|
|
493
508
|
### Embeddings
|
|
494
509
|
|
|
@@ -17,7 +17,7 @@ Quickstart:
|
|
|
17
17
|
print(response.output_text)
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
-
__version__ = "0.5.
|
|
20
|
+
__version__ = "0.5.3"
|
|
21
21
|
|
|
22
22
|
from .oauth import (
|
|
23
23
|
DeviceCode,
|
|
@@ -57,6 +57,10 @@ from .codex_client import (
|
|
|
57
57
|
Image,
|
|
58
58
|
ImageResponse,
|
|
59
59
|
ImagesResponse,
|
|
60
|
+
LiveCreateResponse,
|
|
61
|
+
LiveEvent,
|
|
62
|
+
LiveSession,
|
|
63
|
+
LiveTransport,
|
|
60
64
|
RateLimitResetCredit,
|
|
61
65
|
RateLimitResetCredits,
|
|
62
66
|
ConsumeRateLimitResetCreditResponse,
|
|
@@ -102,6 +106,10 @@ __all__ = [
|
|
|
102
106
|
"Image",
|
|
103
107
|
"ImageResponse",
|
|
104
108
|
"ImagesResponse",
|
|
109
|
+
"LiveCreateResponse",
|
|
110
|
+
"LiveEvent",
|
|
111
|
+
"LiveSession",
|
|
112
|
+
"LiveTransport",
|
|
105
113
|
"RateLimitResetCredit",
|
|
106
114
|
"RateLimitResetCredits",
|
|
107
115
|
"ConsumeRateLimitResetCreditResponse",
|
|
@@ -30,7 +30,7 @@ class CodexClient:
|
|
|
30
30
|
self,
|
|
31
31
|
*,
|
|
32
32
|
store: Optional[TokenStore] = None,
|
|
33
|
-
model: str = "gpt-5.
|
|
33
|
+
model: str = "gpt-5.5",
|
|
34
34
|
instructions: Optional[str] = None,
|
|
35
35
|
timeout: float = 120,
|
|
36
36
|
max_retries: int = 2,
|
|
@@ -44,7 +44,7 @@ class CodexClient:
|
|
|
44
44
|
from .resources.openai_oauth import Audio, Embeddings
|
|
45
45
|
from .resources.files import Files
|
|
46
46
|
from .resources.images import Images
|
|
47
|
-
from .resources.realtime import Realtime
|
|
47
|
+
from .resources.realtime import Live, Realtime
|
|
48
48
|
from .resources.responses import Responses
|
|
49
49
|
|
|
50
50
|
self._store = store
|
|
@@ -66,6 +66,7 @@ class CodexClient:
|
|
|
66
66
|
self.responses = Responses(self)
|
|
67
67
|
self.models = Models(self)
|
|
68
68
|
self.realtime = Realtime(self)
|
|
69
|
+
self.live = Live(self)
|
|
69
70
|
self.embeddings = Embeddings(self)
|
|
70
71
|
self.audio = Audio(self)
|
|
71
72
|
self.images = Images(self)
|
|
@@ -435,6 +435,32 @@ class RealtimeCallResponse(BinaryResponseContent):
|
|
|
435
435
|
)
|
|
436
436
|
|
|
437
437
|
|
|
438
|
+
class LiveSession(CodexBaseModel):
|
|
439
|
+
"""Resolved Live session identity returned by the OAuth adapter."""
|
|
440
|
+
|
|
441
|
+
id: str
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
class LiveTransport(CodexBaseModel):
|
|
445
|
+
"""WebRTC answer using the public OpenAI Live response shape."""
|
|
446
|
+
|
|
447
|
+
type: Literal["webrtc"] = "webrtc"
|
|
448
|
+
sdp: str
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
class LiveCreateResponse(CodexBaseModel):
|
|
452
|
+
"""OpenAI-compatible view of a Codex OAuth Realtime call response."""
|
|
453
|
+
|
|
454
|
+
session: LiveSession
|
|
455
|
+
transport: LiveTransport
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
class LiveEvent(CodexBaseModel):
|
|
459
|
+
"""Forward-compatible Live sideband event with attribute access."""
|
|
460
|
+
|
|
461
|
+
type: str
|
|
462
|
+
|
|
463
|
+
|
|
438
464
|
def _is_uuid(value: str) -> bool:
|
|
439
465
|
if len(value) != 36:
|
|
440
466
|
return False
|
|
@@ -18,6 +18,10 @@ from ._models import (
|
|
|
18
18
|
ImageData,
|
|
19
19
|
ImageResponse,
|
|
20
20
|
ImagesResponse,
|
|
21
|
+
LiveCreateResponse,
|
|
22
|
+
LiveEvent,
|
|
23
|
+
LiveSession,
|
|
24
|
+
LiveTransport,
|
|
21
25
|
Model,
|
|
22
26
|
MemorySummarizeOutput,
|
|
23
27
|
MemorySummarizeResponse,
|
|
@@ -55,6 +59,10 @@ __all__ = [
|
|
|
55
59
|
"ImageData",
|
|
56
60
|
"ImageResponse",
|
|
57
61
|
"ImagesResponse",
|
|
62
|
+
"LiveCreateResponse",
|
|
63
|
+
"LiveEvent",
|
|
64
|
+
"LiveSession",
|
|
65
|
+
"LiveTransport",
|
|
58
66
|
"CompactedResponse",
|
|
59
67
|
"MemorySummarizeOutput",
|
|
60
68
|
"MemorySummarizeResponse",
|
{codex_backend_sdk-0.5.2 → codex_backend_sdk-0.5.3}/codex_backend_sdk/resources/chatgpt_plugins.py
RENAMED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import Any, Literal
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
6
6
|
from urllib.parse import quote
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
@@ -23,13 +23,18 @@ class ChatGPTPlugins:
|
|
|
23
23
|
payload = self._client._get_chatgpt(
|
|
24
24
|
"/plugins/featured", params={"platform": platform}
|
|
25
25
|
)
|
|
26
|
-
if not isinstance(payload, list) or not all(
|
|
26
|
+
if not isinstance(payload, list) or not all(
|
|
27
|
+
isinstance(item, str) for item in payload
|
|
28
|
+
):
|
|
27
29
|
raise RuntimeError("Featured plugins returned an invalid plugin-id list.")
|
|
28
30
|
return payload
|
|
29
31
|
|
|
30
32
|
def curated_export(self) -> dict[str, Any]:
|
|
31
33
|
payload = self._client._get_chatgpt("/plugins/export/curated")
|
|
32
|
-
if
|
|
34
|
+
if (
|
|
35
|
+
not isinstance(payload.get("download_url"), str)
|
|
36
|
+
or not payload["download_url"]
|
|
37
|
+
):
|
|
33
38
|
raise RuntimeError("Curated plugin export is missing its download URL.")
|
|
34
39
|
return payload
|
|
35
40
|
|
|
@@ -46,8 +51,16 @@ class ChatGPTPlugins:
|
|
|
46
51
|
params={
|
|
47
52
|
"scope": _scope(scope),
|
|
48
53
|
"limit": _positive(limit, "limit"),
|
|
49
|
-
**(
|
|
50
|
-
|
|
54
|
+
**(
|
|
55
|
+
{}
|
|
56
|
+
if collection is None
|
|
57
|
+
else {"collection": _required(collection, "collection")}
|
|
58
|
+
),
|
|
59
|
+
**(
|
|
60
|
+
{}
|
|
61
|
+
if page_token is None
|
|
62
|
+
else {"pageToken": _required(page_token, "page_token")}
|
|
63
|
+
),
|
|
51
64
|
},
|
|
52
65
|
)
|
|
53
66
|
|
|
@@ -77,7 +90,11 @@ class ChatGPTPlugins:
|
|
|
77
90
|
"q": _required(query, "query"),
|
|
78
91
|
"limit": _positive(limit, "limit"),
|
|
79
92
|
**({} if scope is None else {"scope": _scope(scope)}),
|
|
80
|
-
**(
|
|
93
|
+
**(
|
|
94
|
+
{}
|
|
95
|
+
if page_token is None
|
|
96
|
+
else {"pageToken": _required(page_token, "page_token")}
|
|
97
|
+
),
|
|
81
98
|
},
|
|
82
99
|
)
|
|
83
100
|
|
|
@@ -124,22 +141,35 @@ class ChatGPTPlugins:
|
|
|
124
141
|
"/ps/plugins/workspace/shared",
|
|
125
142
|
params={
|
|
126
143
|
"limit": _positive(limit, "limit"),
|
|
127
|
-
**(
|
|
144
|
+
**(
|
|
145
|
+
{}
|
|
146
|
+
if page_token is None
|
|
147
|
+
else {"pageToken": _required(page_token, "page_token")}
|
|
148
|
+
),
|
|
128
149
|
},
|
|
129
150
|
)
|
|
130
151
|
|
|
152
|
+
def home(self) -> dict[str, Any]:
|
|
153
|
+
"""Return the curated plugin home sections used by current Desktop."""
|
|
154
|
+
payload = self._client._get_chatgpt("/ps/plugins/home", headers=_headers())
|
|
155
|
+
if not isinstance(payload.get("sections"), list):
|
|
156
|
+
raise TypeError("Plugin home response contains invalid sections.")
|
|
157
|
+
return payload
|
|
158
|
+
|
|
131
159
|
def suggested(
|
|
132
160
|
self,
|
|
133
161
|
*,
|
|
134
162
|
scope: Literal["GLOBAL", "USER", "WORKSPACE"] = "GLOBAL",
|
|
135
163
|
) -> dict[str, Any]:
|
|
136
164
|
payload = self._client._get_chatgpt(
|
|
137
|
-
"/ps/plugins/suggested",
|
|
165
|
+
"/ps/plugins/suggested/codex",
|
|
138
166
|
params={"scope": _scope(scope)},
|
|
139
167
|
headers=_headers(),
|
|
140
168
|
)
|
|
141
169
|
if not isinstance(payload.get("plugins"), list):
|
|
142
|
-
raise RuntimeError(
|
|
170
|
+
raise RuntimeError(
|
|
171
|
+
"Suggested plugins response contains an invalid plugin list."
|
|
172
|
+
)
|
|
143
173
|
return payload
|
|
144
174
|
|
|
145
175
|
def retrieve(
|
|
@@ -150,9 +180,7 @@ class ChatGPTPlugins:
|
|
|
150
180
|
) -> dict[str, Any]:
|
|
151
181
|
payload = self._client._get_chatgpt(
|
|
152
182
|
f"/ps/plugins/{_path(plugin_id, 'plugin_id')}",
|
|
153
|
-
params=(
|
|
154
|
-
{"includeDownloadUrls": True} if include_download_urls else None
|
|
155
|
-
),
|
|
183
|
+
params=({"includeDownloadUrls": True} if include_download_urls else None),
|
|
156
184
|
headers=_headers(),
|
|
157
185
|
)
|
|
158
186
|
if not isinstance(payload.get("id"), str):
|
|
@@ -170,12 +198,18 @@ class ChatGPTPlugins:
|
|
|
170
198
|
headers=_headers(),
|
|
171
199
|
)
|
|
172
200
|
if payload.get("plugin_id") != expected_id:
|
|
173
|
-
raise RuntimeError(
|
|
201
|
+
raise RuntimeError(
|
|
202
|
+
"Plugin skill response returned an unexpected plugin ID."
|
|
203
|
+
)
|
|
174
204
|
if payload.get("name") != expected_name:
|
|
175
|
-
raise RuntimeError(
|
|
205
|
+
raise RuntimeError(
|
|
206
|
+
"Plugin skill response returned an unexpected skill name."
|
|
207
|
+
)
|
|
176
208
|
contents = payload.get("skill_md_contents")
|
|
177
209
|
if contents is not None and not isinstance(contents, str):
|
|
178
|
-
raise RuntimeError(
|
|
210
|
+
raise RuntimeError(
|
|
211
|
+
"Plugin skill response returned invalid Markdown contents."
|
|
212
|
+
)
|
|
179
213
|
return payload
|
|
180
214
|
|
|
181
215
|
def _page(self, path: str, *, params: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -195,9 +229,13 @@ class ChatGPTPlugins:
|
|
|
195
229
|
if token_value is None:
|
|
196
230
|
return plugins
|
|
197
231
|
if not isinstance(token_value, str) or not token_value:
|
|
198
|
-
raise RuntimeError(
|
|
232
|
+
raise RuntimeError(
|
|
233
|
+
"Plugin catalog returned an invalid next page token."
|
|
234
|
+
)
|
|
199
235
|
if token_value in seen:
|
|
200
|
-
raise RuntimeError(
|
|
236
|
+
raise RuntimeError(
|
|
237
|
+
"Plugin catalog returned a repeated next page token."
|
|
238
|
+
)
|
|
201
239
|
seen.add(token_value)
|
|
202
240
|
token = token_value
|
|
203
241
|
|
|
@@ -224,6 +262,46 @@ class ChatGPTPluginInstallation:
|
|
|
224
262
|
def uninstall(self, plugin_id: str) -> dict[str, Any]:
|
|
225
263
|
return self._mutate(plugin_id, "uninstall", expected_enabled=False)
|
|
226
264
|
|
|
265
|
+
def enable(self, plugin_id: str) -> dict[str, Any]:
|
|
266
|
+
return self._set_enabled(plugin_id, "enable")
|
|
267
|
+
|
|
268
|
+
def disable(self, plugin_id: str) -> dict[str, Any]:
|
|
269
|
+
return self._set_enabled(plugin_id, "disable")
|
|
270
|
+
|
|
271
|
+
def enable_skill(self, plugin_id: str, skill_name: str) -> dict[str, Any]:
|
|
272
|
+
return self._set_skill_enabled(plugin_id, skill_name, "enable")
|
|
273
|
+
|
|
274
|
+
def disable_skill(self, plugin_id: str, skill_name: str) -> dict[str, Any]:
|
|
275
|
+
return self._set_skill_enabled(plugin_id, skill_name, "disable")
|
|
276
|
+
|
|
277
|
+
def _set_enabled(self, plugin_id: str, action: str) -> dict[str, Any]:
|
|
278
|
+
expected_id = _required(plugin_id, "plugin_id")
|
|
279
|
+
payload = self._client._request_chatgpt(
|
|
280
|
+
"POST",
|
|
281
|
+
f"/ps/plugins/{_path(expected_id, 'plugin_id')}/{action}",
|
|
282
|
+
headers=_headers(),
|
|
283
|
+
).json()
|
|
284
|
+
if not isinstance(payload, dict):
|
|
285
|
+
raise TypeError("Plugin state mutation returned an invalid response.")
|
|
286
|
+
return payload
|
|
287
|
+
|
|
288
|
+
def _set_skill_enabled(
|
|
289
|
+
self, plugin_id: str, skill_name: str, action: str
|
|
290
|
+
) -> dict[str, Any]:
|
|
291
|
+
expected_id = _required(plugin_id, "plugin_id")
|
|
292
|
+
expected_name = _required(skill_name, "skill_name")
|
|
293
|
+
payload = self._client._request_chatgpt(
|
|
294
|
+
"POST",
|
|
295
|
+
(
|
|
296
|
+
f"/ps/plugins/{_path(expected_id, 'plugin_id')}/skills/"
|
|
297
|
+
f"{_path(expected_name, 'skill_name')}/{action}"
|
|
298
|
+
),
|
|
299
|
+
headers=_headers(),
|
|
300
|
+
).json()
|
|
301
|
+
if not isinstance(payload, dict):
|
|
302
|
+
raise TypeError("Plugin skill mutation returned an invalid response.")
|
|
303
|
+
return payload
|
|
304
|
+
|
|
227
305
|
def _mutate(
|
|
228
306
|
self,
|
|
229
307
|
plugin_id: str,
|
|
@@ -252,7 +330,9 @@ def _headers() -> dict[str, str]:
|
|
|
252
330
|
|
|
253
331
|
def _required(value: str, name: str) -> str:
|
|
254
332
|
if not value:
|
|
255
|
-
raise ValueError(
|
|
333
|
+
raise ValueError(
|
|
334
|
+
f"Expected a non-empty value for `{name}` but received {value!r}"
|
|
335
|
+
)
|
|
256
336
|
return value
|
|
257
337
|
|
|
258
338
|
|
|
@@ -275,7 +355,9 @@ def _path(value: str, name: str) -> str:
|
|
|
275
355
|
def _validate_page(payload: dict[str, Any]) -> None:
|
|
276
356
|
plugins = payload.get("plugins")
|
|
277
357
|
pagination = payload.get("pagination")
|
|
278
|
-
if not isinstance(plugins, list) or not all(
|
|
358
|
+
if not isinstance(plugins, list) or not all(
|
|
359
|
+
isinstance(item, dict) for item in plugins
|
|
360
|
+
):
|
|
279
361
|
raise RuntimeError("Plugin catalog response contains an invalid plugin list.")
|
|
280
362
|
if not isinstance(pagination, dict):
|
|
281
363
|
raise RuntimeError("Plugin catalog response contains invalid pagination.")
|