codex-backend-sdk 0.5.6__tar.gz → 0.5.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.5.6 → codex_backend_sdk-0.5.7}/CHANGELOG.md +9 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/PKG-INFO +19 -1
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/README.md +18 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/__init__.py +1 -1
- codex_backend_sdk-0.5.7/codex_backend_sdk/_codex_version.py +45 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/models.py +7 -7
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/docs/api-reference.md +1 -1
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/docs/backend-api.md +5 -1
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/pyproject.toml +1 -1
- codex_backend_sdk-0.5.7/tests/test_codex_version.py +88 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_responses_resource.py +28 -1
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/uv.lock +1 -1
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/.gitignore +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/LICENSE +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/_client.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/_models.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/_streaming.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/_transport.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/_utils.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/codex_client.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/oauth.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/pkce.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/__init__.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/_responses_payloads.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/_responses_routing.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/chatgpt.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/chatgpt_apps.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/chatgpt_connectors.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/chatgpt_plugin_bundles.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/chatgpt_plugin_shares.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/chatgpt_plugins.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/chatgpt_writing_blocks.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/codex.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/codex_web_search.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/files.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/images.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/openai_oauth.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/realtime.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/remote_control.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/responses.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/responses_websocket.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/worktree_snapshots.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/storage.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/docs/desktop-endpoint-inventory.md +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/docs/endpoint-coverage.md +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/docs/openai-compatibility.md +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/examples/agent.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/conftest.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_account_info.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_basic.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_chatgpt_apps.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_chatgpt_connectors.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_chatgpt_plugin_bundles.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_chatgpt_plugin_shares.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_chatgpt_plugins.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_chatgpt_projects_gizmos.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_chatgpt_resources.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_chatgpt_search_files.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_chatgpt_writing_blocks.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_client_retry.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_codex_resources.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_codex_web_search.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_conversation.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_files_resource.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_hosted_mcp.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_images.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_oauth.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_openai_compatibility.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_openai_oauth_resources.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_prompt_cache_usage.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_realtime_resource.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_reasoning.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_remote_control_resource.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_responses_routing.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_responses_websocket.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_structured_output.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_tools.py +0 -0
- {codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/tests/test_worktree_snapshots.py +0 -0
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.5.7] - 2026-10-22
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- Discover the latest stable Codex CLI version over credential-free HTTPS for
|
|
9
|
+
model catalogs instead of sending a stale fixed version that can hide models.
|
|
10
|
+
Share a 24-hour process cache, retain a verified fallback on registry failure,
|
|
11
|
+
and allow an explicit `extra_query` version without registry access.
|
|
12
|
+
- Keep catalogs fetched with custom request options out of the default cache.
|
|
13
|
+
|
|
5
14
|
## [0.5.6] - 2026-10-22
|
|
6
15
|
|
|
7
16
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: codex-backend-sdk
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.7
|
|
4
4
|
Summary: Unofficial Python SDK for the ChatGPT Codex backend API
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -278,6 +278,24 @@ for model in client.models.list():
|
|
|
278
278
|
model = client.models.retrieve("gpt-5.5")
|
|
279
279
|
```
|
|
280
280
|
|
|
281
|
+
Model discovery obtains the latest stable Codex CLI version over HTTPS from the
|
|
282
|
+
public npm registry; Node, npm and the Codex binary are not required. The version
|
|
283
|
+
is cached for 24 hours per process, shared across clients. The lookup uses a
|
|
284
|
+
separate request without backend credentials and a three-second timeout. On
|
|
285
|
+
failure it keeps the last known version (or a bundled verified fallback) and
|
|
286
|
+
retries no sooner than five minutes later. This only updates the advertised
|
|
287
|
+
catalog version, not the SDK's implementation of backend features.
|
|
288
|
+
|
|
289
|
+
To pin the catalog version and skip registry access entirely:
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
models = client.models.list(extra_query={"client_version": "0.159.2"})
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
`force_refresh=True` bypasses the five-minute model catalog cache, not the
|
|
296
|
+
24-hour version cache. Requests with explicit options do not replace the default
|
|
297
|
+
catalog cache. Other protocol and application version fields are independent.
|
|
298
|
+
|
|
281
299
|
### Upload a file
|
|
282
300
|
|
|
283
301
|
```python
|
|
@@ -249,6 +249,24 @@ for model in client.models.list():
|
|
|
249
249
|
model = client.models.retrieve("gpt-5.5")
|
|
250
250
|
```
|
|
251
251
|
|
|
252
|
+
Model discovery obtains the latest stable Codex CLI version over HTTPS from the
|
|
253
|
+
public npm registry; Node, npm and the Codex binary are not required. The version
|
|
254
|
+
is cached for 24 hours per process, shared across clients. The lookup uses a
|
|
255
|
+
separate request without backend credentials and a three-second timeout. On
|
|
256
|
+
failure it keeps the last known version (or a bundled verified fallback) and
|
|
257
|
+
retries no sooner than five minutes later. This only updates the advertised
|
|
258
|
+
catalog version, not the SDK's implementation of backend features.
|
|
259
|
+
|
|
260
|
+
To pin the catalog version and skip registry access entirely:
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
models = client.models.list(extra_query={"client_version": "0.159.2"})
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
`force_refresh=True` bypasses the five-minute model catalog cache, not the
|
|
267
|
+
24-hour version cache. Requests with explicit options do not replace the default
|
|
268
|
+
catalog cache. Other protocol and application version fields are independent.
|
|
269
|
+
|
|
252
270
|
### Upload a file
|
|
253
271
|
|
|
254
272
|
```python
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Shared discovery of the upstream Codex CLI version (not the SDK version)."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# Last verified upstream protocol version, used when discovery is unavailable.
|
|
11
|
+
CLIENT_VERSION = "0.163.0"
|
|
12
|
+
_RETRY_TTL = 300
|
|
13
|
+
_VERSION_TTL = 24 * 60 * 60
|
|
14
|
+
_VERSION_URL = "https://registry.npmjs.org/@openai/codex/latest"
|
|
15
|
+
_version = CLIENT_VERSION
|
|
16
|
+
_version_refresh_at = 0.0
|
|
17
|
+
_version_lock = threading.Lock()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def codex_client_version() -> str:
|
|
21
|
+
"""Discover the stable CLI version without sharing backend credentials.
|
|
22
|
+
|
|
23
|
+
Cache across clients in this process. Failed refreshes retain the last known
|
|
24
|
+
version and wait five minutes before retrying, including on first use.
|
|
25
|
+
"""
|
|
26
|
+
global _version, _version_refresh_at
|
|
27
|
+
with _version_lock:
|
|
28
|
+
if time.monotonic() < _version_refresh_at:
|
|
29
|
+
return _version
|
|
30
|
+
ttl = _VERSION_TTL
|
|
31
|
+
try:
|
|
32
|
+
with requests.get(_VERSION_URL, timeout=3) as response:
|
|
33
|
+
response.raise_for_status()
|
|
34
|
+
payload = response.json()
|
|
35
|
+
candidate = payload.get("version") if isinstance(payload, dict) else None
|
|
36
|
+
if not isinstance(candidate, str) or not re.fullmatch(
|
|
37
|
+
r"[0-9]+\.[0-9]+\.[0-9]+", candidate
|
|
38
|
+
):
|
|
39
|
+
raise ValueError("Invalid stable Codex version")
|
|
40
|
+
_version = candidate
|
|
41
|
+
except (requests.RequestException, ValueError):
|
|
42
|
+
ttl = _RETRY_TTL
|
|
43
|
+
_version_refresh_at = time.monotonic() + ttl
|
|
44
|
+
return _version
|
|
45
|
+
|
|
@@ -5,18 +5,15 @@ from __future__ import annotations
|
|
|
5
5
|
import time
|
|
6
6
|
from typing import Any, TYPE_CHECKING
|
|
7
7
|
|
|
8
|
+
from .._codex_version import codex_client_version
|
|
8
9
|
from .._models import Model, SyncPage
|
|
9
10
|
from .._utils import _UNSET, CodexBackendUnsupportedParameterError, _is_given
|
|
10
11
|
|
|
11
12
|
if TYPE_CHECKING:
|
|
12
13
|
from .._client import CodexClient
|
|
13
14
|
|
|
14
|
-
# This is the upstream Codex CLI protocol/client version expected by the
|
|
15
|
-
# ChatGPT backend, not the PyPI package version.
|
|
16
|
-
CLIENT_VERSION = "0.130.0"
|
|
17
15
|
_CACHE_TTL = 300
|
|
18
16
|
|
|
19
|
-
|
|
20
17
|
class Models:
|
|
21
18
|
def __init__(self, client: CodexClient) -> None:
|
|
22
19
|
self._client = client
|
|
@@ -41,7 +38,9 @@ class Models:
|
|
|
41
38
|
if time.time() - self._cache_fetched_at <= _CACHE_TTL:
|
|
42
39
|
return self._cache
|
|
43
40
|
|
|
44
|
-
params =
|
|
41
|
+
params = dict(extra_query or {})
|
|
42
|
+
if "client_version" not in params:
|
|
43
|
+
params["client_version"] = codex_client_version()
|
|
45
44
|
response = self._client._get_raw(
|
|
46
45
|
"/models", params=params, headers=extra_headers, timeout=timeout
|
|
47
46
|
)
|
|
@@ -49,8 +48,9 @@ class Models:
|
|
|
49
48
|
models = [_model_from_backend(item) for item in data.get("models", [])]
|
|
50
49
|
models.sort(key=lambda model: getattr(model, "priority", 0))
|
|
51
50
|
page = SyncPage(data=models, etag=response.headers.get("ETag"))
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
if not has_request_options:
|
|
52
|
+
self._cache = page
|
|
53
|
+
self._cache_fetched_at = time.time()
|
|
54
54
|
return page
|
|
55
55
|
|
|
56
56
|
def retrieve(
|
|
@@ -175,7 +175,7 @@ concurrent, turns. Backend error envelopes raise `ResponsesWebSocketError` with
|
|
|
175
175
|
|
|
176
176
|
| Method | Parameters | Returns |
|
|
177
177
|
| --- | --- | --- |
|
|
178
|
-
| `client.models.list(...)` | `force_refresh=False`, `extra_headers=None`, `extra_query=None`, `extra_body=None`, `timeout=UNSET` | iterable `SyncPage` with optional `etag`; cached for 5 minutes |
|
|
178
|
+
| `client.models.list(...)` | `force_refresh=False`, `extra_headers=None`, `extra_query=None`, `extra_body=None`, `timeout=UNSET` | iterable `SyncPage` with optional `etag`; default catalog cached for 5 minutes; Codex version auto-discovered over HTTPS (24-hour process cache), or pinned via `extra_query["client_version"]` |
|
|
179
179
|
| `client.models.retrieve(...)` | `model`, plus the same transport controls | one typed `Model`; raises `LookupError` if absent |
|
|
180
180
|
|
|
181
181
|
`Model` preserves Codex fields including display metadata, context window,
|
|
@@ -78,7 +78,11 @@ List models available to this account.
|
|
|
78
78
|
**SDK method**: `client.models.list()` / `client.models.retrieve(model)`
|
|
79
79
|
|
|
80
80
|
**Query params**
|
|
81
|
-
- `client_version` — Codex CLI
|
|
81
|
+
- `client_version` — Codex CLI version string. The SDK discovers the latest stable
|
|
82
|
+
version from the public npm registry over HTTPS (24-hour in-process cache;
|
|
83
|
+
three-second timeout; last-known/bundled fallback with five-minute retry
|
|
84
|
+
backoff). Override with `extra_query={"client_version": "0.159.2"}` to skip
|
|
85
|
+
discovery. No Node/npm installation is required.
|
|
82
86
|
|
|
83
87
|
**Response** — JSON `{ "models": [ ModelObject, … ] }`
|
|
84
88
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Offline tests for upstream version discovery; no OAuth or npm binary."""
|
|
2
|
+
|
|
3
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
4
|
+
from unittest.mock import MagicMock
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
import requests
|
|
8
|
+
|
|
9
|
+
from codex_backend_sdk import _codex_version as version
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def registry(monkeypatch):
|
|
14
|
+
clock = [100.0]
|
|
15
|
+
monkeypatch.setattr(version.time, "monotonic", lambda: clock[0])
|
|
16
|
+
monkeypatch.setattr(version, "_version", version.CLIENT_VERSION)
|
|
17
|
+
monkeypatch.setattr(version, "_version_refresh_at", 0.0)
|
|
18
|
+
response = MagicMock()
|
|
19
|
+
response.__enter__.return_value = response
|
|
20
|
+
response.json.return_value = {"version": "0.170.0"}
|
|
21
|
+
get = MagicMock(return_value=response)
|
|
22
|
+
monkeypatch.setattr(version.requests, "get", get)
|
|
23
|
+
return clock, get, response
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_version_uses_standalone_https_and_caches_for_24_hours(registry):
|
|
27
|
+
clock, get, response = registry
|
|
28
|
+
assert version.codex_client_version() == "0.170.0"
|
|
29
|
+
get.assert_called_once_with(version._VERSION_URL, timeout=3)
|
|
30
|
+
response.raise_for_status.assert_called_once()
|
|
31
|
+
response.__exit__.assert_called_once()
|
|
32
|
+
clock[0] += 86400 - 1
|
|
33
|
+
assert version.codex_client_version() == "0.170.0"
|
|
34
|
+
assert get.call_count == 1
|
|
35
|
+
clock[0] += 1
|
|
36
|
+
response.json.return_value = {"version": "0.171.0"}
|
|
37
|
+
assert version.codex_client_version() == "0.171.0"
|
|
38
|
+
assert get.call_count == 2
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.mark.parametrize("error", [requests.Timeout(), requests.HTTPError(), ValueError()])
|
|
42
|
+
def test_first_failure_uses_verified_fallback_and_backs_off(registry, error):
|
|
43
|
+
clock, get, _ = registry
|
|
44
|
+
get.side_effect = error
|
|
45
|
+
assert version.codex_client_version() == version.CLIENT_VERSION
|
|
46
|
+
assert version.codex_client_version() == version.CLIENT_VERSION
|
|
47
|
+
assert get.call_count == 1
|
|
48
|
+
clock[0] += 300
|
|
49
|
+
get.side_effect = None
|
|
50
|
+
assert version.codex_client_version() == "0.170.0"
|
|
51
|
+
assert get.call_count == 2
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@pytest.mark.parametrize("stage", ["raise_for_status", "json"])
|
|
56
|
+
def test_response_errors_close_request_and_use_fallback(registry, stage):
|
|
57
|
+
_, _, response = registry
|
|
58
|
+
error = requests.HTTPError() if stage == "raise_for_status" else ValueError()
|
|
59
|
+
getattr(response, stage).side_effect = error
|
|
60
|
+
assert version.codex_client_version() == version.CLIENT_VERSION
|
|
61
|
+
response.__exit__.assert_called_once()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_refresh_failure_keeps_last_success(registry):
|
|
65
|
+
clock, get, _ = registry
|
|
66
|
+
assert version.codex_client_version() == "0.170.0"
|
|
67
|
+
clock[0] += 86400
|
|
68
|
+
get.side_effect = requests.ConnectionError()
|
|
69
|
+
assert version.codex_client_version() == "0.170.0"
|
|
70
|
+
assert version.codex_client_version() == "0.170.0"
|
|
71
|
+
assert get.call_count == 2
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@pytest.mark.parametrize("payload", [
|
|
75
|
+
None, [], {}, {"version": 123}, {"version": "0.170.0-beta.1"},
|
|
76
|
+
{"version": "garbage"}, {"version": "0.170.0\n"},
|
|
77
|
+
])
|
|
78
|
+
def test_invalid_registry_metadata_uses_fallback(registry, payload):
|
|
79
|
+
_, _, response = registry
|
|
80
|
+
response.json.return_value = payload
|
|
81
|
+
assert version.codex_client_version() == version.CLIENT_VERSION
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_concurrent_clients_share_one_lookup(registry):
|
|
85
|
+
_, get, _ = registry
|
|
86
|
+
with ThreadPoolExecutor(max_workers=8) as pool:
|
|
87
|
+
assert list(pool.map(lambda _: version.codex_client_version(), range(16))) == ["0.170.0"] * 16
|
|
88
|
+
assert get.call_count == 1
|
|
@@ -5,6 +5,12 @@ import pytest
|
|
|
5
5
|
from pydantic import BaseModel
|
|
6
6
|
|
|
7
7
|
from codex_backend_sdk import OpenAI, ParsedResponse, Response
|
|
8
|
+
from codex_backend_sdk.resources import models as models_resource
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture(autouse=True)
|
|
12
|
+
def stable_catalog_version(monkeypatch):
|
|
13
|
+
monkeypatch.setattr(models_resource, "codex_client_version", lambda: "0.163.0")
|
|
8
14
|
|
|
9
15
|
|
|
10
16
|
class FakeSSE:
|
|
@@ -363,12 +369,33 @@ def test_models_resource_forwards_official_request_options():
|
|
|
363
369
|
|
|
364
370
|
assert client.gets[1] == (
|
|
365
371
|
"/models",
|
|
366
|
-
{"client_version": "0.
|
|
372
|
+
{"client_version": "0.163.0", "preview": "1"},
|
|
367
373
|
{"x-test": "header"},
|
|
368
374
|
19,
|
|
369
375
|
)
|
|
370
376
|
|
|
371
377
|
|
|
378
|
+
|
|
379
|
+
def test_explicit_catalog_version_skips_discovery_and_does_not_replace_default_cache(monkeypatch):
|
|
380
|
+
client = FakeClient()
|
|
381
|
+
default = client.models.list()
|
|
382
|
+
def unexpected_lookup():
|
|
383
|
+
raise AssertionError("Explicit version must not contact registry")
|
|
384
|
+
monkeypatch.setattr(models_resource, "codex_client_version", unexpected_lookup)
|
|
385
|
+
custom = client.models.list(extra_query={"client_version": "0.159.2"})
|
|
386
|
+
assert custom is not default
|
|
387
|
+
assert client.gets[-1][1] == {"client_version": "0.159.2"}
|
|
388
|
+
assert client.models.list() is default
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def test_forced_catalog_refresh_resolves_version_again(monkeypatch):
|
|
392
|
+
client = FakeClient()
|
|
393
|
+
client.models.list()
|
|
394
|
+
monkeypatch.setattr(models_resource, "codex_client_version", lambda: "0.171.0")
|
|
395
|
+
client.models.list(force_refresh=True)
|
|
396
|
+
assert client.gets[-1][1] == {"client_version": "0.171.0"}
|
|
397
|
+
|
|
398
|
+
|
|
372
399
|
def test_responses_compact_sends_shared_request_fields():
|
|
373
400
|
client = FakeClient()
|
|
374
401
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/chatgpt_apps.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/chatgpt_plugins.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/codex_web_search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/openai_oauth.py
RENAMED
|
File without changes
|
|
File without changes
|
{codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/remote_control.py
RENAMED
|
File without changes
|
{codex_backend_sdk-0.5.6 → codex_backend_sdk-0.5.7}/codex_backend_sdk/resources/responses.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|