Translation Support Matrix

by hand — edit docs/conformance/translation-matrix.yaml and regenerate. –>

Versions marked “overview” do not contain this page. Selecting one opens that version’s documentation overview.

On this page

What Praxis AI serves natively, what it translates between dialects, and what it refuses.

Reading the matrix

StatusMeaning
nativePraxis implements the operation locally and owns the payload.
passthroughForwarded to the backend unchanged; backend semantics apply.
translatedConverted with no semantic loss.
lossyConverted, with something approximated. The surface section says what.
rejectedExplicit dialect-shaped error at the proxy edge.
gapKnown defect: accepted and then silently ignored or silently wrong. See Known gaps.
n/aThe client API has no such concept.

gap is not a support level. A gap cell means the request is accepted and the stated behavior does not happen — treat it as unsupported until the linked blocker closes.

A cell takes the status of the feature’s main path. gap wins whenever any accepted input is silently wrong and lossy wins whenever any accepted input loses information; an explicit rejection of a sub-case is recorded in the surface notes and does not demote the cell.

Before you read the matrix

The cells below describe a pipeline that includes the filters named in each surface section. These facts change what a cell means in a given deployment.

  • clean-rejection-is-opt-in — Praxis emits a provider-shaped error only when one of three filters ran and matched. With none installed the proxy emits RFC 9457 application/problem+json, not the client’s dialect.
    • Installers: ai_operation (apis/src/operation_classifier/mod.rs:280-293, picks the OpenAI or Anthropic formatter from the matched protocol), openai_responses_request (apis/src/openai/responses/request/mod.rs:523 via apis/src/openai/responses/mod.rs:368-376), anthropic_messages_request (apis/src/anthropic/messages_request/mod.rs:273-277).
    • 53 of 105 configs under examples/configs install none of the three.
    • Evidence: praxis-proxy-protocol-0.7.5/src/http/pingora/handler/fail_to_proxy.rs:267-283
  • on-invalid-defaults-to-continue — Malformed or unrecognized bodies are forwarded to the backend and lose the error formatter. 21 of the 51 examples that set on_invalid choose continue.
    • Evidence: apis/src/openai/responses/config.rs:119-120
  • no-deny-unknown-fields-on-the-wire — No request body is deserialized into a typed struct on any surface. Unknown fields are governed only by hand-written allowlists, where those exist. Absence of an allowlist means silent acceptance.
    • Evidence: apis/src/openai/translation/chat_completions.rs:362-370; apis/src/openai/translation/chat_completions.rs:446-469

Matrix

FeatureAnthropic -> CCResponses -> CCCC -> GeminiCC -> AzureCC -> Bedrock
Plain text completiontranslatedtranslatedtranslatedpassthroughtranslated
System / developer instructionslossytranslatedtranslatedpassthroughtranslated
Multi-turn historytranslatedtranslatedtranslatedpassthroughgap
Image inputtranslatedtranslatedlossypassthroughrejected
Audio inputn/arejectedrejectedpassthroughrejected
Document / file inputrejectedtranslatedrejectedpassthroughrejected
Client function toolstranslatedtranslatedlossypassthroughlossy
Tool choicetranslatedtranslatedtranslatedpassthroughlossy
Parallel tool callstranslatedtranslatedrejectedpassthroughrejected
Provider-hosted tools (web search, file search, MCP, code interpreter)rejectedlossyrejectedpassthroughrejected
Structured output / JSON schemalossytranslatedgappassthroughrejected
Streaminglossytranslatedtranslatedpassthroughtranslated
Streaming terminates correctlygaptranslatedtranslatedpassthroughtranslated
Mid-stream upstream failure is surfacedgaplossytranslatedpassthroughtranslated
Content-filter / safety signal reaches the clientgaptranslatedtranslatedgaptranslated
Token usage accountinggaplossylossypassthroughlossy
Prompt-cache token accountinglossytranslatedgappassthroughgap
Reasoning / thinking contentgaprejectedgappassthroughgap
Request-side thinking / prompt-cache controlsrejectedtranslatedrejectedpassthroughrejected
Stop sequenceslossyn/atranslatedpassthroughtranslated
Sampling parameterslossytranslatedlossypassthroughtranslated
Logprobsrejectedlossytranslatedpassthroughrejected
Server-side state continuationn/agapn/an/an/a
Unsupported operations on a supported pathn/agapn/an/an/a
Error envelope matches the client dialectlossylossylossylossylossy
Oversized request bodygapgapgapgapgap

Known gaps

Behavior Praxis does not provide despite accepting the request. These are defects, not support levels.

Spanning several surfaces

  • reasoning-and-cache-tokens-vanish — Reasoning content is silently discarded on the Anthropic and Bedrock paths, and its token count on Vertex. Cache-token counters are discarded entirely on Vertex and Bedrock; Anthropic maps cache reads and loses only the cache-write counter. Customers are billed for tokens the matrix would otherwise claim they receive.
    • Affects: anthropic-messages-to-chat-completions, chat-completions-to-vertexai-gemini, chat-completions-to-bedrock-converse
    • Blocker: TODO-ISSUE systematic handling of reasoning and cache tokens across translators
  • oversized-body-bare-413 — An oversized body produces Rejection::status(413) with no headers and no body, then short-circuits fail_to_proxy before any formatter runs. This fires for the global body_limits ceiling and for every filter’s StreamBuffer{max_bytes}, including the request filters that would otherwise install the formatter. An SDK receives a content-type-less empty 413 in every dialect.
    • Affects: anthropic-messages-to-chat-completions, responses-to-chat-completions, chat-completions-to-vertexai-gemini, chat-completions-to-azureai, chat-completions-to-bedrock-converse
    • Blocker: TODO-ISSUE format the 413 produced by the body-limit filter
  • divergent-error-envelopes — Three filters emit three different OpenAI-shaped envelopes — Vertex includes param:null, Bedrock omits param and uses code “invalid_request”, Azure rewrites whatever Azure returned. One client error handler cannot cover all three.
    • Affects: chat-completions-to-vertexai-gemini, chat-completions-to-bedrock-converse, chat-completions-to-azureai
    • Blocker: TODO-ISSUE converge on one OpenAI error envelope builder
  • inverted-token-budget-precedence — When both max_tokens and max_completion_tokens are present, Vertex prefers max_completion_tokens and Bedrock prefers max_tokens. The same client request gets a different token budget depending on the backend. Vertex documents and tests its own precedence; nothing explains why the two differ.
    • Affects: chat-completions-to-vertexai-gemini, chat-completions-to-bedrock-converse
    • Blocker: TODO-ISSUE pick one max-tokens precedence across translators

Per surface

SurfaceFeatureGapBlocker
Anthropic -> CCStreaming terminates correctlyTerminal events are emitted only from the [DONE] branch. If the upstream stream ends without the sentinel, no message_delta and no message_stop are sent — a truncated response reported as success. Anthropic’s documented ping keep-alive is never emitted either.TODO-ISSUE emit terminal events at end-of-stream
Anthropic -> CCMid-stream upstream failure is surfacedA data: {"error":{...}} chunk after a 200 parses as a valid JSON object, matches neither choices nor usage, and is discarded. The client sees a short clean completion for a failed generation. Only non-JSON and non-object data fails the stream.TODO-ISSUE surface mid-stream upstream error objects
Anthropic -> CCContent-filter / safety signal reaches the clientNon-streaming refuses finish_reason content_filter (the upstream success is rejected with 500); streaming silently maps it to end_turn. Identical backend behavior yields opposite client-visible outcomes depending on stream.TODO-ISSUE reconcile content_filter across streaming and finite paths
Anthropic -> CCToken usage accountingmessage_start.usage.input_tokens is hardcoded 0 rather than null, so clients reading prompt tokens at message_start get a wrong number, not a missing one. Finite-path usage is correct.TODO-ISSUE populate or null message_start usage
Anthropic -> CCReasoning / thinking contentUpstream message.reasoning_content is never read on either path, so billed reasoning output vanishes. A non-null finite refusal is read and refused (the upstream success is rejected with 500); a streaming delta.refusal is not read.TODO-ISSUE reject or surface reasoning_content and refusal
Anthropic -> CCOversized request bodySee the shared oversized-body gap below.TODO-ISSUE format the 413 produced by the body-limit filter
Responses -> CCServer-side state continuationprevious_response_id and conversation rehydrate correctly, but on continuation any stored output item that is not message, function_call, function_call_output, reasoning, compaction or item_reference is skipped with no error — the model silently loses hosted-tool history. Separately, an unknown previous_response_id returns 400 where OpenAI returns 404.TODO-ISSUE preserve or reject hosted-tool history on continuation
Responses -> CCUnsupported operations on a supported pathThe GET /v1/responses WebSocket upgrade is positively classified as Responses traffic, and POST /v1/responses/compact is too unless openai_responses_compact is in the chain to answer it locally. The translator passes anything that is not POST /v1/responses through untouched, so both reach a Chat-Completions-only backend verbatim. No guard says “this Responses operation has no Chat Completions translation”. Accounts for 9 of the 10 templates the OpenResponses manifest marks inapplicable.TODO-ISSUE reject untranslatable Responses operations at the edge
Responses -> CCOversized request bodySee the shared oversized-body gap below.TODO-ISSUE format the 413 produced by the body-limit filter
CC -> GeminiStructured output / JSON schemajson_schema translates, but only json_schema.schema is read: name, description and strict are read by nothing. strict:true is accepted and silently ignored, so structured-output strictness is lost with no error.TODO-ISSUE reject or honor response_format.json_schema.strict
CC -> GeminiPrompt-cache token accountingcachedContentTokenCount is discarded; no prompt_tokens_details.cached_tokens is emitted.TODO-ISSUE map Gemini cachedContentTokenCount
CC -> GeminiReasoning / thinking contentthoughtsTokenCount is discarded, so no completion_tokens_details.reasoning_tokens is emitted. A Part with thought:true fails the response, while a Part carrying only thoughtSignature produces an empty delta and no error.TODO-ISSUE map Gemini thoughtsTokenCount
CC -> GeminiOversized request bodySee the shared oversized-body gap below.TODO-ISSUE format the 413 produced by the body-limit filter
CC -> AzureContent-filter / safety signal reaches the clientAzure’s per-choice content_filter_results, content_filter_offsets, content_filter_raw and top-level prompt_filter_results are stripped from every response with no replacement. A streaming frame carrying only annotations is dropped whole unless it also carries usage or other top-level metadata, in which case it is forwarded with empty choices. Customers relying on Azure safety metadata lose it entirely.TODO-ISSUE preserve or document Azure content-filter metadata
CC -> AzureOversized request bodySee the shared oversized-body gap below.TODO-ISSUE format the 413 produced by the body-limit filter
CC -> BedrockMulti-turn historyRoles and tool results translate correctly and client tool_call ids round-trip. But with no per-message allowlist, messages[].name, refusal, annotations and the deprecated function_call are never read and silently discarded, and a non-“function” tool_call type is accepted and emitted as a toolUse. An assistant turn with no content gets a synthetic empty-string block.TODO-ISSUE add a per-message field allowlist to the Bedrock translator
CC -> BedrockPrompt-cache token accountingcacheReadInputTokens and cacheWriteInputTokens are discarded.TODO-ISSUE map Bedrock cache token counters
CC -> BedrockReasoning / thinking contentStreaming reasoningContent deltas return Ok(None) with no output and no error. Citation, toolResult and image deltas take the same path and vanish silently.TODO-ISSUE reject or surface Bedrock reasoningContent and citations
CC -> BedrockOversized request bodySee the shared oversized-body gap below.TODO-ISSUE format the 413 produced by the body-limit filter

Surfaces

Anthropic Messages to OpenAI Chat Completions

  • Filters: anthropic_messages_to_chat_completions, anthropic_messages_to_chat_completions_stream
  • Dialect-shaped rejection also requires: anthropic_messages_request
  • Unrecognized fields: Sixteen top-level fields are rejected 400: thirteen Chat-only fields (service_tier, container, inference_geo, mcp_servers, n, logprobs, top_logprobs, audio, modalities, functions, function_call, web_search_options, moderation) and three Anthropic-only fields (thinking, context_management, cache_control) unless allow_lossy_features opts them in. Any other unrecognized top-level field is forwarded verbatim to the Chat backend (forward_unmapped_fields); a translated key wins over a forwarded one.
FeatureStatusNotesEvidence
Plain text completiontranslatedapis/src/anthropic/messages_to_chat_completions/request.rs:799-860
System / developer instructionslossyAn array of text blocks is joined with newlines into one string, so per-block boundaries are lost. A block carrying cache_control is rejected 400 by default; with allow_lossy_features: [prompt_caching] the marker is stripped and signalled (see the request-side controls row).apis/src/anthropic/messages_to_chat_completions/request.rs:838-860; apis/src/anthropic/messages_to_chat_completions/request.rs:476-481; apis/src/anthropic/messages_to_chat_completions/request.rs:682-690
Multi-turn historytranslatedIn-array system messages are accepted deliberately (Claude Code emits them).apis/src/anthropic/messages_to_chat_completions/request.rs:516-521
Image inputtranslatedUser messages only. An image in an assistant message, or in any message that also carries tool_use, is rejected 400.apis/src/anthropic/messages_to_chat_completions/request.rs:606-620; apis/src/anthropic/messages_to_chat_completions/request.rs:667
Audio inputn/a
Document / file inputrejectedAll document source types rejected 400; no PDF support.apis/src/anthropic/messages_to_chat_completions/request.rs:667
Client function toolstranslatedinput_schema is moved verbatim, strict is forwarded, and an absent description is synthesized as “”. Unknown tool keys are rejected 400.apis/src/anthropic/messages_to_chat_completions/request.rs:1465-1491; apis/src/anthropic/messages_to_chat_completions/request.rs:530-562
Tool choicetranslatedapis/src/anthropic/messages_to_chat_completions/request.rs:1514-1555; apis/src/anthropic/messages_to_chat_completions/request.rs:568-600
Parallel tool callstranslateddisable_parallel_tool_use true maps to parallel_tool_calls false.apis/src/anthropic/messages_to_chat_completions/request.rs:1499-1511
Provider-hosted tools (web search, file search, MCP, code interpreter)rejectedtools[].type must be absent or “custom”; typed server tools (web_search, bash, computer, code_execution) are rejected 400.apis/src/anthropic/messages_to_chat_completions/request.rs:533-540
Structured output / JSON schemalossyoutput_config.format becomes a strict response_format.json_schema, which matches Anthropic’s always-constrained output. OpenAI strict mode requires every property in required, so a schema with optional properties is rejected upstream by an OpenAI backend. output_config.effort maps to reasoning_effort; any other output_config key is rejected 400.apis/src/anthropic/messages_to_chat_completions/request.rs:1392-1403; apis/src/anthropic/messages_to_chat_completions/request.rs:1357-1389
Streaminglossystream_options.include_usage is force-set true, overriding a client false. stream_options without stream:true is rejected 400.apis/src/anthropic/messages_to_chat_completions/request.rs:1298-1316; apis/src/anthropic/messages_to_chat_completions/request.rs:487-496
Streaming terminates correctlygapTerminal events are emitted only from the [DONE] branch. If the upstream stream ends without the sentinel, no message_delta and no message_stop are sent — a truncated response reported as success. Anthropic’s documented ping keep-alive is never emitted either.apis/src/anthropic/messages_to_chat_completions_stream/mod.rs:539-541; apis/src/anthropic/messages_to_chat_completions_stream/mod.rs:931-948; apis/src/anthropic/messages_to_chat_completions_stream/mod.rs:344-372
Mid-stream upstream failure is surfacedgapA data: {"error":{...}} chunk after a 200 parses as a valid JSON object, matches neither choices nor usage, and is discarded. The client sees a short clean completion for a failed generation. Only non-JSON and non-object data fails the stream.apis/src/anthropic/messages_to_chat_completions_stream/mod.rs:559-575; apis/src/anthropic/messages_to_chat_completions_stream/mod.rs:583-616
Content-filter / safety signal reaches the clientgapNon-streaming refuses finish_reason content_filter (the upstream success is rejected with 500); streaming silently maps it to end_turn. Identical backend behavior yields opposite client-visible outcomes depending on stream.apis/src/anthropic/messages_to_chat_completions/response.rs:123-126; apis/src/anthropic/messages_to_chat_completions/mod.rs:400-411; apis/src/anthropic/messages_to_chat_completions_stream/mod.rs:1016-1022
Token usage accountinggapmessage_start.usage.input_tokens is hardcoded 0 rather than null, so clients reading prompt tokens at message_start get a wrong number, not a missing one. Finite-path usage is correct.apis/src/anthropic/messages_to_chat_completions_stream/mod.rs:1011-1013
Prompt-cache token accountinglossycache_read_input_tokens maps correctly; cache_creation_input_tokens is hardcoded null because Chat Completions has no cache-write counter, so cache-write billing is invisible.apis/src/anthropic/wire.rs:200-214; apis/src/anthropic/messages_to_chat_completions/response.rs:279-303; apis/src/anthropic/messages_to_chat_completions_stream/mod.rs:988-1008
Reasoning / thinking contentgapUpstream message.reasoning_content is never read on either path, so billed reasoning output vanishes. A non-null finite refusal is read and refused (the upstream success is rejected with 500); a streaming delta.refusal is not read.apis/src/anthropic/messages_to_chat_completions/response.rs:178-184; apis/src/anthropic/messages_to_chat_completions_stream/mod.rs:685-706; apis/src/anthropic/messages_to_chat_completions/response.rs:113-121
Request-side thinking / prompt-cache controlsrejectedthinking, context_management and cache_control are rejected 400 by default. With allow_lossy_features listing extended_thinking and/or prompt_caching, recognized markers are validated then stripped, and each real degradation is reported three ways: an x-degraded-features response header, a WARN log, and the praxis_anthropic_messages_to_chat_completions_degraded_total{feature} counter. Malformed markers and features not listed still return 400.apis/src/anthropic/messages_to_chat_completions/request.rs:59; apis/src/anthropic/messages_to_chat_completions/request.rs:471-474; apis/src/anthropic/messages_to_chat_completions/request.rs:194-221; apis/src/anthropic/messages_to_chat_completions/request.rs:377-418; apis/src/anthropic/messages_to_chat_completions/mod.rs:95; apis/src/anthropic/messages_to_chat_completions/mod.rs:476-490
Stop sequenceslossyA genuine stop-sequence stop is reported as end_turn with stop_sequence null whenever the backend does not surface the matched string (vLLM’s choice.stop_reason is consulted on both paths).apis/src/anthropic/messages_to_chat_completions/response.rs:247-266; apis/src/anthropic/messages_to_chat_completions_stream/mod.rs:604-610
Sampling parameterslossytemperature and top_p move verbatim with no rescaling. top_k is not mapped and is forwarded as a literal non-spec key, which vLLM accepts and OpenAI rejects.apis/src/anthropic/messages_to_chat_completions/request.rs:1319-1328; apis/src/anthropic/messages_to_chat_completions/request.rs:721-725
Logprobsrejectedapis/src/anthropic/messages_to_chat_completions/request.rs:34-48; apis/src/anthropic/messages_to_chat_completions/request.rs:737-750
Server-side state continuationn/a
Unsupported operations on a supported pathn/a
Error envelope matches the client dialectlossyUpstream errors carrying Anthropic vocabulary are preserved; a foreign error.type is replaced by the HTTP-status mapping and only message survives. OpenAI error.code and error.param are not read.apis/src/anthropic/messages_to_chat_completions/response.rs:22-31; apis/src/anthropic/messages_to_chat_completions/response.rs:131-154; apis/src/anthropic/wire.rs:58-86
Oversized request bodygapSee the shared oversized-body gap below.praxis-proxy-protocol-0.7.5/src/http/pingora/handler/request_body_filter.rs:99-127

OpenAI Responses to OpenAI Chat Completions

  • Filters: openai_responses_to_chat_completions
  • Dialect-shaped rejection also requires: openai_responses_request
  • Unrecognized fields: No allowlist and no typed deserialization. The Chat request is built field by field, so every unrecognized top-level field, sub-field, and content-part property is accepted and silently dropped. background other than false, truncation other than “disabled”, and a non-null moderation are rejected 400.
FeatureStatusNotesEvidence
Plain text completiontranslatedapis/src/openai/translation/chat_completions.rs:769-788
System / developer instructionstranslatedinstructions becomes a leading system message and is echoed on the response.apis/src/openai/translation/chat_completions.rs:643-647; apis/src/openai/translation/chat_completions.rs:1821-1823
Multi-turn historytranslatedAdjacent function_calls are batched into one assistant message.apis/src/openai/translation/chat_completions.rs:675-703
Image inputtranslatedinput_image with image_url is lowered correctly; file_id-only input is rejected 400.apis/src/openai/translation/chat_completions.rs:1031-1052
Audio inputrejectedapis/src/openai/translation/chat_completions.rs:958-968
Document / file inputtranslatedapis/src/openai/translation/chat_completions.rs:1056-1078
Client function toolstranslatedapis/src/openai/translation/chat_completions.rs:1093-1118
Tool choicetranslatedapis/src/openai/translation/chat_completions.rs:1440-1485
Parallel tool callstranslatedapis/src/openai/translation/chat_completions.rs:453
Provider-hosted tools (web search, file search, MCP, code interpreter)lossyweb_search is replaced by a synthesized private function taking a bounded queries array, while file_search is replaced by a synthesized private function taking a single query. search_context_size, user_location, vector_store_ids, max_num_results, filters and ranking_options are validated but never conveyed to the backend. MCP, code_interpreter and computer_use are rejected 400 unless the dispatch filters lower them first.apis/src/openai/translation/chat_completions.rs:1294-1320; apis/src/openai/translation/chat_completions.rs:1347-1371; apis/src/openai/translation/chat_completions.rs:1110-1121; apis/src/openai/responses/responses_to_chat_completions/mod.rs:588-595
Structured output / JSON schematranslatedapis/src/openai/translation/chat_completions.rs:563-585
Streamingtranslatedapis/src/openai/responses/responses_to_chat_completions/stream/mod.rs:700-741; apis/src/openai/responses/responses_to_chat_completions/stream/mod.rs:1430-1460; apis/src/openai/responses/responses_to_chat_completions/mod.rs:266-320
Streaming terminates correctlytranslatedapis/src/openai/responses/responses_to_chat_completions/stream/mod.rs:1430-1460
Mid-stream upstream failure is surfacedlossySurfaced as response.failed, which force-empties output and contradicts items already announced via output_item.added. The spec’s top-level error SSE event is never emitted.apis/src/openai/responses/responses_to_chat_completions/stream/mod.rs:1509-1573; apis/src/openai/responses/responses_to_chat_completions/stream/mod.rs:1946-1955; apis/src/openai/responses/responses_to_chat_completions/stream/events.rs:228-230
Content-filter / safety signal reaches the clienttranslatedcontent_filter maps to incomplete_details.reason.apis/src/openai/translation/chat_completions.rs:1802-1816
Token usage accountinglossyMissing values become 0 rather than null. audio_tokens and accepted/rejected_prediction_tokens are discarded.apis/src/openai/translation/chat_completions.rs:2213-2259
Prompt-cache token accountingtranslatedcached_tokens and cache_write_tokens are both carried.apis/src/openai/translation/chat_completions.rs:2223-2243
Reasoning / thinking contentrejectedUnder the default dialect none, reasoning input items are rejected 400 and upstream reasoning fields are not read. Under the vllm dialect reasoning round-trips on both the finite and the streaming path (delta.reasoning and delta.reasoning_content become response.reasoning_text events). reasoning.summary is rejected unconditionally — no dialect reports supports_safe_summary.apis/src/openai/translation/reasoning.rs:57-62; apis/src/openai/translation/reasoning.rs:106-123; apis/src/openai/translation/reasoning.rs:186-205; apis/src/openai/responses/responses_to_chat_completions/stream/mod.rs:700-741
Request-side thinking / prompt-cache controlstranslatedreasoning.effort maps to reasoning_effort regardless of dialect. prompt_cache_key, prompt_cache_retention and prompt_cache_options are copied verbatim. reasoning.summary is rejected 400 (see reasoning-content).apis/src/openai/translation/chat_completions.rs:557-561; apis/src/openai/translation/chat_completions.rs:453-456
Stop sequencesn/a
Sampling parameterstranslatedapis/src/openai/translation/chat_completions.rs:446-470
Logprobslossytop_logprobs force-injects logprobs:true into the Chat request, a field the client never set, and the result is surfaced regardless of include.apis/src/openai/translation/chat_completions.rs:548-554; apis/src/openai/translation/chat_completions.rs:1792-1799
Server-side state continuationgapprevious_response_id and conversation rehydrate correctly, but on continuation any stored output item that is not message, function_call, function_call_output, reasoning, compaction or item_reference is skipped with no error — the model silently loses hosted-tool history. Separately, an unknown previous_response_id returns 400 where OpenAI returns 404.apis/src/openai/responses/rehydrate/mod.rs:1252-1277; apis/src/openai/responses/rehydrate/mod.rs:1324-1327
Unsupported operations on a supported pathgapThe GET /v1/responses WebSocket upgrade is positively classified as Responses traffic, and POST /v1/responses/compact is too unless openai_responses_compact is in the chain to answer it locally. The translator passes anything that is not POST /v1/responses through untouched, so both reach a Chat-Completions-only backend verbatim. No guard says “this Responses operation has no Chat Completions translation”. Accounts for 9 of the 10 templates the OpenResponses manifest marks inapplicable.apis/src/classifier/mod.rs:116-118; apis/src/openai/responses/responses_to_chat_completions/mod.rs:530-533; apis/src/openai/responses/routes/mod.rs:200-208; apis/src/openai/responses/routes/mod.rs:256-264; apis/src/openai/responses/compact/mod.rs:331-376
Error envelope matches the client dialectlossytype and code are always set to the same string, where OpenAI usually leaves code null or distinct. Provider param, type and extra fields are discarded on upstream errors.apis/src/openai/responses/error.rs:20-37; apis/src/openai/responses/responses_to_chat_completions/error.rs:42-79
Oversized request bodygapSee the shared oversized-body gap below.praxis-proxy-protocol-0.7.5/src/http/pingora/handler/request_body_filter.rs:99-127

OpenAI Chat Completions to Vertex AI Gemini

  • Filters: openai_chat_completions_to_vertexai_gemini
  • Dialect-shaped rejection: emitted by the translator itself.
  • Unrecognized fields: Strict hand-written top-level allowlist plus a per-message field allowlist. Anything outside both is rejected 400.
FeatureStatusNotesEvidence
Plain text completiontranslatedapis/src/vertex/gemini/request.rs:375-386
System / developer instructionstranslatedMultiple system/developer messages merge in order into systemInstruction.apis/src/vertex/gemini/request.rs:344-368
Multi-turn historytranslatedConsecutive tool results are merged into one user turn as Gemini requires. Client tool_call ids are deliberately not sent and do not round-trip; response ids reuse Vertex’s functionCall.id when present and are minted otherwise.apis/src/vertex/gemini/request.rs:531-572; apis/src/vertex/gemini/request.rs:591-598; apis/src/vertex/gemini/response.rs:383-395
Image inputlossyA base64 data URI translates faithfully. An http URL becomes fileData with the MIME type guessed from the file extension, silently defaulting to image/jpeg for anything not .png/.gif/.webp. image_url.detail other than auto is rejected 400.apis/src/vertex/gemini/request.rs:405-413; apis/src/vertex/gemini/request.rs:427-463
Audio inputrejectedapis/src/vertex/gemini/request.rs:415
Document / file inputrejectedapis/src/vertex/gemini/request.rs:415
Client function toolslossyparameters JSON Schema is copied verbatim with no conversion to Gemini’s OpenAPI subset, so unsupported keywords reach Vertex unchanged and fail upstream rather than at the edge. function.strict true is rejected 400.apis/src/vertex/gemini/request.rs:835-886
Tool choicetranslatedapis/src/vertex/gemini/request.rs:898-927
Parallel tool callsrejectedOnly the default true is accepted and discarded; false is rejected 400.apis/src/vertex/gemini/request.rs:119-127
Provider-hosted tools (web search, file search, MCP, code interpreter)rejectedapis/src/vertex/gemini/request.rs:844-846
Structured output / JSON schemagapjson_schema translates, but only json_schema.schema is read: name, description and strict are read by nothing. strict:true is accepted and silently ignored, so structured-output strictness is lost with no error.apis/src/vertex/gemini/request.rs:146-151; apis/src/vertex/gemini/request.rs:794-819
Streamingtranslatedapis/src/vertex/gemini/response.rs:601-629; apis/src/vertex/gemini/response.rs:731-773; apis/src/vertex/gemini/mod.rs:405-455
Streaming terminates correctlytranslatedGemini never sends [DONE]; it is synthesized proxy-side on a clean finish only, and a provider-sent [DONE] is swallowed.apis/src/vertex/gemini/mod.rs:377-386; apis/src/vertex/gemini/mod.rs:428-434
Mid-stream upstream failure is surfacedtranslatedFrames with no candidates but an error or promptFeedback.blockReason become a terminal in-band server_error SSE frame with no [DONE].apis/src/vertex/gemini/response.rs:615-622; apis/src/vertex/gemini/response.rs:816-843; apis/src/vertex/gemini/mod.rs:447-451; apis/src/vertex/gemini/mod.rs:462-471
Content-filter / safety signal reaches the clienttranslatedSAFETY, RECITATION, BLOCKLIST, PROHIBITED_CONTENT, SPII, IMAGE_SAFETY, LANGUAGE, OTHER and MODEL_ARMOR all map to content_filter. Unknown finish reasons are a hard error, not a silent “stop”.apis/src/vertex/gemini/response.rs:446-493
Token usage accountinglossycompletion_tokens comes from candidatesTokenCount, which excludes thoughtsTokenCount, so on thinking models it under-reports billed output and total_tokens no longer equals prompt + completion.apis/src/vertex/gemini/response.rs:500-513
Prompt-cache token accountinggapcachedContentTokenCount is discarded; no prompt_tokens_details.cached_tokens is emitted.apis/src/vertex/gemini/response.rs:500-513
Reasoning / thinking contentgapthoughtsTokenCount is discarded, so no completion_tokens_details.reasoning_tokens is emitted. A Part with thought:true fails the response, while a Part carrying only thoughtSignature produces an empty delta and no error.apis/src/vertex/gemini/response.rs:302-304; apis/src/vertex/gemini/response.rs:324-336; apis/src/vertex/gemini/response.rs:500-513; apis/src/vertex/gemini/response.rs:876-878
Request-side thinking / prompt-cache controlsrejectedreasoning_effort and prompt_cache_key are outside the top-level allowlist and are rejected 400; there is no thinkingConfig mapping. A null value is accepted and dropped.apis/src/vertex/gemini/request.rs:99-127
Stop sequencestranslatedapis/src/vertex/gemini/request.rs:129-136; apis/src/vertex/gemini/request.rs:775-785
Sampling parameterslossyValues are copied verbatim with no range validation. top_logprobs is copied even when logprobs is absent or false, which OpenAI forbids; there is no coupling check.apis/src/vertex/gemini/request.rs:754-771
Logprobstranslatedapis/src/vertex/gemini/response.rs:528-563
Server-side state continuationn/a
Unsupported operations on a supported pathn/a
Error envelope matches the client dialectlossyOpenAI-shaped with param:null. Differs from the Bedrock filter’s envelope — see the cross-surface gap below.apis/src/vertex/wire.rs:151-164
Oversized request bodygapSee the shared oversized-body gap below.praxis-proxy-protocol-0.7.5/src/http/pingora/handler/request_body_filter.rs:99-127

OpenAI Chat Completions to Azure AI Chat Completions

  • Filters: openai_chat_completions_to_azureai_chat_completions
  • Dialect-shaped rejection also requires: ai_operation
  • Unrecognized fields: None. This filter is not a translator: the entire request-side transform is obj.remove(“model”). There is no allowlist, no validation, and no rejection path — on_request_body always returns Continue, so a malformed JSON body is forwarded unchanged with no 400, and a body model that disagrees with the deployment name in the URL is dropped without a word.
FeatureStatusNotesEvidence
Plain text completionpassthroughapis/src/azure/to_openai/request.rs:19-26
System / developer instructionspassthroughapis/src/azure/to_openai/request.rs:19-26
Multi-turn historypassthroughapis/src/azure/to_openai/request.rs:19-26
Image inputpassthroughapis/src/azure/to_openai/request.rs:19-26
Audio inputpassthroughapis/src/azure/to_openai/request.rs:19-26
Document / file inputpassthroughapis/src/azure/to_openai/request.rs:19-26
Client function toolspassthroughapis/src/azure/to_openai/request.rs:19-26
Tool choicepassthroughapis/src/azure/to_openai/request.rs:19-26
Parallel tool callspassthroughapis/src/azure/to_openai/request.rs:19-26
Provider-hosted tools (web search, file search, MCP, code interpreter)passthroughapis/src/azure/to_openai/request.rs:19-26
Structured output / JSON schemapassthroughSupport depends entirely on the configured api_version, not on Praxis.apis/src/azure/to_openai/request.rs:19-26
StreamingpassthroughThe request stream flag is never read; response mode is chosen from the upstream Content-Type.apis/src/azure/to_openai/mod.rs:252-269
Streaming terminates correctlypassthroughapis/src/azure/to_openai/mod.rs:311-347
Mid-stream upstream failure is surfacedpassthroughapis/src/azure/to_openai/mod.rs:320-337
Content-filter / safety signal reaches the clientgapAzure’s per-choice content_filter_results, content_filter_offsets, content_filter_raw and top-level prompt_filter_results are stripped from every response with no replacement. A streaming frame carrying only annotations is dropped whole unless it also carries usage or other top-level metadata, in which case it is forwarded with empty choices. Customers relying on Azure safety metadata lose it entirely.apis/src/azure/to_openai/response.rs:22; apis/src/azure/to_openai/response.rs:25; apis/src/azure/to_openai/response.rs:44-67; apis/src/azure/to_openai/response.rs:114-139; apis/src/azure/to_openai/mod.rs:362-372
Token usage accountingpassthroughapis/src/azure/to_openai/response.rs:52-57
Prompt-cache token accountingpassthroughapis/src/azure/to_openai/request.rs:19-26
Reasoning / thinking contentpassthroughapis/src/azure/to_openai/request.rs:19-26
Request-side thinking / prompt-cache controlspassthroughapis/src/azure/to_openai/request.rs:19-26
Stop sequencespassthroughapis/src/azure/to_openai/request.rs:19-26
Sampling parameterspassthroughapis/src/azure/to_openai/request.rs:19-26
Logprobspassthroughapis/src/azure/to_openai/request.rs:19-26
Server-side state continuationn/a
Unsupported operations on a supported pathn/a
Error envelope matches the client dialectlossyerror.type is filled from the HTTP status; Azure’s innererror is stripped.apis/src/azure/wire.rs:20-58
Oversized request bodygapSee the shared oversized-body gap below.praxis-proxy-protocol-0.7.5/src/http/pingora/handler/request_body_filter.rs:99-127

OpenAI Chat Completions to AWS Bedrock Converse

  • Filters: openai_chat_completions_to_bedrock_converse
  • Dialect-shaped rejection: emitted by the translator itself.
  • Unrecognized fields: Strict hand-written top-level allowlist plus a no-op-default escape hatch. There is no per-message allowlist, unlike the Vertex filter.
  • Documentation gap: docs/filters/openai_chat_completions_to_bedrock_converse.md does not exist and the filter is absent from docs/filters/reference.md, despite being registered at filters/src/register.rs:123.
FeatureStatusNotesEvidence
Plain text completiontranslatedapis/src/bedrock/converse/request.rs:371-374
System / developer instructionstranslatedapis/src/bedrock/converse/request.rs:266-306
Multi-turn historygapRoles and tool results translate correctly and client tool_call ids round-trip. But with no per-message allowlist, messages[].name, refusal, annotations and the deprecated function_call are never read and silently discarded, and a non-“function” tool_call type is accepted and emitted as a toolUse. An assistant turn with no content gets a synthetic empty-string block.apis/src/bedrock/converse/request.rs:314-346; apis/src/bedrock/converse/request.rs:415-484
Image inputrejectedRejected 400 — a translator limitation, not a provider one. Bedrock Converse supports images natively.apis/src/bedrock/converse/request.rs:387-389
Audio inputrejectedapis/src/bedrock/converse/request.rs:390-392
Document / file inputrejectedapis/src/bedrock/converse/request.rs:390-392
Client function toolslossystrict is forwarded as toolSpec.strict, a documented Converse ToolSpecification field. Whether the model behind the endpoint honours or rejects it is up to Bedrock; no test asserts either.apis/src/bedrock/converse/request.rs:626-657
Tool choicelossy“none” omits the entire toolConfig, which also drops every tools[] definition — the model loses all tool schema awareness instead of merely being told not to call.apis/src/bedrock/converse/request.rs:600-602; apis/src/bedrock/converse/request.rs:690
Parallel tool callsrejectedapis/src/bedrock/converse/request.rs:141-144; apis/src/bedrock/converse/request.rs:217
Provider-hosted tools (web search, file search, MCP, code interpreter)rejectedapis/src/bedrock/converse/request.rs:628-630
Structured output / JSON schemarejectedresponse_format json_object and json_schema are rejected 400 — a translator limitation. Converse supports structured output through outputConfig.textFormat.apis/src/bedrock/converse/request.rs:140-145; apis/src/bedrock/converse/request.rs:216
Streamingtranslatedapis/src/bedrock/converse/response.rs:286-310; apis/src/bedrock/converse/response.rs:317-511
Streaming terminates correctlytranslated[DONE] is appended only on a clean end of stream; truncation and decode failures become in-band SSE error frames instead.apis/src/bedrock/converse/mod.rs:414-434
Mid-stream upstream failure is surfacedtranslatedException and truncated frames become an in-band SSE error frame with a typed code and no [DONE].apis/src/bedrock/converse/mod.rs:414-430; apis/src/bedrock/converse/mod.rs:453-509; apis/src/bedrock/converse/response.rs:514-552
Content-filter / safety signal reaches the clienttranslatedcontent_filtered and guardrail_intervened both map to content_filter. An unknown stopReason fails the response instead of defaulting to stop.apis/src/bedrock/converse/response.rs:70-95; apis/src/bedrock/converse/response.rs:122-127
Token usage accountinglossyMissing token fields default to 0 rather than erroring.apis/src/bedrock/converse/response.rs:221-240
Prompt-cache token accountinggapcacheReadInputTokens and cacheWriteInputTokens are discarded.apis/src/bedrock/converse/response.rs:221-240
Reasoning / thinking contentgapStreaming reasoningContent deltas return Ok(None) with no output and no error. Citation, toolResult and image deltas take the same path and vanish silently.apis/src/bedrock/converse/response.rs:441-451
Request-side thinking / prompt-cache controlsrejectedreasoning_effort (even at OpenAI’s default “medium”) and prompt_cache_key are outside the allowlist and rejected 400.apis/src/bedrock/converse/request.rs:140-166
Stop sequencestranslatedapis/src/bedrock/converse/request.rs:572-586
Sampling parameterstranslatedtemperature, top_p and max_tokens translate with type validation. seed, user and top_logprobs are rejected 400.apis/src/bedrock/converse/request.rs:140-166; apis/src/bedrock/converse/request.rs:210-223; apis/src/bedrock/converse/request.rs:546-570
Logprobsrejectedapis/src/bedrock/converse/request.rs:214
Server-side state continuationn/a
Unsupported operations on a supported pathn/a
Error envelope matches the client dialectlossyOpenAI-shaped but omits param and uses code “invalid_request”. Differs from the Vertex filter’s envelope — see the cross-surface gap below.apis/src/bedrock/wire.rs:61-72; apis/src/bedrock/wire.rs:113-126
Oversized request bodygapSee the shared oversized-body gap below.praxis-proxy-protocol-0.7.5/src/http/pingora/handler/request_body_filter.rs:99-127

Native surfaces

SurfaceOperationsStatusNotesEvidence
OpenAI Conversations8nativeAll 8 operations are HandlingMode::Local; Praxis owns the payload and terminates locally. Handlers emit OpenAI-shaped 400/404/500 themselves.apis/src/openai/conversations/routes.rs:300-436
OpenAI Responses1nativePOST /v1/responses is HandlingMode::Transform with an owned contract.apis/src/openai/responses/routes/mod.rs:187-199
OpenAI Responses3passthroughGET /v1/responses/{id}, DELETE, and GET …/input_items are classified passthrough but are terminated locally when openai_responses_store is in the chain. Registry mode is a classification default, not a transport guarantee.apis/src/openai/responses/routes/mod.rs:210-243; apis/src/openai/responses/store/filter.rs:1031-1051; apis/src/openai/responses/store/filter.rs:1188-1230; apis/src/openai/responses/store/filter.rs:172-196
OpenAI Chat Completions6passthroughRecognized by the registry with owned_contract: None. Outside conformance scope — no oasdiff, no capability claim, no runtime suite.apis/src/openai/chat_completions/routes/mod.rs:104,112-161
Anthropic Messages8passthroughA head-based registry declares 8 operations: create-message is inspected, count_tokens and the 6 batch operations pass through with their bodies untouched. The protocol filter only injects anthropic-version and preserves anthropic-beta. No conformance gate.apis/src/anthropic/protocol/mod.rs:76-113; apis/src/anthropic/routes.rs:194-259; apis/src/anthropic/messages_request/mod.rs:216-239

The report’s “15/15, 100%” counts non-beta Conversations and Responses operations only — 7 of 14 projected Responses operations are scored. Publish the qualified number, never the bare percentage.