codex-as-api 0.8.1__tar.gz → 0.8.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.8.1 → codex_as_api-0.8.3}/PKG-INFO +15 -22
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/README.md +14 -21
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/RELEASES.md +45 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/pyproject.toml +1 -1
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/src/codex_as_api/__init__.py +1 -1
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/src/codex_as_api/anthropic_adapter.py +123 -14
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/src/codex_as_api/model_capabilities.py +6 -5
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/src/codex_as_api/provider.py +62 -6
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/src/codex_as_api/server.py +71 -63
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/tests/conftest.py +1 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/tests/test_anthropic_adapter.py +306 -63
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/tests/test_auth.py +64 -32
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/tests/test_codex_config.py +15 -7
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/tests/test_messages.py +14 -7
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/tests/test_model_capabilities.py +19 -37
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/tests/test_package_versions.py +8 -4
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/tests/test_protocol.py +18 -9
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/tests/test_provider.py +167 -83
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/tests/test_release_artifacts.py +14 -10
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/tests/test_rust_third_party_notices.py +8 -4
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/tests/test_server.py +337 -55
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/.gitignore +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/LICENSE +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/THIRD_PARTY_NOTICES.md +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/config/codex-upstream-contract.json +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/config/o200k_base.tiktoken +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/requirements.txt +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/rust/THIRD_PARTY_NOTICES.md +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/scripts/check_no_static_model_catalog.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/scripts/check_package_versions.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/scripts/check_registry_release.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/scripts/check_release_artifacts.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/scripts/generate_rust_third_party_notices.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/scripts/package_rust_release.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/src/codex_as_api/auth.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/src/codex_as_api/codex_config.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/src/codex_as_api/messages.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/src/codex_as_api/o200k_tokenizer.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/src/codex_as_api/protocol.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/src/codex_as_api/strict_json.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/tests/__init__.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/tests/fixtures/o200k_base_encode_ordinary.json +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/tests/test_no_static_model_catalog.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/tests/test_o200k_tokenizer.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/tests/test_registry_release.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.3}/tests/test_strict_json.py +0 -0
- {codex_as_api-0.8.1 → codex_as_api-0.8.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.8.
|
|
3
|
+
Version: 0.8.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
|
|
@@ -284,11 +284,9 @@ it; continuation uses the complete provider Response items saved behind
|
|
|
284
284
|
|
|
285
285
|
### `POST /v1/messages`
|
|
286
286
|
|
|
287
|
-
|
|
287
|
+
Anthropic Messages compatibility endpoint for any client, including Claude Code and Scheherazade. It supports streaming (`stream: true`) and non-streaming without a client-identification header. A live Codex model ID routes directly. A `claude-*` facade ID uses the configured Codex backend model, or the authenticated catalog default when no backend model is configured. Other unknown IDs fail. The response preserves the client-supplied model name.
|
|
288
288
|
|
|
289
|
-
|
|
290
|
-
and strips the non-forwarded `max_tokens` and `cache_control` compatibility
|
|
291
|
-
fields only when one valid `x-claude-code-session-id` header is present.
|
|
289
|
+
`max_tokens` is a required positive compatibility hint: the private Codex OAuth transport does not expose a matching output-token cap. Valid ephemeral `cache_control` hints are accepted and stripped for every client; their TTL and breakpoint semantics are not forwarded. An optional `x-claude-code-session-id` provides stable hashed cache affinity, not permission to use the endpoint. `metadata.user_id` is accepted as client metadata and is not forwarded.
|
|
292
290
|
|
|
293
291
|
```bash
|
|
294
292
|
curl http://localhost:18080/v1/messages \
|
|
@@ -325,7 +323,7 @@ curl -N http://localhost:18080/v1/messages \
|
|
|
325
323
|
}'
|
|
326
324
|
```
|
|
327
325
|
|
|
328
|
-
Codex reasoning summaries are
|
|
326
|
+
With `thinking.display: "summarized"` (the enabled/adaptive default), real Codex reasoning summaries are returned as unsigned Anthropic `thinking` blocks and streamed `thinking_delta` content, alongside the existing `codex_reasoning` / `codex_reasoning_delta` extensions. `display: "omitted"` and disabled thinking suppress reasoning output. No Anthropic signature is fabricated. Valid thinking blocks in replayed assistant history are checked and stripped before Codex input; ordinary text and tool calls are preserved.
|
|
329
327
|
|
|
330
328
|
Hosted Anthropic `web_search` declarations are rejected with HTTP 400 on
|
|
331
329
|
Messages, token-counting, and compact requests. OpenAI Responses cannot supply
|
|
@@ -345,10 +343,7 @@ the completion is nonterminal or final usage is absent; a stream that has
|
|
|
345
343
|
already started emits an Anthropic `error` event and never emits a successful
|
|
346
344
|
`message_stop`. The final response includes every required usage field, using
|
|
347
345
|
`null` only where the Anthropic type permits it, plus required container,
|
|
348
|
-
citation, context-management, and direct-caller fields.
|
|
349
|
-
Code's realtime behavior, `message_start` is sent immediately without guessed
|
|
350
|
-
input-token counts; this narrow stream-start shape is Claude Code compatibility
|
|
351
|
-
behavior, not generic Anthropic API parity.
|
|
346
|
+
citation, context-management, and direct-caller fields. `message_start` is sent immediately with numeric zero counters in `message.usage`; these are provisional, not measured counts. The final `message_delta.usage` replaces them with authoritative cumulative totals, including disjoint cache buckets.
|
|
352
347
|
|
|
353
348
|
Generated `tool_use` blocks with `caller: {"type":"direct"}` and empty
|
|
354
349
|
assistant content arrays can be replayed through the request adapter. Malformed,
|
|
@@ -364,9 +359,9 @@ different jobs:
|
|
|
364
359
|
| `client_metadata.session_id` | Forwarded to Codex, used as the default `prompt_cache_key`, and required when Codex metadata mode is enabled | Not used or forwarded |
|
|
365
360
|
| `client_metadata.thread_id` | Codex metadata thread identity; defaults to `session_id` for a root request and preserves an explicit child thread | Not used or synthesized |
|
|
366
361
|
| `prompt_cache_key` | Explicit value wins; otherwise a non-empty `client_metadata.session_id` is used; otherwise omitted | Optional proxy extension; explicit value wins, otherwise the Claude Code session header is hashed |
|
|
367
|
-
| `x-claude-code-session-id` | Not used |
|
|
362
|
+
| `x-claude-code-session-id` | Not used | Optional cache-affinity header; its exact value supplies a hashed default key |
|
|
368
363
|
| `previous_response_id` | Resolves a known entry from the 256-chain process-local history and replays full input/output over HTTP; never forwarded | Non-null values return Anthropic-style HTTP 400 because normal Messages is stateless |
|
|
369
|
-
| `cache_control` | Not an OpenAI Chat control | Accepted
|
|
364
|
+
| `cache_control` | Not an OpenAI Chat control | Accepted for every client, validated, then stripped before Codex transport |
|
|
370
365
|
|
|
371
366
|
Anthropic `cache_control` is a cache-boundary annotation, not an identifier.
|
|
372
367
|
This proxy accepts only `{"type":"ephemeral"}` with optional `ttl: "5m"` or
|
|
@@ -378,15 +373,13 @@ responses do not expose the Chat-only `response_id`. The custom
|
|
|
378
373
|
`previous_response_id` support.
|
|
379
374
|
|
|
380
375
|
The Anthropic type permits `cache_control: null`; the proxy treats that exact
|
|
381
|
-
value as omission. A non-null cache-control object is validated in full
|
|
382
|
-
accepted only on a Claude Code request.
|
|
376
|
+
value as omission. A non-null cache-control object is validated in full for every client.
|
|
383
377
|
|
|
384
378
|
### `POST /v1/messages/count_tokens`
|
|
385
379
|
|
|
386
|
-
Anthropic-compatible token counting helper. Codex OAuth does not expose a count-only endpoint equivalent to Anthropic's native API, so this route counts text locally and returns context-window metadata for the effective backend model. Normalized model-visible messages, tool calls, tool-result metadata, reasoning, and tool schemas are counted once. Request-envelope fields and the
|
|
380
|
+
Anthropic-compatible token counting helper. Codex OAuth does not expose a count-only endpoint equivalent to Anthropic's native API, so this route counts text locally and returns context-window metadata for the effective backend model. Normalized model-visible messages, tool calls, tool-result metadata, reasoning, and tool schemas are counted once. Request-envelope fields and the `max_tokens` compatibility hint are excluded; other unsupported generation controls fail instead of becoming no-ops. Image inputs use a separate fixed estimate so inline base64 data is not counted as text.
|
|
387
381
|
|
|
388
|
-
`max_tokens` and `cache_control`
|
|
389
|
-
this route and require `x-claude-code-session-id` when present.
|
|
382
|
+
`max_tokens` and `cache_control` are optional client-neutral compatibility hints on this route; no Claude Code header is required.
|
|
390
383
|
|
|
391
384
|
Text estimation uses a bundled port of official `tiktoken` `o200k_base` `encode_ordinary`: the same Unicode pre-tokenization regex, byte-pair merge algorithm, and merge-rank data are implemented in Python, TypeScript, and Rust. It is a local estimator, not model metadata and not a substitute for a provider count API. The project does not depend on a `tiktoken` package or download encoding data at runtime. The last upstream synchronization check was **2026-07-14**, against [`tiktoken` 0.13.0 at `08a5f3b`](https://github.com/openai/tiktoken/tree/08a5f3b2c987ada4fc5aa1f16c643c203fa8acaa); the bundled rank file SHA-256 is `446a9538cb6c348e3516120d7c08b09f57c36495e2acfffe59a5bf8b0cfb1a2d`.
|
|
392
385
|
|
|
@@ -405,7 +398,7 @@ curl http://localhost:18080/v1/messages/count_tokens \
|
|
|
405
398
|
|
|
406
399
|
### `POST /v1/messages/compact`
|
|
407
400
|
|
|
408
|
-
Anthropic-compatible alias for remote conversation compaction. Accepts Anthropic Messages-shaped bodies and returns compacted checkpoint content. Live GPT model IDs select the matching backend model, and Claude Code effort and Fast Mode controls use the same mappings as `/v1/messages`. Its required non-forwarded `max_tokens` and any `cache_control` hints
|
|
401
|
+
Anthropic-compatible alias for remote conversation compaction. Accepts Anthropic Messages-shaped bodies and returns compacted checkpoint content. Live GPT model IDs select the matching backend model, and Claude Code effort and Fast Mode controls use the same mappings as `/v1/messages`. Its required non-forwarded `max_tokens` and any valid `cache_control` hints are accepted for every client; the optional Claude Code session header only supplies cache affinity.
|
|
409
402
|
|
|
410
403
|
### `POST /v1/images/generations`
|
|
411
404
|
|
|
@@ -545,7 +538,7 @@ Requests can instead use the Responses-shaped effort/context object:
|
|
|
545
538
|
- Responses Lite uses `all_turns` as the Codex wire default. An explicitly different context is rejected instead of silently overwritten; use `responses_lite: false` when the backend route supports classic Responses and another context is required.
|
|
546
539
|
- Remote compact keeps its existing private Codex `reasoning_effort` field but does not accept public `reasoning.mode` or `reasoning.context`.
|
|
547
540
|
|
|
548
|
-
Anthropic `thinking` values map as `enabled → high
|
|
541
|
+
Anthropic `thinking` values map as `enabled → high` and `adaptive → medium`. Disabled thinking selects `none` when available, otherwise the lowest supported catalog effort, and suppresses reasoning output; it does not claim to eliminate a model's internal reasoning. `output_config.effort` takes precedence over adaptive or enabled thinking and supports `low`, `medium`, `high`, `xhigh`, and `max`. Call-level disabled thinking takes precedence over ambient `output_config.effort` for auxiliary calls such as WebFetch. Explicit unsupported effort requests still fail. Hosted Anthropic WebSearch is rejected because its lifecycle cannot be translated losslessly. The supported reasoning context and verbosity extensions are also available on `/v1/messages`, image generation, and inspection requests; Pro, non-null public cache policy/breakpoints, and any supplied `safety_identifier` fail explicitly on the private Codex provider.
|
|
549
542
|
|
|
550
543
|
Anthropic's `JSONOutputFormat` carries a schema but no name. When Claude Code
|
|
551
544
|
sends that official shape, the proxy follows pinned Codex 0.153.3 and supplies
|
|
@@ -819,9 +812,9 @@ ANTHROPIC_BASE_URL=http://127.0.0.1:18080 ANTHROPIC_AUTH_TOKEN=unused \
|
|
|
819
812
|
claude --model gpt-5.6-luna --effort medium
|
|
820
813
|
```
|
|
821
814
|
|
|
822
|
-
Live Codex IDs are sent to the matching backend model. Built-in Claude/Fable/Opus/Sonnet/Haiku IDs are not Codex models, so recognized `claude-*` IDs
|
|
815
|
+
Live Codex IDs are sent to the matching backend model. Built-in Claude/Fable/Opus/Sonnet/Haiku IDs are not Codex models, so recognized `claude-*` IDs use the configured Codex model or authenticated catalog default; the proxy preserves the selected client name in the Anthropic response.
|
|
823
816
|
|
|
824
|
-
`ANTHROPIC_BASE_URL` applies to the whole Claude Code process. Therefore the built-in rows remain available in the picker, but they also pass through codex-as-api and use the
|
|
817
|
+
`ANTHROPIC_BASE_URL` applies to the whole Claude Code process. Therefore the built-in rows remain available in the picker, but they also pass through codex-as-api and use the configured or catalog-default Codex backend; the rows do not call Anthropic models in that process. To use an actual Anthropic model and GPT in parallel, keep the gateway variables out of global settings, start the GPT process with the inline command above, and start a second process normally:
|
|
825
818
|
|
|
826
819
|
```bash
|
|
827
820
|
claude --model sonnet
|
|
@@ -831,7 +824,7 @@ If the gateway variables are already in `~/.claude/settings.json`, shell-level `
|
|
|
831
824
|
|
|
832
825
|
Claude Code gateway discovery cannot expose raw `gpt-*` IDs because Claude Code filters discovered IDs to `claude*` and `anthropic*`. The official single custom-model option avoids a misleading facade ID. This integration is an Anthropic Messages-compatible bridge; Anthropic explicitly does not support routing Claude Code to non-Claude models through third-party gateways. See the official [model configuration](https://code.claude.com/docs/en/model-config), [gateway connection](https://code.claude.com/docs/en/llm-gateway-connect), and [gateway protocol](https://code.claude.com/docs/en/llm-gateway-protocol) references.
|
|
833
826
|
|
|
834
|
-
The
|
|
827
|
+
The bridge accepts Claude Code's no-op thinking cleanup (`clear_thinking_20251015` with `keep: "all"`), validates and ignores boolean `eager_input_streaming`, and accepts `defer_loading: false`. Context edits that would change history, task budgets, enabled strict/deferred tool execution, malformed output formats, and unsupported image source types return HTTP 400. Valid base64 and URL image blocks plus `tool_result.is_error` are preserved during translation.
|
|
835
828
|
|
|
836
829
|
## Architecture
|
|
837
830
|
|
|
@@ -250,11 +250,9 @@ it; continuation uses the complete provider Response items saved behind
|
|
|
250
250
|
|
|
251
251
|
### `POST /v1/messages`
|
|
252
252
|
|
|
253
|
-
|
|
253
|
+
Anthropic Messages compatibility endpoint for any client, including Claude Code and Scheherazade. It supports streaming (`stream: true`) and non-streaming without a client-identification header. A live Codex model ID routes directly. A `claude-*` facade ID uses the configured Codex backend model, or the authenticated catalog default when no backend model is configured. Other unknown IDs fail. The response preserves the client-supplied model name.
|
|
254
254
|
|
|
255
|
-
|
|
256
|
-
and strips the non-forwarded `max_tokens` and `cache_control` compatibility
|
|
257
|
-
fields only when one valid `x-claude-code-session-id` header is present.
|
|
255
|
+
`max_tokens` is a required positive compatibility hint: the private Codex OAuth transport does not expose a matching output-token cap. Valid ephemeral `cache_control` hints are accepted and stripped for every client; their TTL and breakpoint semantics are not forwarded. An optional `x-claude-code-session-id` provides stable hashed cache affinity, not permission to use the endpoint. `metadata.user_id` is accepted as client metadata and is not forwarded.
|
|
258
256
|
|
|
259
257
|
```bash
|
|
260
258
|
curl http://localhost:18080/v1/messages \
|
|
@@ -291,7 +289,7 @@ curl -N http://localhost:18080/v1/messages \
|
|
|
291
289
|
}'
|
|
292
290
|
```
|
|
293
291
|
|
|
294
|
-
Codex reasoning summaries are
|
|
292
|
+
With `thinking.display: "summarized"` (the enabled/adaptive default), real Codex reasoning summaries are returned as unsigned Anthropic `thinking` blocks and streamed `thinking_delta` content, alongside the existing `codex_reasoning` / `codex_reasoning_delta` extensions. `display: "omitted"` and disabled thinking suppress reasoning output. No Anthropic signature is fabricated. Valid thinking blocks in replayed assistant history are checked and stripped before Codex input; ordinary text and tool calls are preserved.
|
|
295
293
|
|
|
296
294
|
Hosted Anthropic `web_search` declarations are rejected with HTTP 400 on
|
|
297
295
|
Messages, token-counting, and compact requests. OpenAI Responses cannot supply
|
|
@@ -311,10 +309,7 @@ the completion is nonterminal or final usage is absent; a stream that has
|
|
|
311
309
|
already started emits an Anthropic `error` event and never emits a successful
|
|
312
310
|
`message_stop`. The final response includes every required usage field, using
|
|
313
311
|
`null` only where the Anthropic type permits it, plus required container,
|
|
314
|
-
citation, context-management, and direct-caller fields.
|
|
315
|
-
Code's realtime behavior, `message_start` is sent immediately without guessed
|
|
316
|
-
input-token counts; this narrow stream-start shape is Claude Code compatibility
|
|
317
|
-
behavior, not generic Anthropic API parity.
|
|
312
|
+
citation, context-management, and direct-caller fields. `message_start` is sent immediately with numeric zero counters in `message.usage`; these are provisional, not measured counts. The final `message_delta.usage` replaces them with authoritative cumulative totals, including disjoint cache buckets.
|
|
318
313
|
|
|
319
314
|
Generated `tool_use` blocks with `caller: {"type":"direct"}` and empty
|
|
320
315
|
assistant content arrays can be replayed through the request adapter. Malformed,
|
|
@@ -330,9 +325,9 @@ different jobs:
|
|
|
330
325
|
| `client_metadata.session_id` | Forwarded to Codex, used as the default `prompt_cache_key`, and required when Codex metadata mode is enabled | Not used or forwarded |
|
|
331
326
|
| `client_metadata.thread_id` | Codex metadata thread identity; defaults to `session_id` for a root request and preserves an explicit child thread | Not used or synthesized |
|
|
332
327
|
| `prompt_cache_key` | Explicit value wins; otherwise a non-empty `client_metadata.session_id` is used; otherwise omitted | Optional proxy extension; explicit value wins, otherwise the Claude Code session header is hashed |
|
|
333
|
-
| `x-claude-code-session-id` | Not used |
|
|
328
|
+
| `x-claude-code-session-id` | Not used | Optional cache-affinity header; its exact value supplies a hashed default key |
|
|
334
329
|
| `previous_response_id` | Resolves a known entry from the 256-chain process-local history and replays full input/output over HTTP; never forwarded | Non-null values return Anthropic-style HTTP 400 because normal Messages is stateless |
|
|
335
|
-
| `cache_control` | Not an OpenAI Chat control | Accepted
|
|
330
|
+
| `cache_control` | Not an OpenAI Chat control | Accepted for every client, validated, then stripped before Codex transport |
|
|
336
331
|
|
|
337
332
|
Anthropic `cache_control` is a cache-boundary annotation, not an identifier.
|
|
338
333
|
This proxy accepts only `{"type":"ephemeral"}` with optional `ttl: "5m"` or
|
|
@@ -344,15 +339,13 @@ responses do not expose the Chat-only `response_id`. The custom
|
|
|
344
339
|
`previous_response_id` support.
|
|
345
340
|
|
|
346
341
|
The Anthropic type permits `cache_control: null`; the proxy treats that exact
|
|
347
|
-
value as omission. A non-null cache-control object is validated in full
|
|
348
|
-
accepted only on a Claude Code request.
|
|
342
|
+
value as omission. A non-null cache-control object is validated in full for every client.
|
|
349
343
|
|
|
350
344
|
### `POST /v1/messages/count_tokens`
|
|
351
345
|
|
|
352
|
-
Anthropic-compatible token counting helper. Codex OAuth does not expose a count-only endpoint equivalent to Anthropic's native API, so this route counts text locally and returns context-window metadata for the effective backend model. Normalized model-visible messages, tool calls, tool-result metadata, reasoning, and tool schemas are counted once. Request-envelope fields and the
|
|
346
|
+
Anthropic-compatible token counting helper. Codex OAuth does not expose a count-only endpoint equivalent to Anthropic's native API, so this route counts text locally and returns context-window metadata for the effective backend model. Normalized model-visible messages, tool calls, tool-result metadata, reasoning, and tool schemas are counted once. Request-envelope fields and the `max_tokens` compatibility hint are excluded; other unsupported generation controls fail instead of becoming no-ops. Image inputs use a separate fixed estimate so inline base64 data is not counted as text.
|
|
353
347
|
|
|
354
|
-
`max_tokens` and `cache_control`
|
|
355
|
-
this route and require `x-claude-code-session-id` when present.
|
|
348
|
+
`max_tokens` and `cache_control` are optional client-neutral compatibility hints on this route; no Claude Code header is required.
|
|
356
349
|
|
|
357
350
|
Text estimation uses a bundled port of official `tiktoken` `o200k_base` `encode_ordinary`: the same Unicode pre-tokenization regex, byte-pair merge algorithm, and merge-rank data are implemented in Python, TypeScript, and Rust. It is a local estimator, not model metadata and not a substitute for a provider count API. The project does not depend on a `tiktoken` package or download encoding data at runtime. The last upstream synchronization check was **2026-07-14**, against [`tiktoken` 0.13.0 at `08a5f3b`](https://github.com/openai/tiktoken/tree/08a5f3b2c987ada4fc5aa1f16c643c203fa8acaa); the bundled rank file SHA-256 is `446a9538cb6c348e3516120d7c08b09f57c36495e2acfffe59a5bf8b0cfb1a2d`.
|
|
358
351
|
|
|
@@ -371,7 +364,7 @@ curl http://localhost:18080/v1/messages/count_tokens \
|
|
|
371
364
|
|
|
372
365
|
### `POST /v1/messages/compact`
|
|
373
366
|
|
|
374
|
-
Anthropic-compatible alias for remote conversation compaction. Accepts Anthropic Messages-shaped bodies and returns compacted checkpoint content. Live GPT model IDs select the matching backend model, and Claude Code effort and Fast Mode controls use the same mappings as `/v1/messages`. Its required non-forwarded `max_tokens` and any `cache_control` hints
|
|
367
|
+
Anthropic-compatible alias for remote conversation compaction. Accepts Anthropic Messages-shaped bodies and returns compacted checkpoint content. Live GPT model IDs select the matching backend model, and Claude Code effort and Fast Mode controls use the same mappings as `/v1/messages`. Its required non-forwarded `max_tokens` and any valid `cache_control` hints are accepted for every client; the optional Claude Code session header only supplies cache affinity.
|
|
375
368
|
|
|
376
369
|
### `POST /v1/images/generations`
|
|
377
370
|
|
|
@@ -511,7 +504,7 @@ Requests can instead use the Responses-shaped effort/context object:
|
|
|
511
504
|
- Responses Lite uses `all_turns` as the Codex wire default. An explicitly different context is rejected instead of silently overwritten; use `responses_lite: false` when the backend route supports classic Responses and another context is required.
|
|
512
505
|
- Remote compact keeps its existing private Codex `reasoning_effort` field but does not accept public `reasoning.mode` or `reasoning.context`.
|
|
513
506
|
|
|
514
|
-
Anthropic `thinking` values map as `enabled → high
|
|
507
|
+
Anthropic `thinking` values map as `enabled → high` and `adaptive → medium`. Disabled thinking selects `none` when available, otherwise the lowest supported catalog effort, and suppresses reasoning output; it does not claim to eliminate a model's internal reasoning. `output_config.effort` takes precedence over adaptive or enabled thinking and supports `low`, `medium`, `high`, `xhigh`, and `max`. Call-level disabled thinking takes precedence over ambient `output_config.effort` for auxiliary calls such as WebFetch. Explicit unsupported effort requests still fail. Hosted Anthropic WebSearch is rejected because its lifecycle cannot be translated losslessly. The supported reasoning context and verbosity extensions are also available on `/v1/messages`, image generation, and inspection requests; Pro, non-null public cache policy/breakpoints, and any supplied `safety_identifier` fail explicitly on the private Codex provider.
|
|
515
508
|
|
|
516
509
|
Anthropic's `JSONOutputFormat` carries a schema but no name. When Claude Code
|
|
517
510
|
sends that official shape, the proxy follows pinned Codex 0.153.3 and supplies
|
|
@@ -785,9 +778,9 @@ ANTHROPIC_BASE_URL=http://127.0.0.1:18080 ANTHROPIC_AUTH_TOKEN=unused \
|
|
|
785
778
|
claude --model gpt-5.6-luna --effort medium
|
|
786
779
|
```
|
|
787
780
|
|
|
788
|
-
Live Codex IDs are sent to the matching backend model. Built-in Claude/Fable/Opus/Sonnet/Haiku IDs are not Codex models, so recognized `claude-*` IDs
|
|
781
|
+
Live Codex IDs are sent to the matching backend model. Built-in Claude/Fable/Opus/Sonnet/Haiku IDs are not Codex models, so recognized `claude-*` IDs use the configured Codex model or authenticated catalog default; the proxy preserves the selected client name in the Anthropic response.
|
|
789
782
|
|
|
790
|
-
`ANTHROPIC_BASE_URL` applies to the whole Claude Code process. Therefore the built-in rows remain available in the picker, but they also pass through codex-as-api and use the
|
|
783
|
+
`ANTHROPIC_BASE_URL` applies to the whole Claude Code process. Therefore the built-in rows remain available in the picker, but they also pass through codex-as-api and use the configured or catalog-default Codex backend; the rows do not call Anthropic models in that process. To use an actual Anthropic model and GPT in parallel, keep the gateway variables out of global settings, start the GPT process with the inline command above, and start a second process normally:
|
|
791
784
|
|
|
792
785
|
```bash
|
|
793
786
|
claude --model sonnet
|
|
@@ -797,7 +790,7 @@ If the gateway variables are already in `~/.claude/settings.json`, shell-level `
|
|
|
797
790
|
|
|
798
791
|
Claude Code gateway discovery cannot expose raw `gpt-*` IDs because Claude Code filters discovered IDs to `claude*` and `anthropic*`. The official single custom-model option avoids a misleading facade ID. This integration is an Anthropic Messages-compatible bridge; Anthropic explicitly does not support routing Claude Code to non-Claude models through third-party gateways. See the official [model configuration](https://code.claude.com/docs/en/model-config), [gateway connection](https://code.claude.com/docs/en/llm-gateway-connect), and [gateway protocol](https://code.claude.com/docs/en/llm-gateway-protocol) references.
|
|
799
792
|
|
|
800
|
-
The
|
|
793
|
+
The bridge accepts Claude Code's no-op thinking cleanup (`clear_thinking_20251015` with `keep: "all"`), validates and ignores boolean `eager_input_streaming`, and accepts `defer_loading: false`. Context edits that would change history, task budgets, enabled strict/deferred tool execution, malformed output formats, and unsupported image source types return HTTP 400. Valid base64 and URL image blocks plus `tool_result.is_error` are preserved during translation.
|
|
801
794
|
|
|
802
795
|
## Architecture
|
|
803
796
|
|
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# Release Notes
|
|
2
2
|
|
|
3
|
+
## v0.8.3
|
|
4
|
+
|
|
5
|
+
### Client-neutral Anthropic compatibility (all runtimes)
|
|
6
|
+
|
|
7
|
+
- Remove Claude Code identity gates around valid required `max_tokens` and
|
|
8
|
+
ephemeral `cache_control` hints on Messages, counting and compact routes.
|
|
9
|
+
These are compatibility hints, not native Codex output caps or cache TTLs;
|
|
10
|
+
optional Claude session cache affinity remains available.
|
|
11
|
+
- Include provisional zero usage in every initial streaming `message_start`,
|
|
12
|
+
followed by authoritative final usage with the 0.8.2 disjoint cache accounting.
|
|
13
|
+
- Support summarized thinking as real unsigned thinking blocks/deltas, retaining
|
|
14
|
+
existing Codex reasoning extensions. Omitted and disabled thinking suppress
|
|
15
|
+
reasoning output; never fabricate an Anthropic signature.
|
|
16
|
+
- Resolve disabled-derived effort to the model's lowest supported level when
|
|
17
|
+
`none` is unavailable; explicit effort selection remains capability-checked.
|
|
18
|
+
- Validate and strip replayed assistant thinking history for multi-turn clients.
|
|
19
|
+
Accept standard `metadata.user_id` and boolean tool streaming hints without
|
|
20
|
+
inventing backend semantics, and use the live catalog default for unconfigured
|
|
21
|
+
`claude-*` facade routing.
|
|
22
|
+
|
|
23
|
+
### Official test quality
|
|
24
|
+
|
|
25
|
+
- Replace regex diagnostic assertions with literal returned-error checks and
|
|
26
|
+
remove source-presence, filesystem-permission and worker-liveness oracles.
|
|
27
|
+
Retain actual input/output, persistence-content and cancellation-state cases.
|
|
28
|
+
- Repair stale client-gate expectations and positive controls, including valid
|
|
29
|
+
null cache hints, live-default Claude facade routing and mixed thinking/tool
|
|
30
|
+
replay. Validate all three runtimes with their standard regression runners.
|
|
31
|
+
|
|
32
|
+
## v0.8.2
|
|
33
|
+
|
|
34
|
+
### Hotfix: disjoint Anthropic cache usage (all runtimes)
|
|
35
|
+
|
|
36
|
+
- Correct Anthropic Messages usage in streaming and non-streaming responses:
|
|
37
|
+
`input_tokens` now excludes both cache reads and reported cache creation,
|
|
38
|
+
which remain in their separate `cache_read_input_tokens` and
|
|
39
|
+
`cache_creation_input_tokens` counters. Previously, consumers adding those
|
|
40
|
+
counters counted cached input twice.
|
|
41
|
+
- Keep OpenAI/Responses totals unchanged. Preserve unknown cache counters as
|
|
42
|
+
null; subtract only reported cache buckets. Reject impossible cache sums
|
|
43
|
+
exceeding the upstream input total instead of emitting negative/clamped usage.
|
|
44
|
+
- Add no-cache, partial/full-cache, cache-write, invalid-total and HTTP pipeline
|
|
45
|
+
regressions across TypeScript, Python and Rust. Example: 100 total input,
|
|
46
|
+
50 cache read and 25 cache write becomes 25 uncached + 50 read + 25 write.
|
|
47
|
+
|
|
3
48
|
## v0.8.1
|
|
4
49
|
|
|
5
50
|
### Explicit Ultra Fast selection (all runtimes)
|
|
@@ -26,6 +26,7 @@ def anthropic_request_to_internal(
|
|
|
26
26
|
max_tokens: int | None = None,
|
|
27
27
|
output_format: dict[str, Any] | None = None,
|
|
28
28
|
output_config: object = None,
|
|
29
|
+
metadata: object = None,
|
|
29
30
|
) -> tuple[
|
|
30
31
|
list[Message], list[ToolSchema] | None, str | dict | None, list[str] | None, str | None, dict[str, Any] | None
|
|
31
32
|
]:
|
|
@@ -37,6 +38,7 @@ def anthropic_request_to_internal(
|
|
|
37
38
|
raise ValueError("model must be a non-empty string")
|
|
38
39
|
if not isinstance(messages, list) or not messages:
|
|
39
40
|
raise ValueError("messages must be a non-empty array")
|
|
41
|
+
validate_anthropic_metadata(metadata)
|
|
40
42
|
if stop_sequences is not None and (
|
|
41
43
|
not isinstance(stop_sequences, list) or any(not isinstance(value, str) or not value for value in stop_sequences)
|
|
42
44
|
):
|
|
@@ -274,9 +276,28 @@ def _convert_assistant_message(content: object, out: list[Message]) -> None:
|
|
|
274
276
|
arguments=json.dumps(arguments, ensure_ascii=False, separators=(",", ":")),
|
|
275
277
|
)
|
|
276
278
|
)
|
|
279
|
+
elif block_type == "thinking":
|
|
280
|
+
_reject_unknown_fields(
|
|
281
|
+
block,
|
|
282
|
+
{"type", "thinking", "signature"},
|
|
283
|
+
f"Anthropic assistant content block {index}",
|
|
284
|
+
)
|
|
285
|
+
thinking_text = block.get("thinking")
|
|
286
|
+
if not isinstance(thinking_text, str):
|
|
287
|
+
raise ValueError(f"Anthropic assistant thinking block {index} requires string thinking")
|
|
288
|
+
signature = block.get("signature")
|
|
289
|
+
if signature is not None and not isinstance(signature, str):
|
|
290
|
+
raise ValueError(f"Anthropic assistant thinking block {index} signature must be a string")
|
|
291
|
+
elif block_type == "redacted_thinking":
|
|
292
|
+
_reject_unknown_fields(
|
|
293
|
+
block,
|
|
294
|
+
{"type", "data"},
|
|
295
|
+
f"Anthropic assistant content block {index}",
|
|
296
|
+
)
|
|
297
|
+
data = block.get("data")
|
|
298
|
+
if not isinstance(data, str):
|
|
299
|
+
raise ValueError(f"Anthropic assistant redacted_thinking block {index} requires string data")
|
|
277
300
|
elif block_type in {
|
|
278
|
-
"thinking",
|
|
279
|
-
"redacted_thinking",
|
|
280
301
|
"server_tool_use",
|
|
281
302
|
"web_search_tool_result",
|
|
282
303
|
}:
|
|
@@ -309,11 +330,17 @@ def _convert_tools(tools: object) -> list[ToolSchema]:
|
|
|
309
330
|
if "allowed_callers" in tool or "output_schema" in tool:
|
|
310
331
|
raise ValueError(f"Anthropic tool {index} uses Programmatic Tool Calling fields that cannot be preserved")
|
|
311
332
|
if "defer_loading" in tool:
|
|
312
|
-
|
|
333
|
+
defer_loading = tool.get("defer_loading")
|
|
334
|
+
if defer_loading is not None and not isinstance(defer_loading, bool):
|
|
335
|
+
raise ValueError(f"Anthropic tool {index} defer_loading must be a boolean")
|
|
336
|
+
if defer_loading is True:
|
|
337
|
+
raise ValueError(
|
|
338
|
+
f"Anthropic tool {index} field defer_loading is not supported by the Codex OAuth backend"
|
|
339
|
+
)
|
|
313
340
|
for field in ("eager_input_streaming",):
|
|
314
341
|
value = tool.get(field)
|
|
315
|
-
if value is not None:
|
|
316
|
-
raise ValueError(f"Anthropic tool {index}
|
|
342
|
+
if value is not None and not isinstance(value, bool):
|
|
343
|
+
raise ValueError(f"Anthropic tool {index} {field} must be a boolean")
|
|
317
344
|
name = tool.get("name")
|
|
318
345
|
if not isinstance(name, str) or not name:
|
|
319
346
|
raise ValueError(f"Anthropic tool {index} requires a non-empty name")
|
|
@@ -449,6 +476,33 @@ def anthropic_parallel_tool_calls(
|
|
|
449
476
|
return not value
|
|
450
477
|
|
|
451
478
|
|
|
479
|
+
def validate_anthropic_metadata(metadata: object) -> None:
|
|
480
|
+
if metadata is None:
|
|
481
|
+
return
|
|
482
|
+
if not isinstance(metadata, dict):
|
|
483
|
+
raise ValueError("metadata must be an object")
|
|
484
|
+
_reject_unknown_fields(metadata, {"user_id"}, "metadata")
|
|
485
|
+
user_id = metadata.get("user_id")
|
|
486
|
+
if user_id is not None and not isinstance(user_id, str):
|
|
487
|
+
raise ValueError("metadata.user_id must be a string")
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
def resolve_anthropic_thinking_options(
|
|
491
|
+
thinking: dict[str, Any] | None,
|
|
492
|
+
max_tokens: int | None = None,
|
|
493
|
+
) -> tuple[str, str | None, bool]:
|
|
494
|
+
"""Return (thinking_display, reasoning_summary, thinking_disabled) for a thinking object."""
|
|
495
|
+
if thinking is None:
|
|
496
|
+
return "summarized", None, False
|
|
497
|
+
_convert_thinking(thinking, max_tokens)
|
|
498
|
+
thinking_type = thinking.get("type")
|
|
499
|
+
if thinking_type == "disabled":
|
|
500
|
+
return "omitted", "none", True
|
|
501
|
+
display = "omitted" if thinking.get("display") == "omitted" else "summarized"
|
|
502
|
+
reasoning_summary = "none" if display == "omitted" else "auto"
|
|
503
|
+
return display, reasoning_summary, False
|
|
504
|
+
|
|
505
|
+
|
|
452
506
|
def _convert_thinking(thinking: dict[str, Any] | None, max_tokens: int | None) -> str | None:
|
|
453
507
|
if thinking is None:
|
|
454
508
|
return None
|
|
@@ -458,8 +512,8 @@ def _convert_thinking(thinking: dict[str, Any] | None, max_tokens: int | None) -
|
|
|
458
512
|
if thinking_type == "enabled":
|
|
459
513
|
_reject_unknown_fields(thinking, {"type", "budget_tokens", "display"}, "thinking")
|
|
460
514
|
display = thinking.get("display")
|
|
461
|
-
if display is not None and display
|
|
462
|
-
raise ValueError("thinking.display must be 'omitted' when provided")
|
|
515
|
+
if display is not None and (not isinstance(display, str) or display not in {"summarized", "omitted"}):
|
|
516
|
+
raise ValueError("thinking.display must be 'summarized' or 'omitted' when provided")
|
|
463
517
|
budget_tokens = thinking.get("budget_tokens")
|
|
464
518
|
parsed_budget_tokens = as_js_safe_integer(budget_tokens)
|
|
465
519
|
if parsed_budget_tokens is None or parsed_budget_tokens < 1024:
|
|
@@ -470,8 +524,8 @@ def _convert_thinking(thinking: dict[str, Any] | None, max_tokens: int | None) -
|
|
|
470
524
|
if thinking_type == "adaptive":
|
|
471
525
|
_reject_unknown_fields(thinking, {"type", "display"}, "thinking")
|
|
472
526
|
display = thinking.get("display")
|
|
473
|
-
if display is not None and display
|
|
474
|
-
raise ValueError("thinking.display must be 'omitted' when provided")
|
|
527
|
+
if display is not None and (not isinstance(display, str) or display not in {"summarized", "omitted"}):
|
|
528
|
+
raise ValueError("thinking.display must be 'summarized' or 'omitted' when provided")
|
|
475
529
|
return "medium"
|
|
476
530
|
if thinking_type == "disabled":
|
|
477
531
|
_reject_unknown_fields(thinking, {"type"}, "thinking")
|
|
@@ -648,11 +702,18 @@ def internal_response_to_anthropic(
|
|
|
648
702
|
response: AssistantResponse,
|
|
649
703
|
model: str,
|
|
650
704
|
request_id: str,
|
|
705
|
+
*,
|
|
706
|
+
thinking_display: str = "summarized",
|
|
651
707
|
) -> dict[str, Any]:
|
|
708
|
+
if thinking_display not in {"summarized", "omitted"}:
|
|
709
|
+
raise ValueError("thinking_display must be 'summarized' or 'omitted'")
|
|
652
710
|
content: list[dict[str, Any]] = []
|
|
653
711
|
|
|
654
712
|
_reject_unrepresentable_response_events(response.raw)
|
|
655
713
|
|
|
714
|
+
if thinking_display == "summarized" and response.reasoning_content:
|
|
715
|
+
content.append({"type": "thinking", "thinking": response.reasoning_content})
|
|
716
|
+
|
|
656
717
|
if response.content:
|
|
657
718
|
content.append({"type": "text", "text": response.content, "citations": None})
|
|
658
719
|
|
|
@@ -688,7 +749,7 @@ def internal_response_to_anthropic(
|
|
|
688
749
|
"stop_sequence": None,
|
|
689
750
|
"usage": usage_dict,
|
|
690
751
|
}
|
|
691
|
-
if response.reasoning_content:
|
|
752
|
+
if thinking_display == "summarized" and response.reasoning_content:
|
|
692
753
|
result["codex_reasoning"] = response.reasoning_content
|
|
693
754
|
return result
|
|
694
755
|
|
|
@@ -718,13 +779,23 @@ def _reject_unrepresentable_response_events(raw: dict | None) -> None:
|
|
|
718
779
|
)
|
|
719
780
|
|
|
720
781
|
|
|
782
|
+
def _uncached_input_tokens(total: int, cache_read: int | None, cache_write: int | None) -> int:
|
|
783
|
+
# OpenAI totals include cache buckets; Anthropic exposes disjoint counters.
|
|
784
|
+
cached = (cache_read or 0) + (cache_write or 0)
|
|
785
|
+
if cached > total:
|
|
786
|
+
raise ChatGPTOAuthProtocolError("usage cache tokens must not exceed input_tokens")
|
|
787
|
+
return total - cached
|
|
788
|
+
|
|
789
|
+
|
|
721
790
|
def _anthropic_usage_from_internal(usage: Usage) -> dict[str, Any]:
|
|
722
791
|
return {
|
|
723
792
|
"cache_creation": None,
|
|
724
793
|
"cache_creation_input_tokens": usage.cache_write_tokens,
|
|
725
794
|
"cache_read_input_tokens": usage.cached_tokens,
|
|
726
795
|
"inference_geo": None,
|
|
727
|
-
"input_tokens":
|
|
796
|
+
"input_tokens": _uncached_input_tokens(
|
|
797
|
+
usage.prompt_tokens, usage.cached_tokens, usage.cache_write_tokens
|
|
798
|
+
),
|
|
728
799
|
"iterations": None,
|
|
729
800
|
"output_tokens": usage.completion_tokens,
|
|
730
801
|
"server_tool_use": None,
|
|
@@ -790,10 +861,14 @@ def anthropic_stream_adapter(
|
|
|
790
861
|
event_stream: Iterator[dict[str, Any]],
|
|
791
862
|
model: str,
|
|
792
863
|
request_id: str,
|
|
864
|
+
*,
|
|
865
|
+
thinking_display: str = "summarized",
|
|
793
866
|
) -> Iterator[str]:
|
|
794
867
|
"""Convert provider chat_stream events into Anthropic SSE strings."""
|
|
868
|
+
if thinking_display not in {"summarized", "omitted"}:
|
|
869
|
+
raise ValueError("thinking_display must be 'summarized' or 'omitted'")
|
|
795
870
|
yield _message_start_sse(model, request_id)
|
|
796
|
-
yield from _render_anthropic_stream_events(event_stream)
|
|
871
|
+
yield from _render_anthropic_stream_events(event_stream, thinking_display=thinking_display)
|
|
797
872
|
|
|
798
873
|
|
|
799
874
|
def _message_start_sse(model: str, request_id: str) -> str:
|
|
@@ -811,6 +886,13 @@ def _message_start_sse(model: str, request_id: str) -> str:
|
|
|
811
886
|
"context_management": None,
|
|
812
887
|
"stop_reason": None,
|
|
813
888
|
"stop_sequence": None,
|
|
889
|
+
# Provisional; message_delta carries authoritative cumulative usage.
|
|
890
|
+
"usage": {
|
|
891
|
+
"input_tokens": 0,
|
|
892
|
+
"output_tokens": 0,
|
|
893
|
+
"cache_read_input_tokens": 0,
|
|
894
|
+
"cache_creation_input_tokens": 0,
|
|
895
|
+
},
|
|
814
896
|
},
|
|
815
897
|
},
|
|
816
898
|
)
|
|
@@ -910,7 +992,7 @@ def _anthropic_usage_from_provider(usage: Any) -> dict[str, Any]:
|
|
|
910
992
|
out: dict[str, Any] = {
|
|
911
993
|
"cache_creation_input_tokens": cache_write,
|
|
912
994
|
"cache_read_input_tokens": cache_read,
|
|
913
|
-
"input_tokens": input_tokens,
|
|
995
|
+
"input_tokens": _uncached_input_tokens(input_tokens, cache_read, cache_write),
|
|
914
996
|
"iterations": None,
|
|
915
997
|
"output_tokens": output_tokens,
|
|
916
998
|
"server_tool_use": server_tool_use,
|
|
@@ -934,7 +1016,11 @@ def _validate_usage_counter_object(
|
|
|
934
1016
|
raise ChatGPTOAuthProtocolError(f"provider usage {field}.{name} must be a non-negative integer")
|
|
935
1017
|
|
|
936
1018
|
|
|
937
|
-
def _render_anthropic_stream_events(
|
|
1019
|
+
def _render_anthropic_stream_events(
|
|
1020
|
+
events: Iterator[dict[str, Any]],
|
|
1021
|
+
*,
|
|
1022
|
+
thinking_display: str = "summarized",
|
|
1023
|
+
) -> Iterator[str]:
|
|
938
1024
|
block_index = 0
|
|
939
1025
|
current_block: str | None = None
|
|
940
1026
|
has_tool_calls = False
|
|
@@ -948,6 +1034,29 @@ def _render_anthropic_stream_events(events: Iterator[dict[str, Any]]) -> Iterato
|
|
|
948
1034
|
text = event.get("text")
|
|
949
1035
|
if not isinstance(text, str):
|
|
950
1036
|
raise ChatGPTOAuthProtocolError(f"provider {typ} event requires string text")
|
|
1037
|
+
if thinking_display == "omitted":
|
|
1038
|
+
continue
|
|
1039
|
+
if current_block != "thinking":
|
|
1040
|
+
if current_block is not None:
|
|
1041
|
+
yield _sse("content_block_stop", {"type": "content_block_stop", "index": block_index})
|
|
1042
|
+
block_index += 1
|
|
1043
|
+
yield _sse(
|
|
1044
|
+
"content_block_start",
|
|
1045
|
+
{
|
|
1046
|
+
"type": "content_block_start",
|
|
1047
|
+
"index": block_index,
|
|
1048
|
+
"content_block": {"type": "thinking", "thinking": ""},
|
|
1049
|
+
},
|
|
1050
|
+
)
|
|
1051
|
+
current_block = "thinking"
|
|
1052
|
+
yield _sse(
|
|
1053
|
+
"content_block_delta",
|
|
1054
|
+
{
|
|
1055
|
+
"type": "content_block_delta",
|
|
1056
|
+
"index": block_index,
|
|
1057
|
+
"delta": {"type": "thinking_delta", "thinking": text},
|
|
1058
|
+
},
|
|
1059
|
+
)
|
|
951
1060
|
yield _sse(
|
|
952
1061
|
"codex_reasoning_delta",
|
|
953
1062
|
{
|
|
@@ -289,11 +289,12 @@ def resolve_model(
|
|
|
289
289
|
except ChatGPTOAuthInvalidRequestError as exc:
|
|
290
290
|
raise ChatGPTOAuthCatalogUnavailableError("configured model is unusable") from exc
|
|
291
291
|
if candidate is not None and anthropic_facade and candidate.startswith("claude-"):
|
|
292
|
-
if configured_model is None:
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
292
|
+
if configured_model is not None:
|
|
293
|
+
return configured_capability(configured_model)
|
|
294
|
+
capability = snapshot.default_model()
|
|
295
|
+
if not capability.slug.strip() or capability.slug != capability.slug.strip():
|
|
296
|
+
raise ChatGPTOAuthCatalogUnavailableError("default model publishes an unusable slug")
|
|
297
|
+
return capability
|
|
297
298
|
if candidate is not None:
|
|
298
299
|
return snapshot.model(candidate)
|
|
299
300
|
if configured_model is not None:
|