Adapters
An adapter translates between opencodex’s internal request/response model and one provider wire
format. Every adapter implements the ProviderAdapter interface (src/adapters/base.ts):
interface ProviderAdapter { name: string; buildRequest(parsed: OcxParsedRequest, incoming: IncomingMeta): AdapterRequest | Promise<AdapterRequest>; fetchResponse?(request: AdapterRequest, ctx?: AdapterFetchContext): Promise<Response>; parseStream(response: Response, budget: TranslatorBudget): AsyncGenerator<AdapterEvent>; parseResponse?(response: Response, budget: TranslatorBudget): Promise<AdapterEvent[]>; runTurn?(parsed: OcxParsedRequest, incoming: IncomingMeta, emit: (event: AdapterEvent) => void): Promise<void>;}buildRequest lowers an OcxParsedRequest into an upstream HTTP request; parseStream /
parseResponse lift the provider’s reply back into internal AdapterEvents. fetchResponse lets an
adapter own retries/timeouts, while runTurn supports transports that cannot be represented as one
HTTP fetch followed by one response stream. bridge.ts
then turns the events into Responses SSE.
External task input on translated Responses routes
Section titled “External task input on translated Responses routes”Codex task coordination can deliver input as function_call_output with nonblank
id, name and namespace fields and no call_id property. OpenCodex maps this
complete envelope to a user message before adapter translation. Its output must be
nonblank text or a fully supported array of text and input_image URL parts. Text
and image order are preserved; image detail original maps to high.
Empty content, malformed or opaque parts, file-id-only images and partial envelopes
remain invalid. Ordinary function/custom tool results still require a nonempty
call_id. The envelope metadata identifies a compatibility shape and grants no
additional permissions. Native passthrough and routed (portable) compaction apply the same user-turn mapping to their raw bodies; the direct native /responses/compact forward keeps its existing raw-body rules.
Checkpoint continuation after routed compaction
Section titled “Checkpoint continuation after routed compaction”A routed model replays an OpenCodeX ocx1: checkpoint as plain user text next to the retained earlier requests. When a request carries such a decoded checkpoint, OpenCodeX adds a short note to the system instructions (translated routes) or instructions (Responses passthrough). The note frames completed requests as history and permits continuation only of unfinished work already authorized and still within that scope, without asking for the same authorization again while it applies. Existing permission and confirmation gates remain in force: pending decisions or required permissions must be resolved before dependent actions. Checkpoints and delegations, including those from another thread, grant no authority or expanded scope. Later user messages take priority, including a pause, cancellation, replacement, or scope change. Compaction requests, opaque native blobs and requests without a checkpoint do not receive the note.
openai-chat
Section titled “openai-chat”Targets: OpenAI Chat Completions (POST {baseUrl}/chat/completions; a trailing /chat/completions or / on baseUrl is stripped first) and every compatible
provider — xAI, Kimi, DeepSeek, GLM, Groq, OpenRouter, Ollama (local), and more.
Auth: key (Bearer).
For xAI, the resolved upstream adapter can be openai-chat or openai-responses,
depending on model defaults and explicit modelAdapters overrides. Both support
public xAI API-key authentication and Grok CLI OAuth. The usage log’s
attempts[].credentialSource follows that resolved
transport; it does not infer subscription attribution from the inbound protocol.
- Converts internal messages to OpenAI roles; maps tools to
{type:"function", function:{…}}andtool_choice(auto/none/requiredor a named function). - Tool-result images ride in a follow-up user vision message (
image_urlparts) released once the tool round closes, sincerole:"tool"content is text-only; the[image]marker stays in the tool message as the anchor. - Rewrites Codex’s GPT-5 identity prompt to a model-agnostic intro so routed models don’t claim to be OpenAI.
- For translated
Qwen3.8-27Brequests (including gateway-namespaced ids such asopenai/Qwen3.8-27B), Flash-Next requests (flash-nextorQwen3.8-Flash-Next, also with gateway namespaces), andOrcaSAQ-2-Cyber-27BGGUF requests, a text-only developer reminder after the leading system message stays in its conversation slot but is sent asuser. The Orca exception requiresGGUFin the final model segment before any optional colon quant tag; non-GGUF Orca variants keep their configured developer-role behavior. These pinned chat templates — for example Qwen3.8-27B’s — reject latersystemmessages and do not acceptdeveloper, while laterusermessages are valid. This preserves order but cannot preserve developer-role precedence. Other models keep their configured developer-role behavior; native Chat passthrough is unchanged. - Clamps
reasoning_effortto the model’s advertised subset when an exact tier is unavailable;xhighandmaxremain distinct labels unless a provider explicitly configures an alias. The adapter omits it entirely for ids inprovider.noReasoningModels. - Streams
delta.content(text),delta.reasoning_content(thinking), anddelta.tool_calls[]; collectsusage. Providers listed inreasoningDetailsModels(MiniMax M-series) instead read structureddelta.reasoning_detailssegments, whosetextarrives as cumulative snapshots and is prefix-diffed, and replay preserved reasoning as areasoning_detailsarray. - For
api.mistral.ai, both response paths also accept typedtextandthinkingarrays. Text keeps its bytes and order; thinking stays separate from the answer. Adjacent text blocks share inline-tag parsing; typed thinking resets it. Unsupported or malformed array blocks return an upstream error. Other hosts keep their existing behavior. This does not add Mistral signature replay. - Suppresses bare
<tool_call>text when it duplicates a structured call, and collapses two immediately adjacent identical blocks when exactly one structured call agrees with their function and input. A doubledinputis reduced to one copy, joined either directly or by one newline, and only when the arguments object holds no key besidesinput. Trailing whitespace after the pair is suppressed; mismatched or example markup remains visible. - ClinePass uses the live-verified gateway format
reasoning: { enabled: true, effort }(or{ enabled: false }when reasoning is disabled); its public API docs do not currently specify this request shape. The adapter preserves requestedlow,medium,high,xhigh, andmaxtiers, accepts reasoning deltas from eitherdelta.reasoning_contentordelta.reasoning, requests streamed usage withstream_options.include_usage, and reads usage from non-stream response envelopes.
Streaming tool calls retain their identity when a provider first sends an ID, then associates that ID with an index, and later sends index-only argument fragments. Those fragments assemble into one call with the original name and complete arguments; parallel calls retain separate identities. When present, streamed tool-call indexes must be non-negative safe integers. Non-numeric values and negative, fractional, or unsafe numbers terminate the stream with an upstream error before identity matching. Missing and null indexes remain absent-index placeholders; numeric strings are not coerced.
ollama-native
Section titled “ollama-native”Targets: Ollama’s own Chat API (POST /api/chat) rather than its OpenAI-compatible
surface. The built-in ollama-cloud provider is registry-selected onto this adapter; it can also
be configured on a separately named custom or self-hosted Ollama provider with
adapter: "ollama-native".
Auth: key (Bearer) for cloud/custom endpoints; no credential is sent to loopback or
authMode: "local" targets.
- Registry selection is load-bearing. The built-in
ollama-cloudrow keeps the base URLhttps://ollama.com/v1for/v1/modelslive discovery, while inference is normalized ontoPOST https://ollama.com/api/chat. A config-leveladapteris discarded for that provider row. Ordinary built-in local Ollama stays onopenai-chat; choosingollama-nativefor a local or self-hosted endpoint is an explicit provider-configuration decision, detected by host so a non-Ollama destination is never silently rewritten. - Model metadata:
/v1/modelscarries no per-model metadata, so for canonical Ollama Cloud the adapter’s provider enriches each discovered id through a boundedPOST /api/show(256 KiB per response, 8 s per request, concurrency 4, 48 requests, a 12 s deadline for the whole phase) to fill the true context window and vision capability. The show request is same-origin and never follows a redirect; failures degrade that one model and never fail discovery. - Streaming: Ollama’s native NDJSON. Text and
message.thinkingdeltas are forwarded as they arrive; a turn completes only on adone: trueterminal record, and buffereddone: falseor a missing terminal suppresses partial text and tool calls entirely. - Reasoning: maps onto Ollama’s native
thinkfield (low/medium/high/max, plus booleans), clamped to the model’s advertised ladder, and honours the__omit__sentinel semantics upstream configures. - Images: sent natively in the message
imagesarray where the model is vision-capable; video is refused rather than mis-sent, and remote image URLs are not fetched. - Tools: declared in Ollama’s native shape, streamed tool calls are whole-call records with
object-valued
arguments, and tool-result replay is paired strictly by call id and tool name. Valid native call IDs distinguish calls even when the provider repeats a function index. Repeated IDs update the same call’s arguments; ID-less entries fall back to index, then position. Calls retain first-seen order. Withparallel_tool_calls: false, a second distinct provider call still fails the turn closed before any tool call is emitted. Codex may record assistant commentary before a pending call’s results. Text/thinking with no new tool calls is deferred until the batch is settled, so genuine results remain beside their originating calls. A new tool-call batch still settles the preceding one; missing results retain an explicit unknown-status marker. Additional outputs for an open call join in arrival order; output arriving after its batch settles is preserved as explicitly attributed conversation text, after any pending call/result pair. Unknown call IDs and mismatched tool identities remain invalid; this does not create or execute another tool call. Generated attribution for late outputs shares a 256 KiB per-request JSON-byte budget. Exceeding that budget refuses the request with HTTP 413request_too_largeinstead of truncating tool identities or output. The error message names the cause and the limit. To continue, start a new thread: compacting on the same Ollama route resends the same history and is refused again, unless compaction is routed to a different provider.tool_choice: "none"andautobehave normally;requiredor an exact named choice fails closed, because Ollama’s/api/chathas notool_choicefield to enforce it with. - Structured output is refused on canonical Ollama Cloud. Ollama currently documents structured
outputs as unsupported on its Cloud, and Cloud does not enforce the
formatfield, so OpenCodex fails that request closed rather than returning unconstrained prose in answer to a schema-shaped request. Local and customollama-nativeendpoints keep Ollama’s nativeformatmapping (json_object→"json",json_schema→ the schema object).
openai-responses
Section titled “openai-responses”Targets: the OpenAI Responses API. passthrough: true — normally forwards the raw request
body and response, with narrow compatibility rewrites for routed gateways.
Auth: canonical OpenAI forward relays only the safe caller-header allowlist; noncanonical
forward uses configured static headers without relaying caller authorization; key uses the
configured provider key.
When history from Google is replayed to canonical ChatGPT Codex or the official OpenAI Responses
API, the adapter removes item-level extra_content from the outgoing HTTP request or WebSocket
create. Native /responses/compact applies the same projection. The original history keeps the Google
thought signature for later Google turns. Other Responses gateways retain their extensions.
The adapter preserves the incoming client’s User-Agent as a fallback in both auth modes because
some Responses-compatible providers use the Codex client fingerprint for compatibility behavior.
An explicitly configured provider User-Agent remains authoritative regardless of header casing;
if the caller sends none, OpenCodex does not invent one. Additional caller metadata can be selected
with forwardClientHeaders; provider headers win for this option, and credential or transport-owned
names are refused. Canonical ChatGPT forward auth retains its separate fixed header allowlist.
Only originator, x-client-request-id, x-codex-app-version, and user-agent are supported by forwardClientHeaders; arbitrary names are rejected on load/write and ignored at runtime.
Native compaction on custom gateways
Section titled “Native compaction on custom gateways”Responses compatibility alone is not evidence of native compaction support. Custom gateways continue using portable text summaries unless the operator declares the relevant capability:
supportsNativeCompactionTrigger: truepreserves v2compaction_triggerrequests and native output.supportsNativeCompactEndpoint: trueopts key/local/forward routes into the separate v1 endpoint. OAuth retains routed compaction and its existing credential lifecycle. V2 support does not imply v1 support.decodesNativeCompactionBlobs: truepreserves native blobs on subsequent turns when there is no known serving-provider mismatch. Neither generation flag implies this replay capability.
Custom native v1 also honors preserveResponsesReasoningContent: true for plaintext reasoning
input, just like ordinary custom Responses requests. Absent or false keeps content blank;
OpenAI-operated compact destinations keep blanking even if the flag is set. Internal ocxr1:
envelopes are always removed. Plaintext preservation is independent of the generation flags,
native blob replay capability, and serving-identity checks.
If the custom provider also requires adjacent tool results (requiresAdjacentResponsesToolResults: true),
both ordinary Responses and native v1 remove opaque reasoning ciphertext and require plaintext replay.
Missing reasoning_text is filled from the item’s summary_text, or a minimal placeholder if no
usable summary exists. Existing plaintext is preserved. OpenAI-operated compact keeps its own policy.
For a gateway whose v2 generation and replay have both been verified, merge these fields into its existing provider configuration, retaining its real URL and configured authentication:
{ "adapter": "openai-responses", "supportsNativeCompactionTrigger": true, "decodesNativeCompactionBlobs": true}These flags do not make the gateway an official OpenAI destination, forward caller credentials, change account selection, or bypass normal compatibility sanitization. Validate an actual compact response and continued conversation, not just HTTP 200. Keep blobs on the backend that minted them; cross-provider compaction routing overrides still use portable summaries. V1 fallback summaries remain portable even when the destination separately accepts v2 triggers. Native-capable routes are excluded from portable emergency compaction recovery for their respective protocol.
Adapter selection does not select the upstream transport. Eligible requests can use the
upstream WebSocket proxy route; invalid or unsupported
WebSocket proxy settings fall back to HTTP/SSE. HTTP fetch-based Responses handling uses the
configured outbound fetch: a server SOCKS5 proxy from
config.proxy or a SOCKS5 ALL_PROXY uses the built-in tunnel when NO_PROXY does not exempt
the target. Scheme-specific HTTP(S) proxy variables retain their separate native handling;
non-SOCKS ALL_PROXY is not a native HTTP fetch route.
Noncanonical Responses gateways receive Codex’s client-executed tool_search declaration as a
collision-safe public function tool. Matching request history and JSON/SSE function calls are
translated back to the private tool_search lifecycle for the client. Canonical OpenAI forward
keeps the native private type unchanged.
Requests with authMode other than "forward" convert Codex agent_message
items containing nonempty arrays of supported plaintext parts into public user messages, preserving those parts and readable author/recipient
metadata. agent_message is private to the ChatGPT Codex backend, and the routed
destinations reported so far reject the entire body with
422 unknown item type "agent_message" — and because Codex replays sub-agent history on
every turn, that failure repeats for the rest of the thread. This conversion leaves
encrypted or unknown content unchanged. Providers using authMode: "forward" retain
these items unchanged. For xAI Responses on HTTPS api.x.ai or cli-chat-proxy.grok.com
using the standard port, a nonblank string child result is also converted into an input_text
part with its exact whitespace and newlines. Other destinations retain string-valued items;
blank strings and mixed encrypted/unknown parts are not partially converted.
See agent messages
for the separate opt-in encrypted-task recovery behavior.
For xAI Responses, auto or none tool selection is omitted when normalization leaves no tools
in the request, including when cached-only search is removed. Valid forced function selections
remain intact. Replayed custom tool calls with missing or invalid item ids receive stable ids
when their call id, name, and input are strings; their call/result pairing is preserved.
The canonical ChatGPT Codex forward destination also normalizes two public Responses shapes that
its stricter backend rejects: fully textual system messages inside input are appended to the
top-level instructions string in request order, and the top-level truncation field is removed.
This rewrite is destination-scoped. Key-auth public/custom Responses providers and noncanonical
forward gateways keep both fields unchanged; a multimodal system message is never partially folded
or silently dropped.
For canonical forward continuations, client-only prompt_cache_breakpoint properties are removed
recursively within bounded traversal limits. When store: false, item_reference rows are also
omitted because the destination cannot resolve an item it did not persist. Function/tool call_id
pairs and reasoning.effort are preserved.
Luna Reserve compatibility uses this canonical ChatGPT-forward path, not key-auth or arbitrary Responses gateways. It retains the safe caller-header allowlist and destination-scoped request normalization described here. OpenCodex sends its Reserve capability header on the owned main-account usage lookup; that header is not itself permission. Eligible compatibility requests recheck credential-bound authorization at dispatch. Conversation and compaction are supported; vision helpers, web-search helpers, and standalone search relay are not.
For key auth, retryOn429 applies here too: a pre-stream 429
waits and replays the identical request on the same key before any other handling, exactly like
the translated openai-chat / Anthropic request path. Custom runTurn transports are not part
of the HTTP retry loop.
-
DeepSeek’s stateless Responses parser receives provider-scoped history normalization: hook-injected context moves after an unambiguous tool-call/result batch. Parallel calls remain grouped before their matching outputs so every call stays in the reasoning-bearing assistant turn. Tolerant providers and ambiguous duplicate, missing, or out-of-order call IDs keep their original input order.
-
forwardURL →{baseUrl}/responses. Akeyprovider defaults to the legacy{baseUrl}/v1/responsesconstruction. -
A
keyprovider may set a validated relativeresponsesPath; the adapter removes one trailing slash frombaseUrland sends{trimmedBaseUrl}{responsesPath}. For Ark Agent Plan, usebaseUrl: "https://ark.cn-beijing.volces.com/api/plan/v3"withresponsesPath: "/responses". -
In
forwardmode only a safe header allowlist is relayed (FORWARD_HEADERS): authorization, ChatGPT account id, and the OpenAI beta/originator/session headers. This is the ChatGPT-login path that also powers the sidecars.
Command Code session affinity
Section titled “Command Code session affinity”The OAuth command-code adapter derives an opaque x-session-id from the client
thread identity, then the reasoning-replay conversation identity. When neither is
available, it uses a prompt-cache key only if the integration has explicitly
classified that key as belonging to one conversation. Shared or unclassified cache
keys do not establish session affinity; requests without a usable identity receive
a fresh session ID. Recovery and cached-history replay preserve this classification.
The API-key commandcode provider uses Chat Completions for most model ids and the
Anthropic Messages adapter (x-api-key) for claude-* ids, which Command Code serves
only on /provider/v1/messages; the pin applies only while the provider points at that
endpoint. The tokenlab provider uses the same endpoint-bound pin for claude-* ids, which
TokenLab declares for Chat and Messages only, on https://api.tokenlab.sh/v1/messages.
Command Code supports forwarding prompt_cache_key; this is separate
from the OAuth adapter’s session header and does not guarantee a provider cache hit.
The OAuth command-code preset streams /alpha/generate as NDJSON. MiMo tool-call
markup echoed by the gateway as text is removed when it duplicates a real call. Markup
after ordinary prose, including a marker split across later deltas of the same text block,
is held and removed only if a native call carries the same content. Otherwise it is
released as text and never restored as a call. It waits only while a native call it could
duplicate is still open, counting the first call of the same tool that starts after the
markup; once those close without a match, it is released right away. Reasoning or other events arriving in
between no longer release a held envelope. After a clean stop or tool-call finish, a
complete bare declared-tool envelope with no native counterpart is restored as a real
call; an interrupted or failed turn leaves the markup as text. A bare call the parser
cannot read is dropped rather than printed
when it still opens, closes, and names a declared tool, and either the real call for that
tool arrives or the turn finishes cleanly. A freeform call echoed without its
</function> close counts as complete once </tool_call> arrives. This applies to every
MiMo model Command Code serves.
anthropic
Section titled “anthropic”Targets: Anthropic Messages (/v1/messages).
Auth: key (x-api-key by default, or Authorization: Bearer with apiKeyTransport: "bearer") or oauth (Bearer + anthropic-beta, for Claude Pro/Max).
- Converts messages to Anthropic content blocks (text, base64 image,
tool_use,thinking). - Translated Anthropic Messages reasoning replay shares the request translation budget, including
encoding/decoding copy overhead. Requests exceeding it return HTTP 413 with
translation_buffer_limit; signatures and opaque reasoning data are never truncated to fit. Native Anthropic passthrough uses its separate body-size contract. - Extended thinking math: Anthropic requires
max_tokens > thinking.budget_tokens. The adapter maps reasoning effort to a budget (minimal 1024 … max 32000), then computes a safemax_tokenswith output headroom, and dropstemperature/top_pwhen thinking is enabled (Anthropic forbids them there). - Adaptive thinking display: adaptive-thinking models (Opus 4.7+, Sonnet 5, Fable) are asked
for
thinking.display: "summarized", so a long think reaches Chat and Responses clients as reasoning deltas instead of minutes of heartbeats. A request that hides the reasoning summary (reasoning.summary: "none") keeps the provider default. - Structured output: Responses
text.formatand Chat Completionsresponse_formatrequests withtype: "json_schema"become Anthropicoutput_config.format. The format merges into an existing adaptive-thinking output configuration, preserving a compatibleoutput_config.effort. Routed Anthropic Messages requests preserve the same format through stored-OAuth translation. The adapter mirrors the Anthropic TypeScript SDK’s supported JSON Schema subset: unsupported constraints are moved intodescriptionas model guidance,oneOfbecomesanyOf, and object schemas receiveadditionalProperties: false. A root$refretains its adjacent$defsso the local reference remains resolvable. OpenAI envelope fields such as schemaname, envelopedescription, andstrictare not part of the Anthropic wire format. JSON object mode without a schema has no Anthropic equivalent and is not translated. In the reverse direction, translated Anthropic output schemas retain the caller’s original schema.strict: truerequires an object root without a root union and complete closed objects throughout nested properties, array items, unions and definitions:propertiesmust be an object,additionalPropertiesmust befalse, andrequiredmust contain exactly its property names. Incomplete objects, unknown schema keywords, and values outside OpenAI’s documented strict subset use explicitstrict: false; the classifier uses an allowlist rather than chasing each unsupported constraint separately. The proxy does not invent required fields, close an open object, or discard the caller’s schema merely to obtain strict mode. - Always sends
anthropic-version: 2023-06-01. Streamscontent_block_delta(text_delta,thinking_delta, compatiblereasoning_delta,input_json_delta). The SSE decoder preserves event state across fetch chunks and accepts a terminalmessage_stopwithout a trailing newline. Atmessage_stop, the translated stream emits its terminal outcome and ends without waiting for upstream EOF. Later pings or comments do not turn that completed message into a web-search stream error. Pings and comments beforemessage_stopstill keep long thinking turns alive; refusals remain non-retryable incomplete outcomes and upstream error stop reasons remain failures. - For routed Anthropic Responses turns with client tools, a bounded terminal guard detects the high-confidence case where the user requested an action but Claude ends with an execution claim and no tool call. It performs at most one internal continuation; normal answers, clarification questions, tool-using turns, and transport-incomplete responses are not auto-retried.
google
Section titled “google”Targets: Google Gemini, Vertex AI, and Antigravity Cloud Code Assist. AI Studio uses
/v1beta/models/{model}:streamGenerateContent; the other modes use their native Google endpoints.
Auth: API key, Vertex ADC, or Google Antigravity OAuth, selected by googleMode.
-
Location denials are permission errors, not invalid requests. Google rejects unsupported geographic or datacenter locations with HTTP 400
FAILED_PRECONDITION: User location is not supported for the API use.The proxy reports this as… location not supported: …and classifies it aspermission_errorwith codelocation_not_supported, so a client does not misread a network-location refusal as a malformed prompt. The direct HTTP response keeps the upstream 400; message-only terminal paths infer 403 (permission class). The restriction itself is Google’s — the proxy does not route around it. -
System prompt →
systemInstruction; messages →contents[](assistant →model); tools →functionDeclarations. Data-URL images →inline_data. -
Tool-call ids are synthesized when Gemini omits them. Vertex and Antigravity preserve and replay opaque
thoughtSignaturevalues so tool-result continuations retain Gemini reasoning continuity. The signature cache is snapshotted to the config directory, so continuations also survive proxy restarts. -
Malformed response shapes fail closed. A claimed candidate, its
content, or itscontent.partsthat is not the documented container terminates the turn with agoogle response contained invalid …error naming the structural reason and the offending value’s type — never its contents. Absence is handled separately from corruption: an absent,nullor emptycontentorpartsstill completes the turn normally, a streaming chunk whosecandidatesis absent,nullor empty is skipped so the turn completes on a later terminal frame, and a buffered response that carries no candidate at all returnsgoogle response contained no candidates. A rootdata: nullkeepalive frame is still skipped as padding. -
Tool-call batches are closed by one immediately adjacent user turn containing one ordered
functionResponseper representable call. Interrupted histories receive an explicit missing-result marker; duplicate or standalone results are preserved as marked text (and image siblings) rather than emitted as invalid unpairedfunctionResponseparts. -
Video input and agentic processing. The OpenAI-compatible content part
{"type": "video_url", "video_url": {"url": "…", "processing": "agentic"}}is accepted on both the Chat and Responses ingress routes.urlis required;processingis optional and is upper-cased onto the Gemini part asmedia_processing(STATICis Gemini’s default,AGENTICrequests agentic video understanding). The field sits on the part, besideinline_dataorfile_data, so it applies to inline bytes and fetched URIs alike — it is not the Interactions API’sprocessing. A request that omitsprocessinggains no field, so existing callers are unchanged.Three URL forms are handled, and only three:
urlsent as data:URLinline_datawith the data URL’s own media typeYouTube watch URL ( youtube.com,youtu.be,m./music./-nocookievariants)file_data.file_urihttps://generativelanguage.googleapis.com/<version>/files/<id>— the Files API resource form, where<version>isv1,v1betaorv1alphafile_data.file_uriAny other remote URL is kept as the text marker
[video: <url>], because the adapter has no media type for it and no evidence Gemini will fetch it. The allowlist is matched on the parsed URL’s host and path over HTTPS — not on a substring — so a look-alike host does not become afile_datareference the proxy asks Gemini to fetch. The path is anchored at the start, so the resumable-upload endpoint (/upload/<version>/files/<id>) is not accepted: it is not a readable resource, andfile_data.file_uriasks Gemini to dereference what it is given.file_datacarriesfile_urionly; no guessedmime_typeis attached. -
Inline image output: when the model is one of the explicit image-capable chat IDs (
gemini-3.1-flash-image,gemini-2.0-flash-preview-image-generation, orgemini-3-pro-image-preview), the adapter sendsresponseModalities: ["TEXT", "IMAGE"]. Standalone media-generation IDs such asgemini-3-pro-imageare not included. ReturnedinlineDataparts are materialized under the configured OpenCodexartifacts/directory and surfaced as markdown image links to the authenticated opaque route/v1/opencodex/artifacts/<id>(notfile:URIs or host filesystem paths). Each image is capped at 50 MB and each response at 100 MB of decoded data; malformed base64 payloads are rejected. Artifacts are pruned automatically when the count exceeds 200 files.
Targets: the Amazon CodeWhisperer Streaming GenerateAssistantResponse service used by Kiro
(https://runtime.{region}.kiro.dev/).
Auth: Kiro OAuth access token as Bearer, with region/profile metadata from the Kiro credential.
- Builds Kiro
conversationState, maps Codex tools and tool results, and sends image blocks supported by the Kiro wire. - Keeps at most 20 inline images per message and 100 per request. Older history images are omitted first when the request exceeds the limit, with a text marker on affected messages; recent images in the current turn remain available.
- When an inline image data URL lacks image bytes or a comma, omits that image with a text marker in its user turn or tool result. Remote image references use a separate marker; neither marker repeats the URL.
- Coalesces adjacent outputs from the same original tool call into one Kiro result. Text remains ordered, images retain the existing per-message limits, and any error flag remains set. User, developer, assistant or another tool’s output ends the group. Distinct original IDs that map to the same normalized Kiro ID are rejected.
- Combined outputs keep real text and failure information without inserting an empty-output hint for a later blank chunk. A single result keeps its existing normalization; an entirely text-empty group receives one fallback, with neutral wording when images or an error flag are present.
- Treats a client
parallel_tool_calls: truevalue as permission rather than a wire requirement. Kiro remains serialized: the routed catalog advertises no parallel-tool capability and the adapter sends no parallel-control field upstream, but ordinary Codex tool turns are not rejected solely because the client permits parallel calls. - Accepts Responses
textcontrols that are not structured output —text.verbosityandtext.format: {"type":"text"}— without forwarding them. Kiro has no wire field for either, so they are ignored rather than rejected. Structured output (text.formatof typejson_schemaorjson_object) is still refused: the Kiro wire cannot constrain the response shape, and a caller expecting JSON would otherwise receive prose. - Decodes
application/vnd.amazon.eventstream, reconstructs text/thinking/tool events, detects truncated tool JSON, and estimates usage because the upstream does not return token counts. - Uses the configured
baseUrlverbatim when it is custom. A canonicalruntime.{region}.kiro.devURL follows the imported credential’s API region; only that canonical shape is eligible for one budgeted fallback toq.{region}.amazonaws.comafter an endpoint, signature, DNS, or connection failure, or HTTP 502/503/504 received before output. - Owns replay-safe connection-reset recovery, that single eligible endpoint fallback, one OAuth refresh/replay after HTTP 401, and bounded recovery for transient Kiro 429s. A shared cooldown and single post-cooldown probe prevent concurrent requests from exhausting independent retry budgets; hard quota failures are not retried on that same account, and other service errors are not replayed. Every Kiro physical send uses configured provider egress. A header deadline returns 504; caller cancellation stops the turn. Final HTTP 5xx bodies use fixed public text without upstream detail.
- Its non-streaming parser drains the same event stream for the web-search loop.
- Reports per-account usage.
AmazonCodeWhispererService.GetUsageLimitsonhttps://management.{region}.kiro.dev/returns the plan allowance, which becomes the monthly quota window for that account; a free-trial balance is reported as its own window. The region comes from the account’s profile ARN, then its stored API/SSO region. An AWS Builder ID account has no profile ARN of its own, so the probe sends the same Builder ID service profile that generation requests use; that fixed ARN never selects the region. An unreadable or unrecognised response is reported as unknown rather than as zero usage, and an account whose overage is enabled is not treated as exhausted merely for passing its limit. The operation is undocumented by AWS, so treat the numbers as best-effort. A non-Builder-ID account without a usable profile ARN makes no usage request; usage is unavailable while an earlier same-login quota bar remains visible. Known quota and exhaustion evidence survive restart only for the same login, each until its own reset or ten-minute lifetime. Missing, old, or malformed evidence becomes unknown. The plan’s precise credit balance is retained with that identity-fenced quota reading. Separately, KirometeringEventcredit values are measured request spend: the last value within a physical response is used, and credits from separately billed sends are added. Token usage remains estimated; credits are never inferred from tokens. - After an admitted request, reads that account’s available models from the regional management service without delaying the request. Cached results add model IDs to the shipped catalog. Empty or unrecognised replies retain the shipped list and any last good account list. Model membership guides eligible-account preference; unknown IDs still go upstream. Reported input limits inform context windows, conservatively combined with shipped limits when evidence is partial. Accounts that have not served have no list evidence yet.
- Participates in multi-account rotation. With two stored accounts, a request-rate refusal
cools the refused account briefly; an exact monthly-quota refusal on HTTP 400 or 429
excludes it until the observed reset or evidence expiry. A confirmed HTTP 403 suspension
quarantines that account in process, while an ordinary 403 does not rotate. Reactive
rotation remains available when
oauthAccountFailover.enabledis false. Refusal-aware selection before the first send requires proactive preference, with the provider setting taking precedence over the global setting. A completed turn from the same login clears an older exhaustion verdict. Each rotated bearer carries its own profile ARN and region.
Completion semantics
Section titled “Completion semantics”Kiro assistant text carries no dependable end-turn phase of its own. Its terminal metadataEvent
can carry a native stopReason, but Kiro can label progress prose as END_TURN. On tool-enabled
turns, END_TURN and STOP_SEQUENCE therefore prove only that the inference stopped; ordinary text
remains commentary and enters the one bounded completion validation.
END_TURN, STOP_SEQUENCE, or a missing stop reason may use the compatibility path. Other explicit
reasons have already terminated the inference upstream, so the adapter reports them instead of
spending another model request: an output-token limit surfaces as incomplete output that a client may continue, while
context-window exhaustion surfaces as a non-retryable context-length error rather than as truncated
output. Filtering and guardrail stops surface as filtered incomplete output, and a TOOL_USE stop
that arrives without an actual tool call is reported as a contradiction rather than treated as
progress.
When an ordinary client tool exists, opencodex adds a private
codex_kiro_final_answer tool to the upstream request; progress text streams as commentary and
cannot terminate the turn. The adapter consumes the private call, emits its answer as final text,
and never exposes the private tool to Codex or Claude Code. Because the stop reason only arrives at
the end of the stream, assistant text in a tool-enabled turn is held until either a real tool call
starts or the stream ends, then releases it as commentary unless the private tool supplied the final
answer. When the web-search sidecar is active, released
commentary still streams ahead of the terminal event; only the events needed to decide whether the
model requested a synthetic search remain buffered.
A question the model cannot proceed without is also a final answer. The injected contract tells a
routed model that when it needs a decision, a piece of information, or a clarification only the user
can give, it should deliver that question through codex_kiro_final_answer and stop, rather than
writing the question as ordinary text and continuing. Such a turn arrives like any other completed
answer: final text with the turn ended, not commentary and not a client tool call. Without this,
the contract described only “still working” and “fully complete”, and a model holding a blocking
question had no way to say so — the observed result was a question and a self-override emitted as one
message, followed by another tool call from the same inference.
If Kiro stops without calling the completion tool, the adapter makes one continuation. Reasoning-
only retries preserve the original valid user/tool-result turn rather than manufacturing an empty
assistant message; visible progress is replayed with a non-empty adapter-owned instruction. Before
transport, the generated conversation is checked for alternating roles, non-empty structural turns,
and matched tool-use/result ids. Empty tool output receives a neutral non-empty placeholder. The
retry cannot recurse: an empty or reasoning-only retry is returned as retryable incomplete, while a
real client tool call keeps the turn open. A completion-tool answer is always emitted as
final_answer, even when it exactly repeats prior commentary, because phase correctness is more
important than cosmetic de-duplication. Tool-free requests retain normal text completion behavior.
Reasoning effort
Section titled “Reasoning effort”The GPT-5.6 family uses additionalModelRequestFields.reasoning.effort; claude-opus-5
uses additionalModelRequestFields.output_config.effort. For gpt-5.6-luna and
gpt-5.6-terra, only low, medium, high, and max use the verified native path.
Their xhigh selection retains the previous bounded thinking instructions in user content
because that native rung has not been verified. gpt-5.6-sol and claude-opus-5 keep
their existing native low, medium, high, xhigh, and max behavior. Other Kiro
models use emulated reasoning; an advertised effort control is not proof of native support.
cursor
Section titled “cursor”Targets: Cursor’s agent.v1.AgentService/Run over HTTP/2 Connect streaming at api2.cursor.sh
by default. With upstreamHttpVersion: "http1.1" (or "h1"), uses Cursor’s HTTP/1.1
compatibility pair: agent.v1.AgentService/RunSSE for server output and
aiserver.v1.BidiService/BidiAppend for client messages.
Auth: Cursor OAuth/access token from provider.apiKey or the forwarded authorization header.
- Uses
runTurnrather than the ordinary fetch/parse path. Requests, server events, tool arguments, usage checkpoints, and client replies are encoded with@bufbuild/protobufschemas incursor/gen/agent_pb.tsand framed as Connect messages. - Replays conversation state through content-addressed blobs, maps server tool calls back to Codex,
discovers live Cursor models through the protobuf
GetUsableModelsRPC, and retries only before a run request is committed to the wire. - After a successful no-tool turn, the adapter keeps Cursor’s returned ConversationStateStructure in a process-local store and reuses that checkpoint on the next validated linear continuation instead of rebuilding the full root history. Tool-result turns reuse the last completed-turn checkpoint plus only the uncovered suffix when the covered message boundary is known. Ref-less prefix lookup requires a remembered Cursor conversation or stable client thread (including the bounded Desktop session/thread fallback) and a checkpoint owned by that same provider conversation; otherwise it full-replays. Compaction, helper/shadow isolation, account/model mismatch, missing refs, decode failures, forced-fresh recovery, and invalid_argument retries fall back to the existing full replay. A process restart drops the in-memory store and full-replays. Cursor Connect still does not expose authoritative cache_read_tokens, so OpenCodex usage is not a cache-hit counter. The bounded Desktop fallback stores only a process-local HMAC-derived owner; raw session/thread headers and OAuth/authorization material are never written to checkpoint state. Cursor’s OAuth-backed live transport and account-filtered model discovery remain experimental; see the provider guide and Cursor provider configuration for login and transport settings. Checkpoint reuse itself is automatic and has no user setting.
- External-model tool continuations keep the latest actual user request in the active action; automatic summaries and standalone ambient-browser context remain historical context. Blank or image-only user input does not revive an older request. Grok 4.6 code-mode guidance requires explicit result emission and never assumes an empty completed cell emitted output. Missing output calls for a read-only state check, not replay of a completed side effect. Repetition advice resets on a new user/developer turn and permits requested polling. If carried checkpoint roots exceed the replay budget, available history is rebuilt under the same limits. These repairs do not guarantee identical wording or reasoning behavior between Cursor and xAI routes.
- Honors
upstreamHttpVersionfor both live model discovery and inference.auto,http2, andh2preserve the existing HTTP/2 transport; onlyhttp1.1andh1select compatibility mode. - Exposes Cursor Router as
cursor/autoplus explicitcursor/auto-cost,cursor/auto-balance, andcursor/auto-intelligenceentries. Explicit levels are encoded inrequested_model.parameterswhile the legacycursor/autoentry retains the account/team default. - Sends regular
cursor/grok-4.5tiers with Cursor’s exact live-discovery wire ids (cursor-grok-4.5-low,-medium, or-high). Keepscursor/grok-4.5-fastselectable while sending the canonicalgrok-4.5model with separateeffortandfast=trueparameters. - Cursor-native local filesystem/shell/network execution is denied by default. Explicit
mcpServersanddesktopExecutorintegrations have separate opt-ins;nativeLocalExec: "on"enables the broader built-in executor and bypasses Codex approval/sandbox semantics, and legacyunsafeAllowNativeLocalExec: trueremains equivalent only whennativeLocalExecis unset. ForegroundshellArgsandshellStreamArgsare an exception: both are rejected before spawn on every platform until kernel-backed descendant ownership is available. Use client shell tools; background-shell execution and other native operations retain their existing policy. - The denial reply follows the request catalog. Explicit Claude-family Cursor models receive
factual wording that names the available tools without instructions to hide or avoid discussing
the redirect. Factual routing commentary can precede the next tool call without ending the turn.
When no client or configured MCP tools are available, Claude models are asked to answer without
tools or report the limitation instead of calling nonexistent shell/edit tools.
Other models, including Auto/default, retain the existing silent-redirect wording and commentary
guard. Tool availability, execution policy, and approval/sandbox behavior are unchanged.
In code mode — a freeform unified
execand no bare shell bridge — the redirect points insideexec, where shell, file, search, and fetch are nestedtools.<name>(...)helpers of the JavaScript cell, and never recommends the top-level shell bridge code mode does not expose. A flat catalog that carriesshell_command/exec_commandor a non-freeform unifiedexeckeeps the bridge wording; a catalog that carries neither — an orchestrator client exposing only its own Responses tools, for example — is redirected to the request’s actual wire names, so the model is pointed at a tool that exists rather than at an alias it cannot see. - A recognized Cursor data-policy gate is reported with its title, the action it requires, and the
Cursor Dashboard review URL instead of a bare
failed_precondition: Error. Recognition is limited to the known structured detail: unknown or malformed details keep the generic Connect error, no upstream text, button, URL, or consent action is forwarded or executed, and the failure stays non-retryable. Reviewing and accepting a data policy remains a user action in Cursor itself.
Codex-compatible shell schemas retain sandbox permissions, justification, reusable
prefix rules and login mode. Freeform tools expose one required string input
and preserve its tool-specific guidance, such as the required patch envelope;
bare exec_command and shell_command names are reserved for non-freeform shell
bridges. Namespace a custom freeform tool that uses either name. These schema
declarations do not grant approval or change execution policy.
Targets: Cognition’s exa.api_server_pb.ApiServerService/GetChatMessage over HTTPS Connect
streaming at server.codeium.com.
Auth: Devin/Cognition API key from provider.apiKey or the forwarded authorization header.
Login first tries to import the credential the installed Devin CLI already holds: devin auth login completes the CLI’s own PKCE sign-in and writes a devin-session-token to its
credentials.toml, which is the same credential SeatManagementService.RegisterUser mints for a
browser sign-in. When no usable CLI credential exists, login falls back to Auth0 browser sign-in
and exchanges the pasted token via RegisterUser for a long-lived API key. devin-cli survives
only as a deprecated alias — ocx login devin-cli still routes to devin, and a saved
configuration that names the old id is rewritten at startup.
- Uses
runTurnrather than the ordinary fetch/parse path. Requests and server events are encoded with manual protobuf framing indevin/cloud-direct/wire.ts; the ordinarybuildRequest/parseStreampath is disabled. - Named conversations reuse a trajectory across sequential turns, scoped to the resolved credential
and tenant host. Own-thread identity takes precedence over session markers (
session_id,session-id, orx-session-affinity). When an own-thread identity and a nonemptyx-codex-parent-thread-idare both supplied, their pair identifies the conversation, so equal own IDs under different parents remain separate. Standalone own and session-only identities retain their existing precedence; a shared parent alone is not a conversation identity. Overlapping turns receive distinct IDs. The proxy retains at most 256 entries in memory, evicts only inactive entries, and releases claims after completion, failure, or cancellation. Restarting the proxy clears retention. Unnamed calls continue allocating an ID per request. This supports continuity but does not guarantee an upstream cache hit or a particular saving. - Reasoning continuity carries provider signatures across turns. If Cognition refuses a signed Anthropic replay before visible output, Devin retries once with the signature withheld and the thinking text preserved.
- Live model discovery via
GetCascadeModelConfigs; the static seed is filtered against the account’s live roster so models not on the plan drop out instead of failing at request time. - Tool definitions are encoded in the request and tool-call events are decoded from the response stream. Cognition enforces a per-tool-description length limit (6,998 chars) and an exact-phrase blocklist; the adapter sanitizes known triggers and truncates over-long descriptions before encoding.
- Devin/Cognition API keys have no refresh endpoint. If Cognition rejects a stored key with 401,
OpenCodex marks that account for reauthentication and can use another signed-in account for
the turn. Run
ocx login devinagain for a revoked browser-login key. A CLI-imported account can follow a laterdevin auth loginkey rotation only when its stored account ID or email matches the minted CLI identity. Imports without a stored identity require an explicitocx login devinafter rotation. A legacydevin-clislot for that same account may already hold the new key; a different account holding it blocks adoption. If the CLI file is temporarily unreadable or the identity check is unavailable, retry after it recovers. A paused account stays paused during this recovery and returns 403. - Only the credential is local when the CLI import path is used. The turn itself goes to
Cognition either way, so the import and browser login paths differ in nothing but where the
credential came from. Install the CLI with
curl -fsSL https://cli.devin.ai/install.sh | bashorbrew install --cask devin-cli, rundevin auth loginonce, then add the provider. - An earlier build shipped a second adapter under the id
devin-clithat ran the turn as an Agent Client Protocol session against a localdevin acpchild process. It is gone. A saved configuration that still names that adapter is rewritten todevinat startup, including a custom-named row such as"devin-acp". - The chat request is calibrated, not guessed. Three things gate it together: the credential is the
session token doubled and dash-joined in an
Authorization: Basicheader while the protobuf body keeps one copy, the request envelope goes up uncompressed, andMetadata#31 carries a 732-character device fingerprint whose length — not value — the service checks. InsideCompletionConfiguration, #2 is the output cap and #3 ismax_newlines, sent at a fixed large value; swapping those two makes every turn fail with an opaqueinvalid_argument. With no caller or configured cap, the output cap is the selected model’s own ceiling from the catalog, and 8192 only when the catalog is unavailable. A temperature of exactly 0 is refused, so it is clamped to the smallest accepted value. - A pre-output 429 with a stated recovery delay is surfaced immediately by default, releasing the
admitted turn’s shared capacity. Set
OPENCODEX_DEVIN_STATED_RESET_WAIT_MSto a positive cumulative allowance in milliseconds to wait for the full stated delay and replay the same request up to twice. The allowance has a one-hour ceiling; an absent, empty, invalid, or negative value disables waiting. An opted-in standalone wait keeps the HTTP turn and its shared active-turn slot open throughout the delay. Streaming turns start SSE on a safe cooldown heartbeat, then schedule heartbeats every 500 ms or less during the wait so the stall watchdog stays fed. A later pre-output 429 may still rotate to another eligible OAuth account; without one it is reported inside the already-open stream. Buffered Grok turns retain an HTTP 429 andRetry-Afteron a final refusal. Combo children surface the pre-output 429 immediately, even when waiting is enabled, so the combo can try its next target without holding an uncommitted response. Delays exceeding the remaining allowance on standalone turns surface the original 429 without an early retry. The final 429 preserves the stated delay as a cooldown hint. A~in its message marks a delay recovered from a secondhand trailer sentence rather than an exact header value. - For Codex Responses streams, known typed rate-limit failures with a valid delay are normalized to
rate_limit_exceededwithPlease try again in Ns.before the original redacted detail. This lets Codex honor the stated delay and use its native reconnect notification without adding a reasoning item to conversation history. Client retries are finite and controlled by the client’sstream_max_retries; this does not promise recovery after app shutdown or restart. LeaveOPENCODEX_DEVIN_STATED_RESET_WAIT_MSunset or0to let the client own the wait. A positive proxy allowance keeps the existing proxy-owned wait; the client only learns of a final refusal afterwards, and client retries can multiply the proxy’s per-request attempts. Combo target/account failover and Grok HTTP 429 handling retain their existing ordering. The exact UI placement and text depend on the Codex version; this is not a custom countdown. Message-only rate-limit errors use the same longest-delay-first formatting. Typed errors without a usable delay retain their original code. Client retries create new HTTP requests; they do not share one proxy request’s send counter or cumulative wait allowance. This compatibility mapping does not replace the controls of an explicitly enabled proxy wait. - Experimental unofficial bridge; not shown in the dashboard preset by default. See the provider guide for login instructions.
The model id you pick is a model family, and the reasoning effort picks the
variant. With no effort, the family’s own default variant is used: swe-1-7
selects swe-1-7-medium and swe-2 selects swe-2-high. An effort changes only
the effort and lands on the lowest variant at or above it, or the highest one
below when nothing is above, so a missing rung never turns reasoning down:
swe-1-7 at high selects the Max row swe-1-7, swe-2 at low selects
swe-2-medium, and kimi-k3 at medium selects kimi-k3-high. fast selects the Fast variant and 1m, max-1m or none-1m
the 1M-context variant where the family has one; otherwise they change nothing.
A suffixed id such as claude-opus-5-high-fast keeps its variant without an
effort, and with one keeps its Fast and context settings unless that would lower the
effort: with no Fast row at or above the effort, the regular row is used instead.
An id naming a variant your account has disabled is sent as named when the effort
still lands on it, so the request fails with that variant’s own error rather than
quietly switching tier. Resolution never moves
to another family. When the account catalog is unavailable, the effort is
appended to the id instead.
Targets: Zed Hosted AI’s POST /completions endpoint at cloud.zed.dev.
Auth: Zed native-app account identity plus access token, exchanged for a short-lived LLM token.
- Uses the native RSA callback login (
ocx login zed) and pairs the returneduser_idwith the access token for account-scoped user and organization lookups. - Wraps the existing Anthropic Messages, Google Gemini, OpenAI Responses, and xAI Chat builders
inside Zed’s provider envelope, then unwraps Zed’s NDJSON/SSE events back into
AdapterEvent. - Fetches a bounded, account-scoped live model roster for display and provider-family inference; the roster is not a model allowlist, so a caller-supplied model id is still forwarded.
- Experimental, unofficial, and not endorsed by Zed. Using it may break Zed’s terms of service and can get the Zed account limited or suspended; that risk is the user’s to accept.
azure-openai (alias: azure)
Section titled “azure-openai (alias: azure)”Targets: Azure OpenAI. Wraps openai-responses (so also passthrough: true).
Auth: key via the api-key header (not Bearer).
- Delegates request building to the Responses passthrough, validates that
baseUrlcontains no unresolved template placeholder, and replacesAuthorizationwithapi-key. The configured URL targets Azure’s v1 Responses API directly, so the adapter does not appendapi-version. - Shares the Responses recovery for reasoning state another provider produced: after a
400 invalid_encrypted_contentit resends once without that state. See Proxy formats under “Switching providers in an existing conversation”.
Image utilities (image.ts)
Section titled “Image utilities (image.ts)”Shared helpers used by the vision-aware adapters:
parseDataUrl(url)— split adata:<type>;base64,<data>URL into{ mediaType, base64 }for Anthropic/Google image blocks.contentPartsToText(content)— flatten content parts to text for text-only tool messages (an undescribed image becomes a short[image]marker, never a token-exploding base64 blob).
Grok Build terminal snapshots
Section titled “Grok Build terminal snapshots”Requests marked with x-opencodex-grok: 1 opt into a narrow Responses terminal
repair. If response.completed.response.output is missing or empty, opencodex
can reconstruct it from real, uniquely indexed, contiguous output_item.done
items whose raw fields satisfy the supported shapes. Deltas alone do not create
output. Malformed, contradictory, duplicated, gapped or oversized evidence keeps
the empty terminal unchanged; failed and incomplete responses never become success.
The marker is a client-selected compatibility option, not authenticated identity
or a permission grant. Unmarked clients retain their existing behavior. This
repair runs before the separate provider responsesSnapshotRepair option and
does not enable that broader lifecycle repair. Existing tool-search, custom-tool,
function-completion and undeclared-tool handling keep their established order.
DeepSeek and Claude Code Artifact
Section titled “DeepSeek and Claude Code Artifact”For the official DeepSeek Chat Completions endpoint, opencodex relaxes regex and
anyOf constraints in Claude Code’s built-in Artifact tool schema to avoid
schema-validation HTTP 400 errors. Strict mode is omitted for this tool. Fields
defined only inside an anyOf are no longer constrained by that union; the tool
must validate its inputs. Other tools and providers retain their existing behavior.

