codex-as-api 0.9.2__tar.gz → 0.9.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_as_api-0.9.2 → codex_as_api-0.9.3}/PKG-INFO +1 -1
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/RELEASES.md +16 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/pyproject.toml +1 -1
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/src/codex_as_api/__init__.py +1 -1
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/src/codex_as_api/provider.py +25 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/src/codex_as_api/server.py +1 -1
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/tests/test_provider.py +62 -3
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/tests/test_server.py +1 -1
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/.gitignore +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/LICENSE +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/README.md +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/THIRD_PARTY_NOTICES.md +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/config/codex-upstream-contract.json +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/config/o200k_base.tiktoken +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/config/verified-hidden-models.json +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/requirements.txt +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/rust/THIRD_PARTY_NOTICES.md +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/scripts/check_no_static_model_catalog.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/scripts/check_package_versions.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/scripts/check_registry_release.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/scripts/check_release_artifacts.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/scripts/generate_rust_third_party_notices.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/scripts/package_rust_release.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/src/codex_as_api/anthropic_adapter.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/src/codex_as_api/auth.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/src/codex_as_api/codex_config.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/src/codex_as_api/codex_version.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/src/codex_as_api/messages.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/src/codex_as_api/model_capabilities.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/src/codex_as_api/o200k_tokenizer.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/src/codex_as_api/protocol.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/src/codex_as_api/reasoning_signature.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/src/codex_as_api/strict_json.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/tests/__init__.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/tests/conftest.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/tests/fixtures/o200k_base_encode_ordinary.json +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/tests/test_anthropic_adapter.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/tests/test_auth.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/tests/test_codex_config.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/tests/test_codex_version.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/tests/test_messages.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/tests/test_model_capabilities.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/tests/test_no_static_model_catalog.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/tests/test_o200k_tokenizer.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/tests/test_package_versions.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/tests/test_protocol.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/tests/test_registry_release.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/tests/test_release_artifacts.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/tests/test_rust_third_party_notices.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/tests/test_strict_json.py +0 -0
- {codex_as_api-0.9.2 → codex_as_api-0.9.3}/tests/test_ultrafast.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: codex-as-api
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.3
|
|
4
4
|
Summary: Use ChatGPT/Codex OAuth as a local OpenAI-compatible API server.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Eunho-J/codex-as-api
|
|
6
6
|
Project-URL: Repository, https://github.com/Eunho-J/codex-as-api
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Release Notes
|
|
2
2
|
|
|
3
|
+
## v0.9.3
|
|
4
|
+
|
|
5
|
+
### Prompt cache routing headers
|
|
6
|
+
|
|
7
|
+
- Send the session headers the official Codex CLI sends on every Responses
|
|
8
|
+
request: `session-id`, `thread-id` and `x-client-request-id`, derived from
|
|
9
|
+
`client_metadata.session_id` / `thread_id` or, failing those, from
|
|
10
|
+
`prompt_cache_key`. Applies to streamed `/responses` and `/responses/compact`
|
|
11
|
+
requests in Python, TypeScript and Rust. Values containing CR or LF are not sent.
|
|
12
|
+
- Measured against the ChatGPT Codex backend: an identical 7k-token prefix sent
|
|
13
|
+
with `prompt_cache_key` alone read 0 cached tokens on every request; with the
|
|
14
|
+
headers, repeated requests read 7,296 cached tokens. The official Codex CLI on
|
|
15
|
+
the same account read ~30k cached tokens per turn.
|
|
16
|
+
- No change when a request carries neither `prompt_cache_key` nor
|
|
17
|
+
`client_metadata.session_id`.
|
|
18
|
+
|
|
3
19
|
## v0.9.2
|
|
4
20
|
|
|
5
21
|
### TypeScript version lookup deadline
|
|
@@ -409,6 +409,29 @@ def _redact_failure_event(
|
|
|
409
409
|
return value
|
|
410
410
|
|
|
411
411
|
|
|
412
|
+
def codex_session_headers(payload: dict[str, Any]) -> dict[str, str]:
|
|
413
|
+
"""Session headers the official Codex CLI sends on every Responses request.
|
|
414
|
+
|
|
415
|
+
The ChatGPT Codex backend does not reuse its prompt cache from
|
|
416
|
+
``prompt_cache_key`` alone: measured 2026-10-27, an identical 7k-token prefix
|
|
417
|
+
read 0 cached tokens on every request without these headers and 7,296 with them.
|
|
418
|
+
"""
|
|
419
|
+
metadata = payload.get("client_metadata")
|
|
420
|
+
if not isinstance(metadata, dict):
|
|
421
|
+
metadata = {}
|
|
422
|
+
|
|
423
|
+
def non_empty(value: Any) -> str | None:
|
|
424
|
+
return value if isinstance(value, str) and value.strip() else None
|
|
425
|
+
|
|
426
|
+
session_id = non_empty(metadata.get("session_id")) or non_empty(payload.get("prompt_cache_key"))
|
|
427
|
+
if session_id is None:
|
|
428
|
+
return {}
|
|
429
|
+
thread_id = non_empty(metadata.get("thread_id")) or session_id
|
|
430
|
+
if any(ch in value for value in (session_id, thread_id) for ch in ("\r", "\n")):
|
|
431
|
+
return {}
|
|
432
|
+
return {"session-id": session_id, "thread-id": thread_id, "x-client-request-id": thread_id}
|
|
433
|
+
|
|
434
|
+
|
|
412
435
|
class ChatGPTOAuthProvider:
|
|
413
436
|
name: str = "chatgpt_oauth"
|
|
414
437
|
provider_namespace: str = "agent.provider.chatgpt_oauth"
|
|
@@ -1558,6 +1581,7 @@ class ChatGPTOAuthProvider:
|
|
|
1558
1581
|
_require_catalog_account(token.account_id, catalog_key)
|
|
1559
1582
|
headers = self._headers(token, client_version=resolved_client_version)
|
|
1560
1583
|
headers["Accept"] = "text/event-stream"
|
|
1584
|
+
headers.update(codex_session_headers(payload))
|
|
1561
1585
|
if extra_headers:
|
|
1562
1586
|
headers.update(extra_headers)
|
|
1563
1587
|
token_values = (token.access_token, token.refresh_token, token.id_token, token.account_id)
|
|
@@ -1649,6 +1673,7 @@ class ChatGPTOAuthProvider:
|
|
|
1649
1673
|
token = token_for_request(self.auth_json_path)
|
|
1650
1674
|
_require_catalog_account(token.account_id, catalog_key)
|
|
1651
1675
|
headers = self._headers(token, client_version=resolved_client_version)
|
|
1676
|
+
headers.update(codex_session_headers(payload))
|
|
1652
1677
|
if extra_headers:
|
|
1653
1678
|
headers.update(extra_headers)
|
|
1654
1679
|
token_values = (token.access_token, token.refresh_token, token.id_token, token.account_id)
|
|
@@ -53,6 +53,7 @@ from codex_as_api.provider import (
|
|
|
53
53
|
_validate_response_event,
|
|
54
54
|
_web_search_event_from_response_item,
|
|
55
55
|
codex_cli_headers_for_version,
|
|
56
|
+
codex_session_headers,
|
|
56
57
|
resolve_codex_cli_version,
|
|
57
58
|
resolve_model_reasoning_effort,
|
|
58
59
|
)
|
|
@@ -2192,7 +2193,7 @@ def test_codex_cli_headers_include_official_originator_and_versioned_user_agent(
|
|
|
2192
2193
|
|
|
2193
2194
|
assert headers["originator"] == "codex_cli_rs"
|
|
2194
2195
|
assert headers["User-Agent"].startswith("codex_cli_rs/0.164.0 (")
|
|
2195
|
-
assert headers["User-Agent"].endswith(") codex-as-api/0.9.
|
|
2196
|
+
assert headers["User-Agent"].endswith(") codex-as-api/0.9.3")
|
|
2196
2197
|
|
|
2197
2198
|
|
|
2198
2199
|
def test_codex_cli_headers_reject_unpinned_version():
|
|
@@ -2932,7 +2933,7 @@ def test_provider_headers_include_codex_cli_headers(auth_json_factory):
|
|
|
2932
2933
|
|
|
2933
2934
|
assert headers["originator"] == "codex_cli_rs"
|
|
2934
2935
|
assert headers["User-Agent"].startswith("codex_cli_rs/0.164.0 (")
|
|
2935
|
-
assert headers["User-Agent"].endswith(") codex-as-api/0.9.
|
|
2936
|
+
assert headers["User-Agent"].endswith(") codex-as-api/0.9.3")
|
|
2936
2937
|
assert headers["Authorization"].startswith("Bearer ")
|
|
2937
2938
|
|
|
2938
2939
|
|
|
@@ -4533,7 +4534,7 @@ def test_provider_injects_dynamic_npm_version_across_catalog_responses_and_401_r
|
|
|
4533
4534
|
for full_url, headers in observed_requests:
|
|
4534
4535
|
assert full_url == "https://example.test/codex/models?client_version=0.175.2"
|
|
4535
4536
|
assert headers["user-agent"].startswith("codex_cli_rs/0.175.2 (")
|
|
4536
|
-
assert headers["user-agent"].endswith(") codex-as-api/0.9.
|
|
4537
|
+
assert headers["user-agent"].endswith(") codex-as-api/0.9.3")
|
|
4537
4538
|
|
|
4538
4539
|
|
|
4539
4540
|
def test_provider_cold_version_resolver_failure_raises_typed_503(auth_json_factory, monkeypatch):
|
|
@@ -4682,3 +4683,61 @@ def test_responses_payload_replays_all_25_compatible_model_pairs(
|
|
|
4682
4683
|
"encrypted_content": "enc_payload",
|
|
4683
4684
|
"summary": [{"type": "summary_text", "text": "summary"}],
|
|
4684
4685
|
}
|
|
4686
|
+
|
|
4687
|
+
|
|
4688
|
+
def test_codex_session_headers_derive_from_cache_key_and_metadata():
|
|
4689
|
+
assert codex_session_headers({"prompt_cache_key": "key-1"}) == {
|
|
4690
|
+
"session-id": "key-1",
|
|
4691
|
+
"thread-id": "key-1",
|
|
4692
|
+
"x-client-request-id": "key-1",
|
|
4693
|
+
}
|
|
4694
|
+
assert codex_session_headers(
|
|
4695
|
+
{"prompt_cache_key": "key-1", "client_metadata": {"session_id": "s-1", "thread_id": "t-1"}}
|
|
4696
|
+
) == {"session-id": "s-1", "thread-id": "t-1", "x-client-request-id": "t-1"}
|
|
4697
|
+
assert codex_session_headers({"client_metadata": {"session_id": "s-2"}}) == {
|
|
4698
|
+
"session-id": "s-2",
|
|
4699
|
+
"thread-id": "s-2",
|
|
4700
|
+
"x-client-request-id": "s-2",
|
|
4701
|
+
}
|
|
4702
|
+
assert codex_session_headers({}) == {}
|
|
4703
|
+
assert codex_session_headers({"prompt_cache_key": " "}) == {}
|
|
4704
|
+
assert codex_session_headers({"prompt_cache_key": "a\r\nInjected: 1"}) == {}
|
|
4705
|
+
assert codex_session_headers({"client_metadata": {"session_id": "s", "thread_id": "t\nx"}}) == {}
|
|
4706
|
+
|
|
4707
|
+
|
|
4708
|
+
def test_responses_requests_carry_codex_session_headers_only_with_a_key(monkeypatch):
|
|
4709
|
+
seen: list[tuple[str, dict[str, str]]] = []
|
|
4710
|
+
|
|
4711
|
+
class Handler(BaseHTTPRequestHandler):
|
|
4712
|
+
def do_POST(self) -> None: # noqa: N802
|
|
4713
|
+
seen.append((self.path, {k.lower(): v for k, v in self.headers.items()}))
|
|
4714
|
+
self.send_response(400)
|
|
4715
|
+
self.end_headers()
|
|
4716
|
+
|
|
4717
|
+
def log_message(self, _format, *args): # noqa: ANN001
|
|
4718
|
+
del args
|
|
4719
|
+
|
|
4720
|
+
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
|
4721
|
+
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
4722
|
+
thread.start()
|
|
4723
|
+
try:
|
|
4724
|
+
token = _test_token()
|
|
4725
|
+
provider = ChatGPTOAuthProvider(base_url=f"http://127.0.0.1:{server.server_port}")
|
|
4726
|
+
monkeypatch.setattr(provider_module, "token_for_request", lambda _path: token)
|
|
4727
|
+
with pytest.raises(ChatGPTOAuthUpstreamError):
|
|
4728
|
+
list(provider._post_sse("/responses", {"prompt_cache_key": "cache-key-7"})) # noqa: SLF001
|
|
4729
|
+
with pytest.raises(ChatGPTOAuthUpstreamError):
|
|
4730
|
+
provider._request_json("/responses/compact", {"prompt_cache_key": "cache-key-7"}) # noqa: SLF001
|
|
4731
|
+
with pytest.raises(ChatGPTOAuthUpstreamError):
|
|
4732
|
+
list(provider._post_sse("/responses", {})) # noqa: SLF001
|
|
4733
|
+
finally:
|
|
4734
|
+
server.shutdown()
|
|
4735
|
+
server.server_close()
|
|
4736
|
+
thread.join(timeout=2)
|
|
4737
|
+
assert [path for path, _ in seen] == ["/responses", "/responses/compact", "/responses"]
|
|
4738
|
+
for _, headers in seen[:2]:
|
|
4739
|
+
assert headers["session-id"] == "cache-key-7"
|
|
4740
|
+
assert headers["thread-id"] == "cache-key-7"
|
|
4741
|
+
assert headers["x-client-request-id"] == "cache-key-7"
|
|
4742
|
+
assert "session-id" not in seen[2][1]
|
|
4743
|
+
assert "thread-id" not in seen[2][1]
|
|
@@ -6648,7 +6648,7 @@ def test_dynamic_npm_version_resolver_updates_models_query_user_agent_and_cold_f
|
|
|
6648
6648
|
responses_req_1 = recording_backend.requests.get(timeout=1)
|
|
6649
6649
|
assert catalog_req_1["path"] == "/models?client_version=0.175.2"
|
|
6650
6650
|
assert catalog_req_1["headers"]["user-agent"].startswith("codex_cli_rs/0.175.2 (")
|
|
6651
|
-
assert catalog_req_1["headers"]["user-agent"].endswith(") codex-as-api/0.9.
|
|
6651
|
+
assert catalog_req_1["headers"]["user-agent"].endswith(") codex-as-api/0.9.3")
|
|
6652
6652
|
assert responses_req_1["headers"]["user-agent"].startswith("codex_cli_rs/0.175.2 (")
|
|
6653
6653
|
|
|
6654
6654
|
active_version = "0.176.0"
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|