codex-as-api 0.8.1__tar.gz → 0.8.2__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.8.1 → codex_as_api-0.8.2}/PKG-INFO +1 -1
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/RELEASES.md +16 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/pyproject.toml +1 -1
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/src/codex_as_api/__init__.py +1 -1
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/src/codex_as_api/anthropic_adapter.py +12 -2
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/src/codex_as_api/server.py +1 -1
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/tests/test_anthropic_adapter.py +34 -1
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/tests/test_provider.py +2 -2
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/.gitignore +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/LICENSE +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/README.md +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/THIRD_PARTY_NOTICES.md +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/config/codex-upstream-contract.json +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/config/o200k_base.tiktoken +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/requirements.txt +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/rust/THIRD_PARTY_NOTICES.md +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/scripts/check_no_static_model_catalog.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/scripts/check_package_versions.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/scripts/check_registry_release.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/scripts/check_release_artifacts.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/scripts/generate_rust_third_party_notices.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/scripts/package_rust_release.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/src/codex_as_api/auth.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/src/codex_as_api/codex_config.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/src/codex_as_api/messages.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/src/codex_as_api/model_capabilities.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/src/codex_as_api/o200k_tokenizer.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/src/codex_as_api/protocol.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/src/codex_as_api/provider.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/src/codex_as_api/strict_json.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/tests/__init__.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/tests/conftest.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/tests/fixtures/o200k_base_encode_ordinary.json +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/tests/test_auth.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/tests/test_codex_config.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/tests/test_messages.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/tests/test_model_capabilities.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/tests/test_no_static_model_catalog.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/tests/test_o200k_tokenizer.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/tests/test_package_versions.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/tests/test_protocol.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/tests/test_registry_release.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/tests/test_release_artifacts.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/tests/test_rust_third_party_notices.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/tests/test_server.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/tests/test_strict_json.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.2}/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.8.
|
|
3
|
+
Version: 0.8.2
|
|
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.8.2
|
|
4
|
+
|
|
5
|
+
### Hotfix: disjoint Anthropic cache usage (all runtimes)
|
|
6
|
+
|
|
7
|
+
- Correct Anthropic Messages usage in streaming and non-streaming responses:
|
|
8
|
+
`input_tokens` now excludes both cache reads and reported cache creation,
|
|
9
|
+
which remain in their separate `cache_read_input_tokens` and
|
|
10
|
+
`cache_creation_input_tokens` counters. Previously, consumers adding those
|
|
11
|
+
counters counted cached input twice.
|
|
12
|
+
- Keep OpenAI/Responses totals unchanged. Preserve unknown cache counters as
|
|
13
|
+
null; subtract only reported cache buckets. Reject impossible cache sums
|
|
14
|
+
exceeding the upstream input total instead of emitting negative/clamped usage.
|
|
15
|
+
- Add no-cache, partial/full-cache, cache-write, invalid-total and HTTP pipeline
|
|
16
|
+
regressions across TypeScript, Python and Rust. Example: 100 total input,
|
|
17
|
+
50 cache read and 25 cache write becomes 25 uncached + 50 read + 25 write.
|
|
18
|
+
|
|
3
19
|
## v0.8.1
|
|
4
20
|
|
|
5
21
|
### Explicit Ultra Fast selection (all runtimes)
|
|
@@ -718,13 +718,23 @@ def _reject_unrepresentable_response_events(raw: dict | None) -> None:
|
|
|
718
718
|
)
|
|
719
719
|
|
|
720
720
|
|
|
721
|
+
def _uncached_input_tokens(total: int, cache_read: int | None, cache_write: int | None) -> int:
|
|
722
|
+
# OpenAI totals include cache buckets; Anthropic exposes disjoint counters.
|
|
723
|
+
cached = (cache_read or 0) + (cache_write or 0)
|
|
724
|
+
if cached > total:
|
|
725
|
+
raise ChatGPTOAuthProtocolError("usage cache tokens must not exceed input_tokens")
|
|
726
|
+
return total - cached
|
|
727
|
+
|
|
728
|
+
|
|
721
729
|
def _anthropic_usage_from_internal(usage: Usage) -> dict[str, Any]:
|
|
722
730
|
return {
|
|
723
731
|
"cache_creation": None,
|
|
724
732
|
"cache_creation_input_tokens": usage.cache_write_tokens,
|
|
725
733
|
"cache_read_input_tokens": usage.cached_tokens,
|
|
726
734
|
"inference_geo": None,
|
|
727
|
-
"input_tokens":
|
|
735
|
+
"input_tokens": _uncached_input_tokens(
|
|
736
|
+
usage.prompt_tokens, usage.cached_tokens, usage.cache_write_tokens
|
|
737
|
+
),
|
|
728
738
|
"iterations": None,
|
|
729
739
|
"output_tokens": usage.completion_tokens,
|
|
730
740
|
"server_tool_use": None,
|
|
@@ -910,7 +920,7 @@ def _anthropic_usage_from_provider(usage: Any) -> dict[str, Any]:
|
|
|
910
920
|
out: dict[str, Any] = {
|
|
911
921
|
"cache_creation_input_tokens": cache_write,
|
|
912
922
|
"cache_read_input_tokens": cache_read,
|
|
913
|
-
"input_tokens": input_tokens,
|
|
923
|
+
"input_tokens": _uncached_input_tokens(input_tokens, cache_read, cache_write),
|
|
914
924
|
"iterations": None,
|
|
915
925
|
"output_tokens": output_tokens,
|
|
916
926
|
"server_tool_use": server_tool_use,
|
|
@@ -1206,6 +1206,23 @@ class TestInternalResponseToAnthropic:
|
|
|
1206
1206
|
result = internal_response_to_anthropic(resp, "m", "msg_1")
|
|
1207
1207
|
assert result["usage"]["cache_read_input_tokens"] == 50
|
|
1208
1208
|
assert result["usage"]["cache_creation_input_tokens"] == 25
|
|
1209
|
+
assert result["usage"]["input_tokens"] == 25
|
|
1210
|
+
|
|
1211
|
+
@pytest.mark.parametrize("read,write", [(0, 0), (40, 0), (100, 0), (0, 100), (40, 60), (101, 0), (60, 41)])
|
|
1212
|
+
def test_disjoint_cache_buckets(self, read, write):
|
|
1213
|
+
source = Usage(prompt_tokens=100, completion_tokens=7, total_tokens=107,
|
|
1214
|
+
cached_tokens=read, cache_write_tokens=write)
|
|
1215
|
+
response = AssistantResponse(content="hi", tool_calls=(), finish_reason="stop",
|
|
1216
|
+
usage=source, reasoning_content=None, raw=None)
|
|
1217
|
+
if read + write > 100:
|
|
1218
|
+
with pytest.raises(ChatGPTOAuthProtocolError, match="cache tokens must not exceed"):
|
|
1219
|
+
internal_response_to_anthropic(response, "m", "msg")
|
|
1220
|
+
else:
|
|
1221
|
+
usage = internal_response_to_anthropic(response, "m", "msg")["usage"]
|
|
1222
|
+
assert usage["input_tokens"] == 100 - read - write
|
|
1223
|
+
buckets = ("input_tokens", "cache_read_input_tokens", "cache_creation_input_tokens")
|
|
1224
|
+
assert sum(usage[k] for k in buckets) == 100
|
|
1225
|
+
assert source.prompt_tokens == 100
|
|
1209
1226
|
|
|
1210
1227
|
def test_web_search_provider_output_is_rejected(self):
|
|
1211
1228
|
resp = AssistantResponse(
|
|
@@ -1460,7 +1477,7 @@ class TestAnthropicStreamAdapter:
|
|
|
1460
1477
|
assert msg_delta["usage"] == {
|
|
1461
1478
|
"cache_creation_input_tokens": 11,
|
|
1462
1479
|
"cache_read_input_tokens": 13,
|
|
1463
|
-
"input_tokens":
|
|
1480
|
+
"input_tokens": 99,
|
|
1464
1481
|
"iterations": None,
|
|
1465
1482
|
"output_tokens": 7,
|
|
1466
1483
|
"server_tool_use": {"web_search_requests": 2, "web_fetch_requests": 1},
|
|
@@ -1516,6 +1533,22 @@ class TestAnthropicStreamAdapter:
|
|
|
1516
1533
|
with pytest.raises(ChatGPTOAuthProtocolError, match="provider (finish )?usage"):
|
|
1517
1534
|
self._collect_events(events)
|
|
1518
1535
|
|
|
1536
|
+
@pytest.mark.parametrize("read,write", [(0, 0), (40, 0), (100, 0), (0, 100), (40, 60), (101, 0), (60, 41)])
|
|
1537
|
+
def test_stream_disjoint_cache_buckets(self, read, write):
|
|
1538
|
+
source = {"input_tokens": 100, "output_tokens": 7, "total_tokens": 107,
|
|
1539
|
+
"input_tokens_details": {"cached_tokens": read, "cache_write_tokens": write}}
|
|
1540
|
+
events = [{"type": "finish", "finish_reason": "stop", "usage": source}]
|
|
1541
|
+
if read + write > 100:
|
|
1542
|
+
with pytest.raises(ChatGPTOAuthProtocolError, match="cache tokens must not exceed"):
|
|
1543
|
+
self._collect_events(events)
|
|
1544
|
+
else:
|
|
1545
|
+
result = self._collect_events(events)
|
|
1546
|
+
usage = next(e["usage"] for e in result if e["type"] == "message_delta")
|
|
1547
|
+
assert usage["input_tokens"] == 100 - read - write
|
|
1548
|
+
buckets = ("input_tokens", "cache_read_input_tokens", "cache_creation_input_tokens")
|
|
1549
|
+
assert sum(usage[k] for k in buckets) == 100
|
|
1550
|
+
assert source["input_tokens"] == 100
|
|
1551
|
+
|
|
1519
1552
|
def test_maps_responses_cache_write_details_into_message_delta(self):
|
|
1520
1553
|
result = self._collect_events(
|
|
1521
1554
|
[
|
|
@@ -2158,7 +2158,7 @@ def test_codex_cli_headers_include_official_originator_and_versioned_user_agent(
|
|
|
2158
2158
|
|
|
2159
2159
|
assert headers["originator"] == "codex_cli_rs"
|
|
2160
2160
|
assert headers["User-Agent"].startswith("codex_cli_rs/0.153.3 (")
|
|
2161
|
-
assert headers["User-Agent"].endswith(") codex-as-api/0.8.
|
|
2161
|
+
assert headers["User-Agent"].endswith(") codex-as-api/0.8.2")
|
|
2162
2162
|
|
|
2163
2163
|
|
|
2164
2164
|
def test_codex_cli_headers_reject_unpinned_version():
|
|
@@ -2881,7 +2881,7 @@ def test_provider_headers_include_codex_cli_headers(auth_json_factory):
|
|
|
2881
2881
|
|
|
2882
2882
|
assert headers["originator"] == "codex_cli_rs"
|
|
2883
2883
|
assert headers["User-Agent"].startswith("codex_cli_rs/0.153.3 (")
|
|
2884
|
-
assert headers["User-Agent"].endswith(") codex-as-api/0.8.
|
|
2884
|
+
assert headers["User-Agent"].endswith(") codex-as-api/0.8.2")
|
|
2885
2885
|
assert headers["Authorization"].startswith("Bearer ")
|
|
2886
2886
|
|
|
2887
2887
|
|
|
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
|