- Accept fast|priority (canonical priority), flex|auto|default|scale on
/v1/responses and /v1/chat/completions; reject unknown/empty/non-string
with HTTP 400; omitted and null stay compatible.
- Propagate service_tier through JSON/SSE, Responses<->Chat conversions,
fallback paths and HTTP->upstream WebSocket bridge.
- Billing prefers the upstream terminal tier; the outbound (policy-
transformed) tier is used only when upstream omits the field.
Explicit upstream default bills Standard even when Fast was requested.
- Pricing: Fast premium 2x Standard for gpt-5.6-sol/terra/luna and
gpt-5.4; 2.5x for gpt-5.5; channel FastMultiplier stays authoritative.
- Live verification (official Codex 0.149.0 + gateway, HTTP & WS):
upstream ChatGPT backend may return terminal default even when the
account catalog advertises priority; billing follows the actual tier.
The Responses client-tool restore raised an upstream function_call back to
custom_tool_call and tool_search_call without touching its item ID, so the
client received a custom_tool_call carrying the upstream's fc_ ID. That
combination is invalid: once the item lands in the client's history, any
later replay to an upstream that validates item IDs fails the whole request
with "Invalid 'input[N].id': 'fc_...'. Expected an ID that begins with
'ctc'", and every following message re-submits the same bad item.
Re-prefix the ID to match the type it is raised to, keeping the suffix so
the ID stays stable and unique per upstream item. IDs without a known
tool-call prefix are left alone rather than guessed at.
The lowering direction has to reverse this, because the WebSocket HTTP
bridge replays restored items back to the upstream and would otherwise lose
the upstream's own ID. normalizeLoweredFunctionItemID now maps a ctc_/tsc_
call ID back to fc_ instead of dropping it, which makes the round trip
lossless; output item IDs (ctco_/tso_) still have no function-protocol
counterpart and are still dropped. The helper was renamed from
dropInvalidLoweredFunctionItemID to match what it now does.
The stream restorer keeps the upstream ID for its own bookkeeping so later
upstream events still match the call, and emits the client-facing ID on
output_item.added/done and the custom_tool_call_input events.
Closes#6071
The responses->chat streaming converter re-emits the tool name as an empty
string on every function_call_arguments.delta because ChatFunctionCall.Name
has no omitempty. OpenAI-compatible clients accumulate the name from the
first tool-call delta, so the trailing "name":"" overwrites it and the
call fails dispatch (unknown tool ""). Empty names also break the next
round-trip upstream (missing field 'name').
Fixes#5631
Round-trip Grok encrypted reasoning via Anthropic thinking.signature and
seed cache identity from Claude Code session IDs, so multi-turn /v1/messages
traffic can hit xAI prompt cache more like CLIProxyAPI. Also strip invalid
encrypted_content and retry on the Messages path when decrypt fails after
account switches.
Use *string for AnthropicResponse.StopReason so streaming message_start
serializes as JSON null instead of "", matching official Anthropic wire
format and avoiding strict client turn-finalization failures.
The Anthropic→Responses streaming converter omits two things the OpenAI
Responses wire format requires, which breaks clients that reconstruct the
response from the event stream (rather than just reading deltas).
1. response.content_part.added is never emitted.
A message item is opened with content: [], and the OpenAI SDK's
accumulating stream helper (client.responses.stream) only appends a
content part when it sees content_part.added. Without it, the next
output_text.delta indexes output.content[content_index] and raises:
File "openai/lib/streaming/responses/_responses.py", line 352,
in accumulate_event
content = output.content[event.content_index]
IndexError: list index out of range
Raw iteration (responses.create(stream=True)) does not accumulate and is
unaffected, which is why this went unnoticed.
2. response.completed carries Output: []ResponsesOutput{}.
get_final_response() and tracing integrations parse the terminal event's
response directly, so callers see an empty output_text even though the
deltas streamed correctly. This one is invisible when only watching the
stream render.
Also carries the full text on output_text.done / content_part.done (deltas
carry increments only, done events carry the whole part) and fills in
content/arguments/summary on output_item.done, which had the same
empty-payload issue.
Reproduced against a live Anthropic-platform group with the openai Python
SDK 2.46.0; Arize Phoenix's playground hits the same path. Verified before
(IndexError) and after (full text via get_final_response()).
Adds regression tests covering event ordering, done-event payloads, and the
terminal event's output for both text and tool calls.