Provider Configuration
A provider tells opencodex where a model lives, which wire adapter it speaks, and how requests are authenticated.
Initial model selection
Section titled “Initial model selection”New non-OAuth connections wait for a reliable model list before exposing models. If that list contains at least 20 distinct Models-tab rows, all model switches start OFF; the provider itself stays ACTIVE. OAuth and ChatGPT-login connections keep their defaults, based on the effective authentication mode.
This runs only for a new provider registration. Existing selections survive updates, re-login and key replacement. After initialization, enable the models you need in Models or with the CLI below; the separate new-model-arrival policy is unchanged. Replace <model-id> with an ID from the list.
ocx models live --provider openrouterocx models enable '<model-id>'ocx models disable '<model-id>'ocx models provider openrouter onAfter GUI registration or OAuth login, the confirmation dialog lets you open the Models page. CLI registration and login print model-management commands; JSON includes structured next steps. --no-wait reports pending login, not completion. Start the proxy with ocx start before using live model commands.
Z.ai Coding Plan quota endpoints
Section titled “Z.ai Coding Plan quota endpoints”The Z.ai quota probe recognizes the international coding Chat base
https://api.z.ai/api/coding/paas/v4, the documented
Claude Code Anthropic base
https://api.z.ai/api/anthropic, and the documented
Codex Responses base
https://api.z.ai/api/v1. All three read quota from the international monitor with
Bearer authentication; this does not change the inference URL or imply different
quota consumption between adapters. Existing BigModel CN monitor selection remains
separate. Full request URLs such as /api/v1/responses are not provider base URLs.
Provider-related top-level fields
Section titled “Provider-related top-level fields”| Field | Type | Default | Meaning |
|---|---|---|---|
providers |
Record<string, OcxProviderConfig> |
— | Map of provider name to provider config. |
openaiProviderTierVersion? |
2 |
set by migration | Marks the single option-aware OpenAI projection as complete. |
disabledModels? |
string[] |
— | Models hidden from Codex’s catalog and /v1/models, but not blocked from direct proxy calls. A routed id is removed from listings. An account-qualified native id hides only that selector row; a bare native GPT id hides the bare row and every account-selector row for that model. The dashboard Models page exposes only routed and bare native rows; use this configuration field directly to hide one selector-qualified row. |
providerContextCaps? |
Record<string, number> |
{} |
Active provider context limits. Ordinary windows are lowered; native models with a supported long window can expand only up to their own supported ceiling. |
providerContextCapValues? |
Record<string, number> |
{} |
Last selected provider limits, retained while disabled. These values do not activate a cap. An enabled value takes precedence over a remembered value. |
contextCapValue? |
number |
350000 |
Default used on first enable. A later enable restores the selected provider value. Updating the global value with setAll: true changes enabled caps only; setAll: true without a value enables all configured providers at the current global value. |
codexAccounts? |
CodexAccount[] |
[] |
ChatGPT/Codex pool account metadata managed by Codex Auth. Secrets live separately in codex-accounts.json. |
pausedCodexAccountIds? |
string[] |
[] |
Accounts excluded from Pool selection until resumed, including the main __main__ account when paused. |
codexQuotaAutoRefresh? |
Record<string, object> |
{} |
Per-Codex-login-account opt-in for automatic fiveHour and weekly window activation in Pool mode; Direct mode does not run this worker. In Providers/Codex Auth Advanced settings, one control enables or disables both supported windows across all current main and added accounts. New accounts are not opted in automatically. Enable skips windows absent from live WHAM data; disable also clears stale enabled windows. The UI reuses granular /api/settings writes, reconciles partial failures, and retries the original ON/OFF intent without replacing unrelated settings or completed reset markers. The API still rejects enabling an unavailable window with HTTP 409. At a reported reset time, opencodex sends one minimal non-stored Codex message using that account’s quota and persists the activated timestamp. This does not apply to API-key providers. |
codexAccountNamespaces? |
Record<string, string> |
— | Optional map from an arbitrary public model selector to a stored Codex account target. When account-qualified picker rows are enabled, each selector whose target is present adds separate <selector>/<native-openai-model> rows to the Codex picker; each row uses only that account. With any selector active, bare native rows are hidden in the picker, but their ids remain routable and listed by raw /v1/models unless explicitly disabled. |
codexAccountPickerEnabled? |
boolean |
off when the map is empty | Controls whether eligible codexAccountNamespaces mappings generate account-qualified Codex picker rows. true allows mapped rows to appear. If omitted with a non-empty map, it is treated as enabled for backward compatibility; if the map is empty, it is off. false hides generated rows and restores bare native picker rows without deleting mappings or disabling exact <selector>/<native-openai-model> routing. |
activeCodexAccountId? |
string |
— | Manually selected Pool account for the next request. Selection clears thread affinity; in-flight requests keep captured credentials. |
codexAccountPriorities? |
Record<string, number> |
— | Per-account selection order for the Codex pool: account id → integer from -100 to 100, higher is used earlier, absent means 0. This is an ordering boundary, not an eligibility one: selection narrows the already-eligible accounts to the highest tier that still has quota headroom, and accountPoolStrategy then picks within that tier. A tier is skipped only when every member is over autoSwitchThreshold, cooling down, soft-avoided, paused, or needs reauthentication — unknown quota never drains a tier. Ordering never makes an ineligible account selectable and never re-binds a thread that already has an account. The main __main__ account participates on equal terms, which is how the Codex Desktop login can be set to drain last. With no entries the pool behaves exactly as before. A malformed map is ignored with a console warning (ordering off, no config repair). Managed by ocx account priority and the Codex Auth page. |
activeCodexAccountPinned? |
string |
— | Account id the operator last selected by hand. While set, a higher codexAccountPriorities tier cannot preempt it until the pin is released by drain, exclusion, deletion, or an explicit failover/promotion away. Ordinary round-robin movement inside the capped tier does not release it. Writing any codexAccountPriorities entry also releases the pin, so a pin made before an order existed cannot outrank one set afterward. GET /api/codex-auth/active reports both whether the effective account is pinned (pinned) and the account carrying the ceiling (pinnedAccountId). |
autoSwitchThreshold? |
number |
80 |
Usage threshold for placing new/unbound work. quota can re-evaluate unbound tasks on their next request once usage crosses this threshold. Bound tasks keep their account past the threshold by default (pool.cacheAffinity); they leave only when that account is exhausted or otherwise cannot serve, and then only for an account with genuine quota headroom and strictly lower usage. Set pool.cacheAffinity: false to re-evaluate bound tasks at this threshold, still only onto such a destination. fill-first uses it only as the drain point for unbound assignment; normal round-robin selection does not use it. The score uses the hottest known 5h, weekly, or 30d quota window. 0 disables usage-based proactive switching only, not unbound assignment or failure recovery. |
accountPoolStrategy? |
"quota" | "round-robin" | "fill-first" | "reset-first" |
"quota" |
Assignment strategy for new/unbound Codex requests. A request is unbound when it has no live (parent thread id, quota scope) affinity; a visible existing task can become unbound after proxy restart or affinity reset. quota picks the lowest-usage eligible account when no active account exists, keeps an eligible active account below autoSwitchThreshold, and after the threshold may move an unbound request. Bound tasks follow pool.cacheAffinity (on by default): they stay until the account is exhausted (known usage at 100%) or otherwise cannot serve, and then may rebind only to an account with genuine quota headroom and strictly lower usage. Set the flag false to proactively rebind a bound task at the threshold, still only onto such a destination. round-robin distributes unbound requests evenly; fill-first keeps assigning unbound requests to the active account until cooldown, unavailability, or the configured drain threshold. reset-first: Prefer the nearest future 5-hour or weekly reset among accounts below the usage threshold. Bound tasks follow the configured affinity policy. Independent model quotas use quota ordering. Monthly resets do not determine this ordering. |
pool.cacheAffinity? |
boolean |
true |
Cache-affinity ordering for bound Codex threads, independent of pool.kernel. On by default; omitting the key or setting true keeps a bound task on its account until that account genuinely cannot serve. Only an explicit false restores threshold-based rebinding of bound tasks. A live binding outranks quota headroom: quota does not move the thread merely because usage crossed autoSwitchThreshold. The thread still leaves if that account cannot serve — paused, unusable, or genuinely exhausted (known usage at 100%) — and then only to an account with genuine quota headroom and strictly lower usage. Under either setting, an account with unknown usage is never chosen as a destination for a bound task, so when every account sits above the threshold the task stays put. Affinity is a reordering, not a pin. |
pool.credentialGroups? |
Array<{ id: string; credentials: string[]; note?: string }> |
[] |
Accepted and validated, but not yet consumed by routing: declaring a group changes no routing decision until a consuming layer lands. Operator-declared quota domains: groups of credentials that demonstrably share one upstream usage limit. Members of one group count once toward available capacity, and a quota refusal inside a group is not answered by rotating to another member — the limit is the same, so the move would pay a cold prefix for zero new capacity. Declared groups speak only to quota; sharing a limit says nothing about prompt-cache compatibility, which is classified separately. Each member is written provider-qualified as "<provider>:<credential-id>", because a credential id means something only inside its provider; the provider segment accepts the usual aliases (chatgpt: and codex: both mean OpenAI). Group ids must be unique, credentials must be non-empty, and a credential may appear in at most one group — an ambiguous declaration is rejected on write and dropped with a warning on load rather than resolved by whichever group is listed first, since that would merge two unrelated quota domains. A malformed list costs only the grouping: pool.kernel and pool.cacheAffinity are preserved. Absent or empty means no declared grouping, so an unconfigured install behaves exactly as before. |
accountPoolStickyLimit? |
number |
1 |
New/unbound task assignments retained on one round-robin selection before advancing; the counter advances when a task is bound, not after an upstream success. Range 1–100. |
upstreamFailoverThreshold? |
number |
3 |
Consecutive transient failures before future new sessions fail over. Set 0 to disable. For regular Responses and native compact sends, proven pre-connection DNS/TCP reachability failures are tracked at the provider-host level: they never affect account health, account cooldowns, thread/session affinity, active-account selection, or Pool routing, and never count toward this threshold. |
upstreamHostCircuitThreshold? |
number |
0 |
Opt-in circuit threshold for proven pre-connection DNS/TCP failures on native OpenAI forward Responses and compact sends. 0 disables it; 1–20 opens a 30-second provider-origin cooldown after that many terminal logical requests. While open, requests receive 503 with Retry-After before account selection or upstream send; after cooldown, one half-open request is admitted. Timeouts and HTTP responses never count, and any HTTP response closes the circuit. Applies only to Codex Pool routing with no pinned account; it is inert for codexAccountMode: "direct" and account-qualified selectors. |
maxUpstreamBodyBytes? |
number |
0 |
Opt-in ceiling, in bytes, on a serialized native Responses passthrough body. 0 or omitted disables it — no limit is inferred for any destination. When set, a built body above the ceiling is refused locally before the send: streaming turns receive a terminal response.failed / context_length_exceeded so the client compacts instead of resending, and non-streaming turns receive a 413 naming the size, the number of embedded input_image items, and roughly how many megabytes of image data they represent. Checked at every build and rebuild point, including OAuth-refresh replay and alternate-account retry. Translated adapter paths are not covered. There is deliberately no default: the only measured ceiling here belongs to the WebSocket transport, which already falls back to HTTP for oversized turns, so a default would refuse requests that currently succeed. Set it when your gateway has a known request-size limit and you would rather see an actionable local error than an opaque upstream failure. |
maxInboundBodyBytes? |
number |
0 |
Opt-in ceiling, in bytes, on a decompressed inbound data-plane request body — the mirror of maxUpstreamBodyBytes above. 0 or omitted keeps the built-in 256 MiB default. Raise it when a large-context session can no longer compact: Codex replays the whole history to the compaction model, so on the 922k-token opt-in window the compaction request is itself the one that crosses the limit, and the session is stuck at the only operation that would have shrunk it. Clamped to 1 MiB–512 MiB. The ceiling is not negotiable: the reader materializes the body several times over (wire bytes, decoded bytes, the decoded string, and the parsed object graph), so peak memory is a multiple of whatever is admitted, and an unbounded value would be a memory exhaustion lever. Applies to /v1/responses, /v1/responses/compact, /v1/chat/completions, and /v1/messages. The listener’s accept size is fixed when the proxy binds, so a change takes effect on restart. A body above the limit is refused locally with HTTP 413 and code: "inbound_body_too_large", which is deliberately distinct from the context_length_exceeded 413 a provider size refusal produces. |
modelCacheTtlMs? |
number |
300000 |
Freshness window for the per-provider /models cache. |
cacheRetention? |
"none" | "short" | "long" |
"short" |
Anthropic prompt-cache policy: disabled, 5-minute ephemeral, or 1-hour extended. |
tokenGuardian? |
OcxTokenGuardianConfig |
off | Optional proactive OAuth refresh and Codex-account warmup policy. |
Selector names are user-chosen public labels; opencodex assigns no account-role semantics to them.
codexAccountNamespaces keys are 1–64 characters, starting and ending with an
ASCII letter or number, with letters, numbers, ., _, or - inside. Reserved JavaScript object
names are rejected. Each value is a valid pool-account id (never internal __main__) or "@main"
for the Codex Desktop account. Provider and reserved openai / combo / policy collisions are
checked case-insensitively; a namespaced combo or routing-profile alias cannot reuse a selector as
its namespace prefix, and configured pool ids or selector targets also cannot reuse a selector. Keep
raw account ids and emails private; the selector is the public name. See Routing Configuration
for exact-selection behavior and precedence.
The Codex Auth dashboard control owns maps that have an explicit codexAccountPickerEnabled field.
Enabling an empty managed map creates privacy-safe selectors; later account additions extend that map
even while picker rows are hidden, without renaming existing selectors. A hand-written map that omits
the flag remains manual and is never auto-expanded. Deleting an account keeps its mapping so exact
routes fail closed while it is missing; adding the same account id again restores the existing public
selector instead of allocating a new one.
Reserved OpenAI providers
Section titled “Reserved OpenAI providers”openai and openai-apikey are fixed reserved ids. openai.codexAccountMode is "pool" by default
and selects across the main plus added accounts; "direct" uses only the current caller/main login.
API uses only its configured API key or key pool. Use a bare model or openai-apikey/<model>; there
is no cross-route credential fallback. API GPT-5.6 rows carry 1,050,000 context / 922,000 max input
metadata, and Pro virtual ids rewrite to the base wire model with reasoning.mode: "pro".
openaiProviderTierVersion: 2 marks the current single-provider projection. Before migrating a
shipped v1 config, opencodex creates config.json.pre-openai-tiers-v2.bak without replacing a
differing backup and rewrites known legacy namespaced selected ids to bare ids.
GPT-6 Astra
Section titled “GPT-6 Astra”gpt-6-astra uses the Codex-login route; openai-apikey/gpt-6-astra uses your API key.
Availability still depends on the upstream account. Native Astra keeps the shipped Codex defaults:
272,000 context, low reasoning, and the low/medium/high/xhigh/max/ultra ladder.
Its Fast catalog description is 2x speed; that is not the billing multiplier.
Set providerContextCaps.openai to 922000 to opt the native group into long context; Astra
stops at its own 872,000 ceiling. Per-model providers.openai.modelContextWindows
and modelAutoCompactTokenLimits can narrow its window and soft compaction budget. For example,
modelAutoCompactTokenLimits: { "gpt-6-astra": 700000 } lowers the long-window default of 784,800.
An explicit smaller provider cap or target limit still wins, including native-alias combos.
The API row has 1,050,000 context, 922,000 maximum input, 128,000 maximum output, text/image input,
and API reasoning efforts through max. OpenCodex’s routed synthetic Ultra control retains its
existing wire-effort mapping; it is not an additional API effort. There is no Astra -pro alias.
Use the existing fastMode setting, or Codex’s service_tier = "fast" with
[features].fast_mode = true; API fast and priority are accepted Fast spellings.
Pricing checked September 5, 2026:
| Astra API (USD per million tokens) | Input | Cached input | Cache write | Output |
|---|---|---|---|---|
| Standard, up to 272k input | 10 | 1 | 12.5 | 50 |
| Standard, above 272k input | 20 | 2 | 25 | 75 |
| Fast, up to 272k input | 20 | 2 | 25 | 100 |
| Fast, above 272k input | 40 | 4 | 50 | 150 |
The API price table reprices the whole request above 272k, counting cached tokens toward the threshold. Fast and long-context rates combine; this also applies to the published GPT-5.6 API rows and their Pro virtual selections.
All built-in dollar estimates use API-reference prices, including Codex-login routes.
Astra and GPT-5.6 therefore use the same API base/cache rates, 2x Fast multiplier, and
published long-context bands on openai and openai-apikey. The two Daybreak Blue selectors
follow the Sol API reference. These are comparison estimates, not invoices or credit-balance
predictions. Explicit provider/model price overrides still take precedence.
Provider namespace aliases
Section titled “Provider namespace aliases”Providers can expose a built-in shorthand, such as agy for google-antigravity. A configured provider name or explicit alias claims that shorthand case-insensitively; a different provider’s built-in shorthand is then suppressed in both catalog names and alias routing. For example, configuring a provider named agy keeps Google’s models under google-antigravity/<model>, while agy/<model> selects the configured provider. Canonical provider names still require an exact case match, and unrecognized prefixes retain the existing model-routing fallback.
Provider entries (OcxProviderConfig)
Section titled “Provider entries (OcxProviderConfig)”| Field | Type | Meaning |
|---|---|---|
adapter |
string |
One of openai-chat, openai-responses, anthropic, google, kiro, cursor, ollama-native, azure-openai (or alias azure), codebuddy, qoder. |
baseUrl |
string |
Upstream API base URL. Most built-in fixed endpoints ignore a mismatch; collision-safe key presets preserve an older same-named custom destination. |
requestPacing? |
{ enabled, requestsPerMinute?, minIntervalMs?, models? } |
Optional client-side outbound request-start pacing, separate from upstream usage, billing, and rate-limit indicators. RPM is converted to an even interval; minIntervalMs may impose a longer interval. Provider limits apply across all models, while models entries use exact upstream model IDs (for example nvidia/llama-3.1-nemotron-ultra-253b-v1) and can only add delay. Queue waits do not consume the upstream response-header timeout. HTTP, Responses WebSocket, and explicit adapter fetchResponse/runTurn dispatches are covered. |
upstreamHttpVersion? |
"auto" | "http1.1" | "h1" | "http2" | "h2" |
Pin the HTTP version used for upstream requests to this provider. Defaults to auto, which lets Bun negotiate. An explicit pin requires an HTTPS target and fails locally when it cannot be honored. Set http1.1 when a provider’s HTTP/2 SSE stream stalls instead of delivering events — the symptom is a long-running streaming request that produces nothing and eventually times out. For Cursor, http1.1/h1 selects its RunSSE + BidiAppend compatibility transport for inference and also pins live model discovery. Management POST/PATCH accept null to clear it back to auto. |
responsesPath? |
string |
Relative resource path for key-auth openai-responses requests. It must start with / and contain no scheme, query, or fragment. |
chatCompletionsPath? |
string |
Relative resource path for openai-chat requests, the mirror of responsesPath and subject to the same shape rules. Needed when one upstream serves Chat Completions and Responses under different prefixes: a per-model wire override changes the adapter and leaves baseUrl alone, so without this an opted-in Chat request would be sent to the Responses base. Z.AI is the shipped example. |
allowEncryptedV2AgentTasks? |
boolean |
Disabled by default. Trust a direct key-auth openai-responses provider to consume or relay opaque encrypted V2 sub-agent tasks unchanged. Eligible routes skip agentTaskRecovery; all other routes keep the existing recovery or fail-closed behavior. OpenCodex does not decrypt, translate, or recover tasks sent through this opt-in. |
upstreamWebsocket? |
boolean |
Opt-in upstream Responses WebSocket transport for openai-responses requests (default false). When the upstream supports the Responses WebSocket protocol, streaming POST requests to the configured Responses path (default /v1/responses) are dialed as WSS over an HTTPS base URL and re-encoded to SSE for the usual pipeline. Forward providers use {baseUrl}/responses; key-auth providers use responsesPath, or the legacy /v1/responses fallback. This mirrors the canonical ChatGPT backend optimization for OpenAI-compatible gateways (for example sub2api) whose WebSocket ingress is measurably faster than its SSE queue. Plain HTTP remains on SSE; non-Responses paths and openai-chat requests stay on HTTP. |
supportsServiceTier? |
boolean |
Tri-state canonical Fast capability fallback. true publishes Fast in the catalog, satisfies service-tier routing requirements, contributes a supported fingerprint, and lets fast mode inject the provider’s canonical wire value on a compatible final adapter. false strips the field and never injects, and exact model declarations cannot reopen it. Absent leaves the provider unclassified: fast mode does not inject or normalize a canonical caller value, and caller values obey the final wire’s forwarding permission (chatServiceTier on Chat; passthrough on Responses). The registry classifies canonical OpenAI (true), DeepSeek, and Volcengine Ark (false); set it explicitly only for custom gateways that genuinely support tiers. |
modelSupportsServiceTier? |
Record<string, boolean> |
Exact upstream model capability overrides. Exact true enables canonical Fast for that model; exact false narrows provider defaults. An explicit provider-level supportsServiceTier: false remains fail-closed and cannot be reopened. Exact true does not authorize foreign caller-tier forwarding on Chat. Undeclared models fall back to provider-wide behavior. Management PATCH /api/providers merges entries and accepts null to clear one. |
chatServiceTier? |
boolean |
Provider-wide Chat-wire opt-in for forwarding caller service_tier values. On a classified route it governs foreign values such as flex, not proxy-owned canonical Fast after capability validation; on an unclassified route it governs every caller value because no Fast capability has been validated. Exact model capability does not authorize foreign forwarding. Responses routes retain their capability-based caller forwarding behavior. |
promptCacheKey? |
boolean |
Provider-wide openai-chat opt-in for forwarding a prompt_cache_key. The adapter forwards the key it is given and never invents one, but the key is not always the caller’s: Claude Messages translation derives one from metadata.user_id, or from a model/system/tools cohort when no metadata is sent. Default off. Enable only when the upstream documents support, because strict gateways may reject the unknown field with HTTP 400. |
preserveResponsesReasoningContent? |
boolean |
Keep plaintext reasoning content on replayed Responses reasoning items instead of blanking it (blanking is the ChatGPT backend’s rule). Enable for upstreams whose contract accepts reasoning replay, such as DeepSeek. Proxy-minted ocxr1 envelopes are always stripped. |
disabled? |
boolean |
Keep the provider on disk but exclude it from routing and model/catalog listings. |
apiKey? |
string |
API key, an ${ENV_VAR} / $ENV_VAR reference, or a keychain:<provider> reference written by ocx provider keychain <name> store. References resolve at request time. See Storing keys in the OS keychain. |
apiKeyTransport? |
"x-api-key" | "bearer" |
Anthropic key header style. Defaults to native x-api-key; valid only for key-auth anthropic providers. |
apiKeyPool? |
ApiKeyPoolEntry[] |
Multi-key pool. apiKey mirrors the active entry; each item has id, key, optional label, and optional numeric addedAt. |
apiKeyPoolStrategy? |
"round-robin" | "fill-first" | "quota" |
How a warm key is chosen before the first attempt when the committed key is already cooling. Omitted keeps rotation reactive-only: the pool moves after a 429 or 401 and not before. round-robin takes the next key in the pool, fill-first keeps the first eligible one, and quota prefers the key with the most remaining headroom, falling back to fill-first order for a provider whose per-key quota is unknown. A healthy committed key is never overridden, so a manual key selection stands. |
defaultModel? |
string |
Model used when this provider is selected without an explicit model. |
models? |
string[] |
Seed/fallback model list. With liveModels: false, a nonempty models list is followed by retainModels; an empty or omitted models list instead seeds defaultModel (if configured), then retainModels, removing duplicate ids in first-seen order. |
liveModels? |
boolean |
Fetch the live catalog on start/sync (default true). Custom providers use ${baseUrl}/models; built-ins may use a registry URL and filter. |
selectedModels? |
string[] |
Catalog allowlist after discovery. Non-empty exposes only those ids; empty or omitted exposes all discovered models. |
retainModels? |
string[] |
Ids kept in the catalog even when live discovery omits them. They need not be repeated in models. Empty or omitted keeps today’s behavior. |
modelDisplayNames? |
Record<string, string> |
Durable labels used only for display, keyed by this provider’s exact upstream model id. Labels win over provider catalog metadata, survive discovery refreshes and provider edits, and never change authentication, adapter behavior, routing, billing, upstream request construction, the routed provider/model selector, or the upstream wire model. Keys are exact and case sensitive. Unknown model ids are kept so a temporarily missing model receives its label when it returns. The map accepts at most 2,000 entries, matching the discovery limit. |
contextWindow? |
number |
Provider-wide context fallback when upstream metadata is absent; otherwise a cap that retains smaller live metadata. The Models dashboard exposes this separately from providerContextCaps. |
modelContextWindows? |
Record<string, number> |
Per-model context fallbacks/caps. These override contextWindow: an unknown window uses the configured value, while smaller live metadata remains authoritative. |
modelInputModalities? |
Record<string, string[]> |
Per-model input hints such as ["text"] or ["text", "image"]. |
modelMaxInputTokens? |
Record<string, number> |
Positive per-model max input limits used for catalog auto-compaction hints. |
modelAutoCompactTokenLimits? |
Record<string, number> |
Positive safe-integer per-model soft auto-compaction budgets. Values can only lower the effective 90%-of-context/max-input envelope and are omitted when no authoritative context window is known. For canonical openai, keys must be exact supported native model IDs without provider or account-selector prefixes. Provider PATCH merges entries; set a key to null to delete it or the whole field to null to clear the map. These null tombstones are PATCH-only. |
defaultMaxOutputTokens? |
number |
Provider-wide openai-chat fallback when the client omits max_output_tokens. |
modelMaxOutputTokens? |
Record<string, number> |
Positive per-model openai-chat fallback budgets; exact/pattern matches beat the provider default. |
modelCosts? |
Record<string, Cost4> |
Per-model display prices (USD per 1M tokens), keyed by that provider’s exact upstream model id — not a provider identifier or a routed provider/model label, e.g. { "deepseek-v4-flash": { "input": 0.14, "output": 0.28, "cacheRead": 0.0028, "cacheWrite": 0 } }. Any model id is a valid key — custom providers may target any OpenAI-compatible endpoint through the openai-chat adapter, and local or internal provider ids work even when they are absent from the built-in catalogs. User-configured prices win over the built-in catalogs in the Logs ~$ and Usage estimates; historical entries are repriced from the current overlay, so editing a price can move past totals. The fallback order is user modelCosts → exact official correction → jawcode catalog → expected-price overlay → model-level vendor fallback, and an explicit all-zero user entry means a known-zero estimate; delete that model entry to restore automatic pricing. All-zero catalog metadata still falls through. Each rate must be a non-negative finite number at most 1,000,000 (USD per 1M tokens); out-of-range rows are rejected by the management boundary and dropped on load. Display-time estimation only: overlays never affect routing, account selection, quotas, or billing. |
headers? |
Record<string, string> |
Extra upstream headers. Authorization, cookies, API-key headers, embedded newlines, and invalid names are rejected. |
openRouterRouting? |
OpenRouterProviderRouting |
Default OpenRouter order, only, and allowFallbacks preferences; valid only for canonical OpenRouter with openai-chat. |
modelOpenRouterRouting? |
Record<string, OpenRouterProviderRouting> |
Exact model-id overrides that replace the provider-wide OpenRouter preference. |
vercelGatewayRouting? |
VercelGatewayRouting |
Default Vercel AI Gateway order, only, and sort ("cost" | "ttft" | "tps") preferences; valid only for canonical Vercel AI Gateway with openai-chat. |
modelVercelGatewayRouting? |
Record<string, VercelGatewayRouting> |
Exact model-id overrides that replace the provider-wide Vercel AI Gateway preference. |
authMode? |
"key" | "forward" | "oauth" | "local" |
Authentication mode (default key). OAuth/subscription credentials are stored outside config.json; local is limited to providers whose registry entry permits it. |
codexAccountMode? |
"pool" | "direct" |
Canonical openai only; defaults to Pool. Direct bypasses pool state. |
refreshPolicy? |
"proactive" | "lazy-only" | "disabled" |
Override this OAuth provider’s Token Guardian policy. |
reasoningEfforts? |
string[] |
Provider-wide Codex reasoning labels to advertise and send. For google-adapter providers, a configured ladder also asserts thinkingLevel capability: direct and Vertex non-image requests send the selected effort as generationConfig.thinkingConfig.thinkingLevel, while Cloud Code Assist uses its envelope-specific path. |
modelReasoningEfforts? |
Record<string, string[]> |
Per-model labels. An empty list hides effort control. As with reasoningEfforts, each configured google-adapter ladder asserts thinkingLevel capability; direct and Vertex non-image requests use the flat Gemini path, while Cloud Code Assist sends it under its request envelope. |
modelSupportsReasoningSummaries? |
Record<string, boolean> |
Set a model to false to stop advertising summaries and strip summary-delivery fields. |
modelReasoningSummaryDelivery? |
Record<string, "sequential" | "sequential_cutoff" | "concurrent" | "concurrent_cutoff"> |
Per-model Responses delivery enum; rewrites an existing delivery field. |
modelAdapters? |
Record<string, string> |
Per-model openai-chat or openai-responses wire override for mixed-wire gateways. Explicit entries beat registry defaults. The OpenCode Go preset selects Responses for gpt-5.6-luna while leaving sibling models on their documented wires; DeepSeek can select native Responses for deepseek-v4-flash; and GitHub Copilot declares Responses-only defaults for the following models (gpt-5.3-codex, gpt-5.4, gpt-5.4-mini, gpt-5.5, gpt-5.6-luna, gpt-5.6-sol, gpt-5.6-terra, gpt-6-astra, grok-4.5, grok-4.6, mai-code-1.1-flash, mai-code-1-flash-picker) because those models reject /chat/completions for agent traffic. Models without a built-in default (for example gpt-5.4-nano) can be opted in here. Single-wire upstream pins and canonical ChatGPT forward reject overrides. |
| xAI Chat Completions (dashboard / CLI) | switch | Grok 4.5/4.6 OAuth Responses requests default to Responses. Existing Chat overrides are migrated once on upgrade; later Chat choices are preserved. Turn on to select Chat for both models, off to select Responses. CLI: ocx provider edit xai --xai-chat on or --xai-chat off (running proxy required). Mixed means only one model currently uses Chat. Other overrides and tier policy stay unchanged. API-key and translated Chat/Anthropic defaults are unchanged. |
xaiResponsesXSearch? |
boolean |
Disabled by default. On an xAI Responses destination, append the provider-hosted x_search declaration only when a live web_search tool survives final request normalization. Existing declarations are not duplicated, caller tool_choice/allowed_tools selectors are never widened, and this is separate from the web-search sidecar’s search.xSearch options. |
modelPreferHostedTools? |
Record<string,string[]> |
Exact-model opt-in for non-forward Responses gateways that reserve a hosted-tool namespace. Currently accepts only ["image_generation"]; a matching model must use the openai-responses wire and support that hosted tool. It removes colliding client image_gen declarations and rewrites their selectors to preserve caller tool choice. For OpenAI API virtual -pro models, the selected public ID is matched first and the resolved base wire-model ID is a fallback. modelAdapters resolves the public ID first, then the base ID; the second resolution determines the final wire. Other models retain normal alias behavior. |
annotateEmptyToolOutputs? |
boolean |
Replace a present-but-empty tool result with a short marker before it reaches the model, so a blank result is not read as a missing one. Applies to blank strings and text-only part arrays; image, file, and encrypted parts are never touched. Defaults to true for DeepSeek from the built-in registry and is otherwise unset. Set false to opt a provider out — an explicit false is preserved across later edits that omit the field. PATCH /api/providers?name=<provider> accepts true, false, or null to clear the override and return to registry-default behavior. |
reasoningEffortMap? |
Record<string, string> |
Provider-wide wire aliases for reasoning labels. Map a label to "__omit__" to drop the reasoning field from the upstream request entirely: reasoning_effort on an OpenAI-compatible wire, and Ollama’s native think field on the Ollama native adapter (#2356). |
modelReasoningEffortMap? |
Record<string, Record<string, string>> |
Per-model wire aliases for reasoning labels. Map a label to "__omit__" to drop the reasoning field from the upstream request entirely. |
reasoningWireFormat? |
"gateway-object" |
For OpenAI-compatible gateways that accept reasoning: { enabled, effort } instead of reasoning_effort. The ClinePass preset sets this automatically. |
noReasoningModels? |
string[] |
Models that reject reasoning/thinking parameters. |
noTemperatureModels? |
string[] |
Models that reject caller-specified temperature. |
noTopPModels? |
string[] |
Models that reject caller-specified top_p. |
noPenaltyModels? |
string[] |
Models that reject presence/frequency penalties. |
noStructuredOutputModels? |
string[] |
Exact model IDs whose openai-chat endpoint rejects response_format. Only an exact requested-model match omits the field; structured-output translation stays enabled for every other openai-chat model. |
noJsonSchemaModels? |
string[] |
Exact model IDs whose openai-chat endpoint rejects a json_schema response_format but still accepts json_object. Such a request is downgraded to json_object instead of being dropped, so a caller asking for JSON still gets JSON. noStructuredOutputModels wins when a model is on both lists. The opencode go, opencode zen, and opencode free presets ship this for their DeepSeek routes. |
omitReasoningEffortWithToolsModels? |
string[] |
Exact openai-chat model IDs that accept a reasoning-effort field on an ordinary turn but reject it once function tools are present. The model keeps its advertised effort ladder; OpenCodex omits the wire field for tool-bearing requests only and the upstream default applies. Narrower than noReasoningModels, which strips reasoning from every request and costs the model its picker entirely. |
parallelToolCalls? |
boolean |
Toggle parallel tool calls. OpenAI Chat defaults on; non-chat adapters advertise only on explicit true. |
terminalContinuationGuard? |
boolean |
Opt in an openai-chat provider to one bounded internal re-ask when an actionable turn announces work, then cleanly stops without a tool call. Defaults to false; explicit false behaves like omission. Combo attempts and routed compaction turns are excluded, and non-openai-chat adapters ignore this option. |
responsesItemIdRepair? |
{ message?: string[]; reasoning?: string[]; repairMissingTerminalIds?: boolean; repairInvalidIds?: boolean } |
Disabled-by-default downstream SSE repair for exact placeholder ids, missing terminal ids, and (with repairInvalidIds) message/reasoning ids missing the canonical msg_/rs_ prefix. Function-call ids are never rewritten. Built-in DeepSeek enables the last two by default. |
responsesSnapshotRepair? |
boolean |
Disabled-by-default client-facing repair for sparse Responses lifecycle snapshots in SSE and JSON. Fills missing canonical status, output, and tool metadata while raw inspection and persistence remain unchanged. |
webSearchBridge? |
{ enabled?: boolean; backend?: "ollama" | "openai" | "anthropic" | "xai" | "gemini" | "exa"; maxSearches?: number; timeoutMs?: number; endpoint?: string } |
Key-auth openai-responses passthrough providers only. Off by default. Codex always declares the hosted web_search tool, and the passthrough relays it on the assumption the destination executes it. A gateway that does not run hosted search answers with a function_call named web_search that nothing runs, and the undeclared-tool guard ends the turn. With enabled: true and an explicit backend OpenCodex intercepts that call, runs the search itself, feeds the result back to the same upstream, and shows Codex a hosted web_search_call cell. Never armed for authMode: "forward" (ChatGPT already searches) or for a provider that executes hosted search upstream. backend is required; there is no implicit default and a missing credential for the named backend leaves the bridge disarmed rather than falling through to another paid search. ollama reuses this provider’s own API key on POST <origin>/api/web_search, so the origin must be https://ollama.com unless the operator names endpoint explicitly. openai / anthropic / xai / gemini / exa reuse the matching sidecar executor and that executor’s own credential (webSearchSidecar.exaApiKey for Exa). The search model comes from webSearchSidecar.model only when webSearchSidecar.backend resolves to the same backend this bridge names; otherwise the bridge runs that backend’s own default, because a model chosen for one vendor is rejected by another. An unset webSearchSidecar.backend resolves to openai, so an unset-backend model reaches an openai bridge and no other. There is no per-provider bridge model override. Streaming turns only. A turn that mixes web_search with another client tool call still fails closed rather than dropping the client’s call. Assistant text such as XML-like <web_search> prose is not executed. Defaults: maxSearches: 3 (1..10), timeoutMs: 60000 (1000..600000). |
retryOn429? |
{ enabled?: boolean; attempts?: number; intervalMs?: number; maxIntervalMs?: number; respectRetryAfter?: boolean } |
API-key providers only (authMode: "key"). Opt-in same-target 429 retry: when retryOn429 is absent the feature is off; object presence enables it unless enabled: false. On 429 the proxy waits (upstream Retry-After or the fixed interval) and replays the identical request on the same key before any key failover — across the main text-turn recovery loop, the Responses passthrough wire, the image/video bridge, the web-search sidecar, and terminal continuations. Only pre-stream HTTP 429 responses are eligible for replay; custom runTurn transports are outside the HTTP retry loop. attempts counts same-key replays after the first 429 (total sends = attempts + 1) and is one request-wide budget shared by the main recovery loop, the terminal-guard continuation, and bridge retries. Exhausting attempts only stops further same-key replays: normal key failover or final-error handling then applies per the available targets — on the key-auth passthrough wire there is no failover, so the exhausted 429 surfaces as-is. Codex itself never retries 429, so this is the only defense for single-key providers. Defaults: enabled: true, attempts: 3, intervalMs: 5000, maxIntervalMs: 60000 (any single wait is capped at maxIntervalMs, itself capped at 600000), respectRetryAfter: true. |
transientRetryOn5xx? |
{ enabled?: boolean; attempts?: number } |
Key-auth openai-chat providers only — a provider whose adapter is openai-responses goes through the Responses passthrough path instead, which applies its own fixed transient ladder and never reads this option. Opt-in retry for pre-stream transient upstream statuses (500, 502, 503, 504, 520, 521, 522): absent means off, object presence enables it unless enabled: false. Covers the initial Responses request, the terminal-guard continuation, and native /v1/chat/completions. attempts is the TOTAL number of upstream sends allowed for one request including the first (1..10, default 3) — it is one budget shared with connection-reset recovery, so 3 means at most three real requests reach the provider. Waits use a fixed 400 ms exponential backoff capped at 5 s and honor Retry-After. Separate from retryOn429, which handles rate limiting; mid-stream failures are never replayed. |
autoToolChoiceOnlyModels? |
string[] |
Models whose tool_choice accepts only auto or none; forced choices are downgraded. |
preserveReasoningContentModels? |
string[] |
Models requiring prior assistant reasoning_content in chat history. |
reasoningDetailsModels? |
string[] |
Models whose endpoint returns thinking as a structured reasoning_details array (MiniMax M-series with reasoning_split); stream deltas are cumulative snapshots that are prefix-diffed, and preserved reasoning replays as a reasoning_details array instead of a reasoning_content string. |
requiresReasoningPlaceholderModels? |
string[] |
Models whose upstream rejects a tool_call continuation missing reasoning_content (DeepSeek thinking mode); a minimal placeholder is injected when the replay cache misses. Defaults to preserveReasoningContentModels; set [] to opt out. |
showThinkingSummary? |
boolean |
Display provider-authored summaries when a Responses client omits reasoning.summary. Explicit wire "none" wins; a client that serializes its preference as omission cannot be distinguished. Raw reasoning remains content and is never relabeled as a summary. The google-antigravity preset defaults to true; explicit false disables that default. CCA Gemini requests also opt into generationConfig.thinkingConfig.includeThoughts when display is enabled; image, Claude and gpt-oss requests do not. This does not change client configuration or global catalog summary defaults. |
thinkingToggleModels? |
string[] |
Chat models using thinking.enabled rather than an effort ladder. |
thinkingBudgetModels? |
string[] |
Chat models using integer thinking_budget; effort maps to a budget fraction. |
noVisionModels? |
string[] |
Text-only models sent through the vision sidecar; matching tolerates an Ollama :size tag. |
escapeBuiltinToolNames? |
boolean |
Escape built-in tool names for Anthropic-compatible gateways and restore them in returned calls. |
anthropicEofTolerance? |
boolean |
Let an Anthropic-compatible gateway complete a stream that ends before message_stop, only when visible text or a complete JSON-object tool input was received. Off by default. |
googleMode? |
"ai-studio" | "vertex" | "cloud-code-assist" |
Google transport/auth mode. Default ai-studio. |
directGeminiWireRenames? |
boolean |
Google only. Applies only to direct AI Studio requests. Omitted or true keeps the -tiered wire rename for Gemini Flash ids (gemini-3.7-flash -> gemini-3.7-flash-tiered); false sends the requested bare ids to the wire unchanged. Vertex preserves the requested model ID, and Cloud Code Assist routing is unchanged. Set false when the configured upstream still serves the bare ids. |
project? |
string |
Vertex or Antigravity Cloud Code Assist project id. |
| — | — | Antigravity account quota probes (retrieveUserQuota and retrieveUserQuotaSummary) always go to Google’s own Cloud Code host through the pinned outbound transport, regardless of a configured baseUrl; the account bearer is never sent to an operator-configured endpoint and a redirect aborts the probe. Only the model-list fallback still honors baseUrl. |
location? |
string |
Vertex location; environment fallback is GOOGLE_CLOUD_LOCATION. |
mcpServers? |
Record<string, CursorMcpServerConfig> |
Cursor only: stdio or Streamable HTTP MCP servers. |
desktopExecutor? |
DesktopExecutorConfig |
Cursor only: external computer-use and record-screen commands. |
unsafeAllowNativeLocalExec? |
boolean |
Cursor legacy boolean, equivalent to nativeLocalExec: "on" only when the newer field is unset. |
nativeLocalExec? |
"off" | "codex-sandbox" | "on" |
Cursor local-exec policy. off is default; codex-sandbox currently fails closed like off. |
With webSearchBridge enabled, a search continuation stays bound to the API-key selection that
served the first request. Changing the selected key, its reference or resolved value, authentication
mode, or base URL during search or provider pacing ends the turn with a bridge error before another
provider request is sent. Changing away and back also ends that continuation. Start a new turn to
use the new selection. Selection changes before the first provider send retain normal reselection.
Custom-model reasoningEfforts normally override discovered provider metadata. The bounded
exception is an explicit custom row whose model id has pinned native Codex capabilities,
including Astra or Daybreak on an arbitrary gateway: its advertised list is intersected with
that model’s pinned native capabilities. Full native identity still requires the canonical
openai Codex-forward destination. An explicit empty list remains empty with no default; a
nonempty incompatible list falls back to the native default as a single choice. Defaults must
belong to the final list. This changes the catalog projection, not stored configuration.
See custom native catalog examples.
Operator-pinned reasoning effort
Section titled “Operator-pinned reasoning effort”Set pinnedReasoningEffort on an existing provider to override incoming effort choices, or
use modelPinnedReasoningEfforts for individual upstream model IDs. Per-model provider pins
win over the provider-wide pin; the root modelPinnedEfforts map is the fallback. These are
operator settings, not provider-registry defaults. They do not change model discovery or the
advertised effort ladder.
{ "pinnedReasoningEffort": "high", "modelPinnedReasoningEfforts": { "example-model": "max" }}Merge these fields into the existing provider row. Accepted values are none, minimal,
low, medium, high, xhigh, max, and ultra. none removes the explicit effort field;
it uses the provider’s default behavior and does not guarantee that reasoning is disabled.
Applicable effort caps still run after the pin, and provider wire mapping/normalization can
lower or omit an unsupported value. ultra is normalized before it reaches an upstream wire.
Compaction maintenance requests are exempt from pins.
PATCH /api/providers?name=<provider> accepts these fields. Omit a field to preserve it;
use null to clear a scalar or the whole map. A map entry set to null or "" removes that
entry while preserving other entries. Malformed writes are rejected before saving. A malformed
optional pin in a hand-edited file is ignored on load without discarding the rest of the config.
Auto-review (approval) model selection
Section titled “Auto-review (approval) model selection”Codex reads auto_review_model_override from the catalog row of the current turn’s model to
choose the model that reviews approval requests. The root auto_review_model setting in
$CODEX_HOME/config.toml applies one reviewer to every catalog row; the provider-scoped fields
below override it per provider. The provider guide
has the operator workflow and a worked example.
autoReviewModel is the provider-wide reviewer target. A value can be a bare model id of that same
provider (the catalog row is normalized to the provider/model slug) or a full public catalog
slug such as opencode-go/deepseek-v4-flash. A bare value resolves against that provider’s rows
first and then against a bare catalog row, which is how a native model such as gpt-5.6-terra is
named, and a bare value that lands outside the provider prints a note naming the row that actually
supplies the reviewer; a value that matches neither is left unresolved. autoReviewModelOverrides
keys are exact upstream model ids of that provider, or the provider’s published alias for one
(modelAliases); either spelling names the same routed row, whose slug carries the upstream id. An
entry wins over the provider-wide value for its model. A provider
stamp wins over the root selector on its own routed rows, and the root selector remains the
fallback for native rows and routed rows without a provider stamp. Removing a provider selector
clears only that provider’s stamps; removing the root selector never clears provider stamps.
Model ids that contain a slash may be written raw or in their encoded catalog form; both
spellings resolve to the same routed row. Model keys preserve case.
Selectors are resolved against the final catalog on each sync, independently of one another, and
each fails closed on its own: an unresolved autoReviewModel emits a diagnostic and stamps no
provider-wide rows, an unresolved autoReviewModelOverrides entry emits a diagnostic and stamps
no per-model override, so a valid provider-wide target remains its fallback. Any selector that does resolve is still applied. Rows without a provider
stamp keep the root selector, or normal upstream auto-review behavior when that is unset. The
canonical openai provider does not accept these fields.
Removing the root selector clears root stamps from every row, including native rows stamped by earlier releases that predate OpenCodex’s provenance marker. That cleanup recognizes a legacy stamp by its shape — one value across the whole catalog that a routed row also carries — so a genuine per-row value matching that shape is cleared with it, and a catalog that has since diverged from that shape needs one manual sync. Provider stamps are never touched by root removal.
PATCH /api/providers?name=<provider> accepts both fields. Use null to clear the scalar or
the whole map; use a map entry of null or "" to remove that model while preserving other
entries. Unrelated provider saves preserve previously configured values.
These fields are available in config.json, the provider management API, and the Dashboard raw
JSON provider editor. Dedicated form controls are not present. Native root stamps record the
previous value and restore it on removal when the stamped value has not been changed externally.
Discovered model display names
Section titled “Discovered model display names”Use modelDisplayNames when a provider returns machine friendly ids but the Codex model picker
needs shorter labels. The map belongs to one provider, so the same model id can have a different
label under another provider. Add the field to the existing provider row in config.json and keep
all other provider settings. The example includes the surrounding required fields for context:
{ "providers": { "xai": { "adapter": "openai-chat", "baseUrl": "https://api.x.ai/v1", "modelDisplayNames": { "grok-4.6": "Grok 4.6" } } }}Supported bare native GPT rows in the local Codex catalog also accept exact labels in
providers.openai.modelDisplayNames, for example "gpt-6-astra": "GPT 6 Astra".
Both startup synchronization and local catalog convergence reapply these labels. Removing a label
restores the original native name only when the row’s display name still matches the applied
override. A newer external display name is preserved subject to existing native metadata normalization;
for example, Astra (gpt-6-astra) still replaces a non-pinned name with its pinned native name.
The label overlay leaves model IDs, metadata (including capabilities), ordering,
routed combo aliases, and account-qualified rows unchanged. This local catalog override does
not relabel the HTTP model listings or virtual *-pro rows.
The effective label order is operator modelDisplayNames, then provider catalog metadata, then the
normal provider/model fallback. The routed selector remains xai/grok-4.6, while the upstream
wire model remains grok-4.6. Labels are display only. They do not change authentication, adapter
behavior, routing, billing, or upstream request construction. Removing a map entry resets only its
label. A management client can set or reset one label with
PUT /api/providers/:provider/model-display-names and a body of
{ "modelId": "grok-4.6", "displayName": "Grok 4.6" }; send displayName: null to reset it.
Provider PATCH does not edit this map. Use this dedicated PUT endpoint to change or remove labels.
The dashboard exposes the same durable setting on Models. Expand the provider, find a
discovered model, and choose Name. The dialog keeps the exact provider/model selector visible
while you save a friendly label. Choose Reset name to return to provider metadata or the normal
selector fallback. Name changes presentation only; the separate alias pencil changes the
short routing alias and is not a display name editor. Native OpenAI and custom model rows keep their
existing controls.
If the change is saved but refreshing fails, the dialog reflects the saved override and keeps Retry available. Retry repeats catalog convergence when the server reported it failed, or reloads the list when only the list request failed. Reset recovery keeps the reset operation; it does not restore the old name. Requests have a 60-second deadline covering the write and its follow-up list refresh. A timeout does not undo a write: use Retry to check the current name before making another change.
Codex catalog and root config.toml settings
Section titled “Codex catalog and root config.toml settings”These settings belong in the root of $CODEX_HOME/config.toml, alongside
approvals_reviewer; they are not provider fields.
| Field | Type | Meaning |
|---|---|---|
auto_review_model |
string |
Public catalog selector in provider/model form, for example opencode-go/deepseek-v4-flash. After each catalog merge, OpenCodex resolves it against the final catalog and stamps the trimmed value as auto_review_model_override on catalog entries. Boundary whitespace is removed; the selector’s slash-delimited components are otherwise unchanged. If the value is absent or blank, existing routed overrides are cleared and normal upstream auto-review selection is preserved. If it is syntactically invalid or absent from the final catalog (including after provider/model removal), OpenCodex fails closed for the override only: it clears the dead override, preserves normal upstream behavior, and emits a diagnostic. Re-adding the provider/model on a later sync allows the configured selector to be stamped again. |
The setting is evaluated after provider discovery, model filtering, native/account-row projection, and merge precedence, so only a selector present in the catalog produced by that sync can become an override. Native upstream values are preserved when the setting is cleared or unresolved. The persisted catalog field is read by Codex for the current turn’s model, which is why a valid configured selector is copied to each applicable entry. Provider-scoped selectors (above) are applied before this root fallback and win on routed rows.
FastWire B1 capability migration
Section titled “FastWire B1 capability migration”Fast capability and arbitrary Chat caller-tier forwarding are independent after FastWire B1. The provider-field definitions above remain the authoritative contract; existing configurations see these migration deltas:
- A Chat provider/model declared Fast-capable no longer needs
chatServiceTier: truefor canonical Fast. Publication, routing eligibility, and injection still require an eligible policy and a compatible FastWire mapping on the final adapter. On classified routes,fastMode: falsestill removes canonical Fast. SetsupportsServiceTier: falseor an exact-modelfalsewhen the route is not Fast-capable. - On an eligible classified route, caller spellings
fastandFASTnormalize throughfastWire.canonicalToWire.priority; callerpriorityremains canonical. Configure a verified mapping tofastonly when that is the upstream’s canonical value. Unclassified routes retain their existing forwarding behavior. - Exact-model
trueno longer authorizes foreign Chat tiers such asflexor vendor-specific values. Those still requirechatServiceTier: true; otherwise they are removed and recorded as dropped caller tiers.
Explicit capability false and Responses caller-tier forwarding retain their existing contracts.
Cursor Fast (cursor-variant)
Section titled “Cursor Fast (cursor-variant)”Cursor has no service_tier field. Its fast product is a different model variant —
claude-opus-5-thinking-high-fast, or a {id:"fast",value:"true"} request parameter for
Grok — so the Cursor entry declares fastWire.kind: "cursor-variant" and the request
builder resolves the variant instead of setting a request field.
Only the bases that actually declare a fast variant advertise Fast: claude-opus-4-7,
claude-opus-4-8, claude-opus-5, grok-4.5, grok-4.6. Every other Cursor row publishes
supportsServiceTier: false, so Codex shows no toggle rather than a dead one.
A base whose umbrella row routes thinking upgrades to its thinking-fast variant, not to
the plain fast sibling — that sibling is a different product with a shorter effort ladder,
and for claude-opus-5 its regular family is quarantined upstream.
fastMode behaves differently per surface, because only Codex has a Fast toggle of its own:
| Surface | fastMode: true |
|---|---|
| Codex | rows stay umbrella rows; the app’s Fast toggle selects the variant |
Claude Code (?ids=cli) |
lists the fast identity, e.g. claude-ocx-cursor--claude-opus-5-thinking-fast |
OpenAI /v1/models |
lists cursor/claude-opus-5-thinking-fast |
| Claude Desktop (3P) | unchanged — its aliases are hashed from the model name |
Dashboard /api/models |
row ids unchanged; they are the enable/disable keys |
Requests are promoted either way: with fastMode: true, picking the umbrella id still
resolves to the fast variant, so a client whose saved config predates the switch does not
need to rediscover. Every legacy variant id keeps routing unchanged.
xAI Priority Processing
Section titled “xAI Priority Processing”The built-in xai preset supports Fast on both of its transports, with different scope.
API-key mode targets https://api.x.ai/v1; routes resolved to openai-chat send
service_tier: "priority" through Chat Completions, while model defaults and overrides can
select the openai-responses transport instead. ocx login xai
instead stores OAuth credentials for the Grok subscription gateway
(https://cli-chat-proxy.grok.com/v1; these credentials refresh automatically), where Fast
is classified per model (live-probed 2026-09-13): grok-4.6, grok-4.5, grok-4.3, grok-4.20-0309-reasoning,
grok-4.20-0309-non-reasoning, grok-build-0.1, and grok-composer-2.5-fast accept
service_tier: "priority" over Grok OAuth and echo it, so those rows advertise Fast, accept
--fast selectors, and forward a caller-sent tier on either wire. grok-4.20-multi-agent-0309
is excluded: the gateway answers service_tier: "default" when sent priority, so it stays
unclassified and its caller tier is not forwarded. Unlisted models stay unclassified on both
transports.
xAI charges Priority Processing at 2× the standard token price for input, output, cached, and
reasoning tokens; cache discounts are applied before the multiplier. Cost estimates use that premium
only when xAI’s response confirms service_tier: "priority". A missing or unparsed response tier is
not confirmation, and an echoed default is a downgrade; all three stay at the standard price.
For grok-4.6, the standard rate per 1M tokens is $2.00 input, $0.50 cached input, and $6.00
output. A prompt of at least 200,000 tokens reprices the whole request at $4.00 / $1.00 / $12.00.
xAI has not published how that long-context band combines with Priority Processing. When a
long-context response confirms priority, the dashboard therefore shows the published long-context
cost with a ≥ marker and a lower-bound explanation; it never invents a stacked multiplier.
OpenRouter Fast
Section titled “OpenRouter Fast”The canonical https://openrouter.ai/api/v1 preset advertises Fast only for these exact
OpenAI-backed model slugs:
openai/gpt-5.6-solopenai/gpt-5.6-terraopenai/gpt-5.6-luna
anthropic/claude-sonnet-5 and undeclared OpenRouter models remain unclassified. A provider-level
supportsServiceTier default is intentionally absent, and a user-set supportsServiceTier: false
still disables the exact-model declarations. The registry declarations apply only while the
provider still targets the canonical OpenRouter base URL; a same-named custom destination is not
assumed to share OpenRouter’s contract.
Fast sends service_tier: "priority". It does not add or rewrite provider.only,
provider.order, or provider.allow_fallbacks. OpenRouter documents priority endpoints as the
first routing choice, followed by graceful fallback to other endpoints when priority capacity is
unavailable. Billing follows the endpoint actually used, and the response reports the actual
top-level service_tier. Pinning tier endpoints and disabling fallback would therefore reduce
availability without improving billing safety.
Request logs use that response echo as the authority. priority confirms Fast as applied;
default records a downgrade and uses the standard-price estimate; a missing field leaves the
attempt assumed rather than guessing a downgrade. OpenRouter’s priority multiplier varies by
upstream and is not bundled here. When priority is confirmed but no exact priority price is known,
the dashboard keeps the standard-price estimate as a documented lower bound and prefixes it with
≥; downgraded attempts have no lower-bound marker.
API-key providers may hold a literal key or an environment reference. OAuth providers use the
credential store populated by ocx login; subscription-backed Claude Code launch behavior is
configured under claudeCode.authMode.
OrcaRouter exposes both forms explicitly: orcarouter is the manual API-key provider and
orcarouter-oauth runs browser consent with S256 PKCE, then stores the returned durable API key as
an account credential. The public defaults intentionally split authentication
(https://www.orcarouter.ai) from inference (https://api.orcarouter.ai/v1). Set
ORCAROUTER_BASE_URL before the first account login for a one-origin self-hosted deployment, or use
ORCAROUTER_AUTH_BASE_URL and ORCAROUTER_API_BASE_URL for separate origins.
For a loopback/private self-hosted endpoint, before the first login, create or update
providers["orcarouter-oauth"] with adapter: "openai-chat", the intended baseUrl,
authMode: "oauth", and an explicit allowPrivateNetwork: true. Login preserves that operator
setting and never grants it from a URL override. Without it, destination validation rejects the
local endpoint for inference and model discovery. The OAuth browser callback listener itself
does not require this provider opt-in. See the OrcaRouter setup example.
Provider diagnostic outbound safety
Section titled “Provider diagnostic outbound safety”Dashboard connection tests and live model discovery use a bounded GET-only transport. Without an outbound proxy, opencodex resolves the hostname once and connects only to that validated address. HTTPS retains the original Host, SNI, and certificate verification; provider config cannot disable certificate checks.
When HTTP_PROXY, HTTPS_PROXY, or ALL_PROXY applies, these operations keep Bun’s native fetch.
URL and literal-address checks still run, but the proxy chooses the final route, DNS answer, and peer,
so opencodex cannot pin or verify that peer. This is an explicit security limitation.
Private/local destinations require allowPrivateNetwork: true and, when an outbound proxy is active,
a matching NO_PROXY entry. Loopback is added automatically; list each LAN host explicitly because
CIDR entries are not interpreted. The matcher supports exact hosts, domain suffixes, optional ports,
bracketed IPv6, and *; for example, list 192.168.1.50 explicitly. Metadata and link-local
destinations stay blocked. Diagnostic
requests reject redirects and report a credential-stripped target. Ordinary provider request redirect
review remains separate from this diagnostic guard.
Two fake-IP DNS accommodations exist for Clash / Surge / Mihomo users, and both apply to DNS
answers only — a literal address in the URL is still rejected. The IANA benchmark range
198.18.0.0/15 (and its IPv4-mapped IPv6 spellings) is accepted whenever an outbound proxy applies
to the host. Mihomo’s default IPv6 fake-IP range fdfe:dcba:9876::/48 is accepted on a stricter
gate: the proxy variable that matches the URL scheme (HTTPS_PROXY for https:, HTTP_PROXY for
http:; ALL_PROXY does not count) must be set, the host must not match NO_PROXY, and the
request is then bound to that proxy explicitly. Any other ULA, an adjacent prefix, or a fake-IP answer
mixed with a real private answer still requires allowPrivateNetwork: true. Provider save-time
validation never applies the IPv6 accommodation.
Codex account pool
Section titled “Codex account pool”Use Codex Auth in the dashboard to add pool accounts and refresh quotas. config.json stores
non-secret metadata; access and refresh tokens use the hardened credential store. Pool routing
separates new/unbound assignment, usage-based proactive switching, and failure recovery. A bound task
normally keeps affinity. By default (pool.cacheAffinity) quota does not rebind it merely because
the usage threshold is crossed; that rebind waits until the bound account is exhausted or otherwise
cannot serve, and then only onto an account with genuine quota headroom and strictly lower usage.
Set pool.cacheAffinity: false to restore threshold rebinding of bound tasks, still only onto such
a destination. Pause, cooldown, reauthentication, and quota refusals still release a live binding
immediately. A transient-failure streak serves the request from another account while keeping the
binding, and the task returns once that account serves again; after 10 minutes the binding is
released normally. An unbound request has no live account binding; this can include an existing visible
task after proxy restart or affinity reset. A pre-stream 429 or 402, or a 5xx response whose bounded
body explicitly reports quota exhaustion, retries once on an eligible alternate account in the same
request, even when usage-based proactive switching is off. The ordinary transient-5xx policy runs
first, so a wrapped quota response may make up to three sends on the exhausted account before pool
rotation. Account changes preserve and replay the conversation context, but provider-side
prompt-cache reuse across accounts is not guaranteed and the cache may need to warm again.
On a 401/403, App login clears that account’s process-local affinity and requires reauthentication.
On a 429, opencodex honors Retry-After, starts the account cooldown, clears affinity, and may
rotate the request to another eligible Pool account. These failure transitions remain active with
autoSwitchThreshold: 0; that setting disables only usage-based proactive switching.
Pausing an account preserves its quota metadata but excludes it from switching, failover, recovery probes, and manual activation. It also clears that account’s thread affinities. In-flight requests keep captured credentials; later turns are rerouted. If every account is paused, Pool routing fails rather than silently choosing one. Pause exhausted refreshes eligible accounts with available credentials and pauses only accounts freshly confirmed at 100%; unknown or failed refreshes remain unchanged.
| Strategy | Behaviour |
|---|---|
quota (default) |
If no active account exists, choose the lowest-usage eligible account across 5-hour, weekly, and 30-day windows. Otherwise retain an eligible active account below autoSwitchThreshold; after it crosses the threshold, an unbound request can move to a lower-usage eligible account. Bound tasks keep cache affinity by default and stay until the account is exhausted (known usage at 100%) or cannot serve (paused, unusable); a move then requires genuine quota headroom and strictly lower usage on the destination, so the task stays put when every account is over the threshold or the only cooler account has unknown usage. Set pool.cacheAffinity: false to let a bound task’s next request move at the threshold, still only onto such a destination. 0 disables this usage-driven re-evaluation, not failure recovery. |
round-robin |
Evenly assign unbound requests across eligible accounts. autoSwitchThreshold does not change normal round-robin selection. accountPoolStickyLimit (1–100) counts assignments on one pick, not successful upstream responses. |
fill-first |
Assign unbound requests to the active account until cooldown, reauthentication, or the configured drain threshold; unknown usage does not force a switch. Healthy bound tasks keep affinity. |
Rotation does not protect against provider enforcement; multi-account use may violate provider terms.
anthropicAccountPool (experimental)
Section titled “anthropicAccountPool (experimental)”This opt-in pools multiple Anthropic OAuth accounts already stored in auth.json. It is off by
default and not battle-tested. Accounts in the same organization may share quota, and automated
rotation may trigger provider restrictions.
| Key | Type | Default | Description |
|---|---|---|---|
anthropicAccountPool.enabled? |
boolean |
false |
Enable sticky session affinity and quota-ranked new-session selection. 429 failover is not gated here: it activates whenever two or more usable accounts are stored, exactly like every other multi-credential provider, and cannot be switched off. |
anthropicAccountPool.autoSwitchThreshold? |
number |
80 |
For new sessions, when the active account reaches this threshold, choose the lowest known cached usage in the configured window; the account chosen does not itself have to be at or above the threshold. 0 disables proactive usage-based switching only — new-session selection and routing recovery after an eligible 429 still consult quotaWindow. |
anthropicAccountPool.strategy? |
"quota" | "round-robin" | "fill-first" |
"quota" |
New-session strategy; quota ranks accounts by the window set by quotaWindow, and fill-first evaluates its drain threshold in that same window. |
anthropicAccountPool.quotaWindow? |
"five-hour" | "weekly" | "max-utilization" |
"five-hour" |
The cached provider-reported utilization bar used for usage-aware account selection. five-hour keeps the original behavior. weekly scores the weekly bar and skips accounts whose 5-hour bar is exhausted while another eligible account remains, but falls back to exhausted candidates when none do. max-utilization scores the highest known bar, so it can use 5-hour usage before weekly usage is available; if neither is known, the account follows unknown-usage ordering. Known usage ranks before unknown usage under the opt-in weekly and max-utilization windows only; an omitted or explicit five-hour preserves the legacy ordering. If every eligible account is unknown, selection still returns one in eligible order. After the documented lower-5-hour tie-break, exact ties preserve eligible order. A healthy affinity-bound session is not proactively rebalanced. For new-session assignment and routing recovery after an eligible 429 replacement, quota ranks eligible candidates directly with this window; fill-first advances in stable order using this window’s threshold and exhaustion rules; round-robin ignores it. Cooldown, failover limits, and reauthentication eligibility remain separate local state. Per-account weekly bars come from usage probes or observed response headers. |
anthropicAccountPool.stickyLimit? |
number |
1 |
Successful new-session binds retained on one round-robin selection. Range 1–100. |
When enabled, 429 records a cooldown and may rotate within the request. The cooldown length comes
from a usable Retry-After, otherwise from the latest valid reset time among rate-limit windows
Anthropic reports as rejected, including weekly windows. Valid upstream deadlines are not
shortened to a fixed cooldown ceiling; non-finite or unrepresentable deadlines are ignored.
A refusal with no usable deadline falls back to a 60-second default backoff. Affinity is process-local
and size-bounded. Credential 401/403 marks the account as needing reauthentication. If all eligible accounts are cooling, clients receive 429 with
Retry-After when known, not an authentication error.
Anthropic responses also report the serving account’s 5-hour and weekly utilization, and whichever of those two a given response carries is recorded against that account — each window independently, on refusals as well as successes. Usage-aware selection therefore works from the accounts you actually use, without waiting for the dashboard Providers page to poll them. These readings refresh the existing row rather than replacing it, so the model-scoped weekly bars that only the usage endpoint reports are preserved until their known reset time passes. Expired measurements become unknown, including retained standard windows omitted by later headers. A reset-only header cannot extend an older utilization measurement. Values with no known reset retain their existing behavior; missing measurements are never replaced with zero usage.
Header observations do not postpone usage probes or clear a failed probe’s unavailable status. After restart, cached Anthropic observations remain available while the next quota read probes again, because the saved observations do not include the probe clock.
oauthAccountFailover
Section titled “oauthAccountFailover”Rotates to another logged-in account of the same provider when one is rate-limited, for OAuth providers that have no pool of their own — xAI, Cursor, Kimi, GitHub Copilot, Google Antigravity, and Nous.
Logging in a second account is what turns this on, and nothing turns it off. Rotation
activates for any of those providers holding 2 or more accounts that are not flagged for
reauthentication — the same rule apiKeyPool already applies to a 2+ key pool. A provider with
one stored account behaves exactly as before.
Rotation here runs only after upstream has already refused the request, so the only choice a disable switch could offer is between retrying on a second account you deliberately logged in and returning a 429 while that account sits idle. Refusing rotation is expressed by not storing a second account.
| Key | Type | Default | Description |
|---|---|---|---|
oauthAccountFailover.enabled? |
boolean |
presence-driven | Global override for the pre-dispatch account preference only. false stops a healthy request being steered toward the account with more known headroom. It does not disable 429 rotation. |
providers.<name>.oauthAccountFailover.enabled? |
boolean |
inherits | Per-provider override for the same preference; beats the global setting in either direction. false declines the preference for this provider even when the global setting is true, and true opts this provider in even when the global setting is false. Reactive 429 rotation is unaffected either way. |
providers.<name>.oauthAccountFailover.strategy? |
"quota" | "round-robin" | "fill-first" |
— | Pool strategy for a generic OAuth provider (#695). Persisted through ocx account strategy <provider> <name> or PUT /api/oauth/accounts/pool. The selector acts on it only while pool.kernel is on; with the flag off, omitted and set behave the same. quota is the pre-kernel behaviour either way. |
providers.<name>.oauthAccountFailover.autoSwitchThreshold? |
number |
80 |
0–100 usage percent at which fill-first advances off the active account (#695). Set with ocx account auto-switch <provider> threshold <n>. Read only under pool.kernel with strategy: "fill-first"; an account with no measured usage counts as under the threshold. |
providers.<name>.oauthAccountFailover.stickyLimit? |
number |
1 |
Successful dispatches retained on one round-robin selection, 1–100 (#695). Read only under pool.kernel with strategy: "round-robin". |
To decline proactive account steering for one provider whose terms you would rather not test, while still recovering from a rate limit:
{ "providers": { "cursor": { "oauthAccountFailover": { "enabled": false } } }}That setting survives logging in, adding an account, and reauthenticating.
Generic OAuth providers (Google Antigravity, xAI, Cursor, Kimi, GitHub Copilot, Nous, and any
other OAuth provider outside the Codex and Anthropic pools) also accept strategy and
autoSwitchThreshold on the same key, through GET/PUT /api/oauth/accounts/pool?provider=<name>
and the ocx account strategy / ocx account auto-switch / ocx account sticky verbs. The response carries
"inert" for those three fields only — true while they are stored but not consumed,
false once pool.kernel is on and they actually select an account — enabled is live and governs the pre-dispatch
preference. quotaWindow is not part of the generic contract. Codex (/api/codex-auth) and Anthropic
(anthropicAccountPool) keep their own contracts unchanged.
Deliberately narrower than anthropicAccountPool: no session affinity, no quota-ranked
selection, no probe leases. It answers one question — the account that just returned 429 is
cooled, is there another one available.
The Codex pool and the Anthropic pool are excluded and keep their own rotation; enabling this changes neither. A provider with a single stored account is a strict no-op, and no cooldown is recorded for it.
On a 429 the failed account is cooled using Retry-After when present (capped at 15 minutes)
or a default backoff, and the request is replayed on the next eligible account, up to three
rotations per request. An account flagged for reauthentication is never selected. Cooldowns are
process-local, so a restart forgets them.
Rotation carries the alternate account’s full credential snapshot, not just its bearer, so a provider that pairs routing metadata with its token — Antigravity’s Cloud Code Assist project id, for example — cannot end up sending one account’s token with another account’s metadata.
Current scope is the ordinary Responses request paths. Cursor reports rate limits as adapter events rather than an HTTP status, and the standalone Antigravity image endpoint has its own request path; neither rotates yet.
Managed record shapes
Section titled “Managed record shapes”apiKeys[] entries contain id, name, generated key, and ISO createdAt strings.
codexAccounts[] entries require id, email, and isMain, with optional plan,
chatgptAccountId, and privacy-safe logLabel. These records are normally dashboard-managed.
tokenGuardian (OcxTokenGuardianConfig)
Section titled “tokenGuardian (OcxTokenGuardianConfig)”| Field | Type | Default | Meaning |
|---|---|---|---|
enabled? |
boolean |
false |
Global proactive-refresh switch. |
tickSeconds? |
number |
21600 |
Sweep interval (6 hours, minimum 60 seconds). |
jitterSeconds? |
number |
300 |
Random delay before a sweep. |
concurrency? |
number |
3 |
Maximum simultaneous refreshes. |
leadSeconds? |
number |
900 |
Extra refresh lead time beyond one tick. |
failureBackoffBaseSeconds? |
number |
300 |
Initial transient-failure backoff. |
failureBackoffMaxSeconds? |
number |
3600 |
Backoff ceiling and permanent-failure delay. |
codexWarmupEnabled? |
boolean |
false |
Opt into synthetic Codex pool-account validation. |
codexWarmupMaxAgeSeconds? |
number |
691200 |
Revalidate an account after 8 days. |
codexWarmupModel? |
string |
gpt-5.6-luna |
Native model used for optional warmup. |
Fixed provider endpoints
Section titled “Fixed provider endpoints”Routing resolves a provider endpoint before the adapter. For most built-ins, the registry endpoint
wins over configured baseUrl. Four entry types keep the configured URL:
- override-enabled providers:
ollama,vllm,lm-studio,litellm,qwen-cloud, andalibaba-token-plan-intl; - registry templates filled by the user, such as
azure-openaiandcloudflare-ai-gateway; - promoted fixed API-key presets preserving an older same-named custom destination; and
- providers absent from the registry.
Adapters can adjust the resolved URL afterward. Kiro, for example, follows the imported credential’s
API region for canonical runtime.{region}.kiro.dev. See Adapters.
When routing discards baseUrl, opencodex logs the registry endpoint and only the configured origin;
a configured path may itself contain a credential. Remove the unused URL or choose the provider entry
matching the intended region. alibaba-token-plan is pinned to Beijing, while
alibaba-token-plan-intl covers international endpoints.
For a broken openai-responses gateway, repair belongs on the provider object:
{ "providers": { "custom-gateway": { "adapter": "openai-responses", "baseUrl": "https://gateway.example/v1", "apiKey": "${GATEWAY_KEY}", "responsesItemIdRepair": { "reasoning": ["rs_0"], "message": ["msg_0"], "repairMissingTerminalIds": true } } }}Placeholder lists are exact matches. Leave the field unset for normal/stateful Responses providers so passthrough stays byte-for-byte identical.
Cursor provider (adapter: "cursor")
Section titled “Cursor provider (adapter: "cursor")”The Cursor bridge is experimental. After ocx login cursor, add or edit providers.cursor.
If a proxy cannot carry Cursor’s default HTTP/2 stream, set upstreamHttpVersion to "http1.1"
or its "h1" alias.
This switches inference to Cursor’s RunSSE + BidiAppend compatibility transport and uses
HTTP/1.1 for GetUsableModels discovery as well. The value requires an HTTPS baseUrl. Leave it
unset or use "auto" for the existing HTTP/2 behavior. In the dashboard choose
Providers → Cursor → Settings → Cursor transport.
Cursor Router’s optimization ladder is exposed as separate Codex ids because the picker cannot render Cursor-specific model parameters:
| Codex model | Cursor Router mode |
|---|---|
cursor/auto |
Team/account default |
cursor/auto-cost |
Cost |
cursor/auto-balance |
Balance |
cursor/auto-intelligence |
Intelligence |
Explicit variants send Cursor’s default model with its optimization parameter, preserving the
selection on every request. They remain available when live discovery omits default.
Vision
Section titled “Vision”Native Cursor vision uses SelectedImage (JPEG soft-cap + blobIdWithData) for models that can
see images natively — Claude, Gemini, GPT, Kimi, and Grok among them — using active-turn data:
images only. Earlier-turn images replay as [image attached] text markers; remote or undecodable
images become omission markers. Auto, the Composer family, and GLM (glm-5.2, glm-5.3) stay on
the curated noVisionModels list and use the vision describe sidecar instead.
Cursor server-driven local tools are disabled by default. Codex continues using its own tools such as
apply_patch and exec_command with its own approval and sandbox policy:
"off"(default) rejects Cursor-nativeread,write,delete,ls,grep,shell, andfetchexecution."on"opts into trusted-local execution and bypasses Codex approval/sandbox semantics."codex-sandbox"is retained for compatibility but fails closed like"off"; request prose is not trustworthy sandbox attestation.
{ "providers": { "cursor": { "adapter": "cursor", "baseUrl": "https://api2.cursor.sh", "authMode": "oauth", "defaultModel": "auto", "nativeLocalExec": "off" } }}Set nativeLocalExec on providers.cursor, not at the top level. In the dashboard use Providers
→ Cursor → Edit JSON, save, then restart. Legacy unsafeAllowNativeLocalExec: true equals
nativeLocalExec: "on" only when nativeLocalExec is unset. MCP, screen recording, and computer use
are controlled separately by mcpServers and desktopExecutor.
Each mcpServers.<name> accepts either command (stdio) or url (Streamable HTTP). Stdio also
accepts args, env, and cwd; HTTP accepts headers. Both support enabled (default true) and
toolPrefix. desktopExecutor accepts computerUseCommand, recordScreenCommand, cwd, env,
and timeoutMs (default 30000). Commands run through sh -c, read one JSON request from stdin,
and must write one JSON result to stdout.
OpenRouter provider routing
Section titled “OpenRouter provider routing”OpenRouter can serve one model through several inference providers. openRouterRouting keeps
requests on preferred providers; modelOpenRouterRouting replaces it for exact model ids. This is
useful for prompt-cache affinity because cache support, retention, hit rates, and pricing vary by
inference provider.
Provider names are OpenRouter slugs. allowFallbacks: false fails closed; true allows another
eligible provider after the ordered list. only is always an allowlist.
{ "providers": { "openrouter": { "adapter": "openai-chat", "baseUrl": "https://openrouter.ai/api/v1", "apiKey": "${OPENROUTER_API_KEY}", "openRouterRouting": { "order": ["deepseek"], "allowFallbacks": false }, "modelOpenRouterRouting": { "anthropic/claude-sonnet-5": { "only": ["anthropic"], "allowFallbacks": false } } } }}Vercel AI Gateway provider routing
Section titled “Vercel AI Gateway provider routing”Vercel AI Gateway can route a model across multiple underlying inference providers. vercelGatewayRouting configures provider-wide preferences; modelVercelGatewayRouting replaces it for exact model IDs. Leaving both unset makes resolveVercelGatewayRouting() return undefined, so Chat request builders omit the provider field and Vercel AI Gateway retains its default dynamic routing behavior.
order: Vercel AI Gateway upstream provider slugs in priority order.only: explicit allowlist restricting eligible Vercel AI Gateway upstream providers.sort: automatically sort eligible providers by"cost"(lowest cost),"ttft"(time to first token), or"tps"(tokens per second).
{ "providers": { "vercel-ai-gateway": { "adapter": "openai-chat", "baseUrl": "https://ai-gateway.vercel.sh/v1", "apiKey": "${VERCEL_AI_GATEWAY_KEY}", "vercelGatewayRouting": { "sort": "ttft" }, "modelVercelGatewayRouting": { "zai/glm-5.2": { "only": ["novita", "deepinfra"], "order": ["novita", "deepinfra"] } } } }}Model keys are Vercel public model selectors without the outer OpenCodex provider prefix. Selecting
vercel-ai-gateway/zai-glm-5.2 restores native zai/glm-5.2 before applying the model rule. The
same mapping applies to a native vercel/<model-id> selector: use the encoded
vercel-ai-gateway/vercel-<model-id> selector in OpenCodex and keep vercel/<model-id> as the
model key.
Static model allowlists
Section titled “Static model allowlists”Storing keys in the OS keychain
Section titled “Storing keys in the OS keychain”By default a provider’s apiKey and apiKeyPool sit in config.json (mode 0600, atomic writes).
If you would rather keep the key material out of the file, move it into the OS credential store:
ocx provider keychain deepseek status # store: file | env | keychain, and whether the keychain answersocx provider keychain deepseek store # move active key + pool into the OS keychainocx provider keychain deepseek restore # bring the plaintext back and delete the keychain itemsThe same operations are GET/POST /api/providers/keychain. After store, config.json holds
"apiKey": "keychain:deepseek" (pool entries keychain:deepseek/<id>) and the secret lives under the
opencodex.provider-api-key.v1 service in macOS Keychain, Windows Credential Manager, or the Linux
Secret Service. Backups of config.json therefore carry references only. Key rotation and failover
keep working: pool entries compare by reference, so a rotation never writes plaintext back.
Before touching the config, store writes and reads back every entry; if the keychain is unavailable
or the read-back does not match, it refuses with 503 and leaves the file as it was. At request time
a reference that cannot be read yields no credential and one warning per key — there is no plaintext
fallback, by design.
When not to opt in: a proxy running as a headless service (systemd, launchd, Task Scheduler) or in a
container usually has no unlocked keychain session, so requests would fail closed. Use an
${ENV_VAR} reference in the service environment there instead. Env references are left untouched
by store.
The zhipu-bigmodel-responses preset seeds glm-5.3, glm-5.3-flash and glm-5-turbo with
liveModels: false for https://open.bigmodel.cn/api/v1. Its static roster and
per-model context, effort, and summary metadata come from the
BigModel Responses guide.
The official local models.json example does not establish a live /models API.
With liveModels: false, an empty or omitted models list seeds the configured defaultModel
first, followed by retainModels; duplicate ids are removed while preserving first occurrence.
A nonempty explicit models list instead seeds models followed by retainModels, without
implicitly adding a different defaultModel. That default can still be listed explicitly in
models or retainModels. If none of these fields supplies an id, the static seed is empty.
This is seed order, not a promise of final picker order. selectedModels, disabledModels and
provider-disabled policy still apply. authMode: "forward" keeps its separate branch and does
not use this routed static seed. These rules do not change live-discovery failure fallback.
Live discovery rejects more than 4 MiB or 2,000 raw model rows before caching; built-in presets may use lower limits and filter to chat-eligible rows. Oversized or malformed results follow stale/configured fallback. A valid zero-eligible result remains authoritative and is not silently replaced or truncated.
Use selectedModels when discovery should still run but only selected ids should appear in Codex and
/v1/models. The dashboard retains the full discovered list for later allowlist changes.
Use retainModels for the opposite problem: a provider whose /models endpoint omits an id that is
still callable (a private deployment, a preview id, an OpenAI-compatible gateway with a partial
listing). Listed ids are kept in the routed catalog with the same context and effort hints as
models, and they survive liveModels: false too. selectedModels still narrows what is visible,
so an id must be in both lists when an allowlist is active. Retaining an id does not make the
upstream accept it; a wrong id fails at request time with the upstream error. From the CLI:
ocx provider edit <name> --retain-models gemini-3.7-flash,other-id (- clears).
Preview GPT-5.6 fallback entries use the same mechanism. The OpenAI API-key preset seeds base and Pro
ids with context 922000 and max input 922000; OpenRouter seeds openai/gpt-5.6-sol,
openai/gpt-5.6-terra, and openai/gpt-5.6-luna with context 922000. Pool/Direct advertises
922000; the synced catalog advertises max while keeping xhigh distinct.
{ "providers": { "openrouter": { "adapter": "openai-chat", "baseUrl": "https://openrouter.ai/api/v1", "apiKey": "${OPENROUTER_API_KEY}", "liveModels": false, "models": ["deepseek/deepseek-v4-flash", "qwen/qwen3-coder-plus"] } }}Complete example
Section titled “Complete example”{ "port": 10100, "defaultProvider": "openai", "providers": { "openai": { "adapter": "openai-responses", "baseUrl": "https://chatgpt.com/backend-api/codex", "authMode": "forward" }, "anthropic": { "adapter": "anthropic", "baseUrl": "https://api.anthropic.com", "authMode": "oauth", "defaultModel": "claude-sonnet-4-6" }, "ollama-cloud": { "baseUrl": "https://ollama.com/v1", "apiKey": "${OLLAMA_API_KEY}", "defaultModel": "glm-5.2", "noVisionModels": ["glm-5.2", "glm-5.3", "gpt-oss", "qwen3-coder", "deepseek-v4-flash"] } }, "subagentModels": ["anthropic/claude-opus-5", "ollama-cloud/glm-5.2"], "disabledModels": [], "websockets": false, "webSearchSidecar": { "maxSearchesPerTurn": 3, "routedModelStallTimeoutMs": 200000, "timeoutMs": 60000 }, "visionSidecar": { "enabled": true }}OpenCode Go Responses compatibility
Section titled “OpenCode Go Responses compatibility”On non-forward requests whose resolved endpoint is https://opencode.ai/zen/go/v1/responses, OpenCodex moves
Codex’s additional_tools input declarations into top-level tools after tool and namespace
normalization. Supported hosted tools are preserved until model-specific filtering; malformed
wrappers remain unchanged. This does not discard ciphertext or unknown agent-message content.
The check uses the final URL, so endpoint-inclusive base URLs and split baseUrl/responsesPath
configurations receive the same behavior. A custom path resolving elsewhere does not.
The canonical opencode-go preset defaults to statelessResponses: true: requests use explicit
history with store: false, without previous_response_id, conversation, background,
metadata, or stored prompt references. This avoids Go’s rejection of reasoning ciphertext
combined with previous_response_id. The continuation cache records reasoning in the same
representation returned to the client, including the visible content-to-summary rewrite, so
echoing full history with previous_response_id does not duplicate that history. Hidden-summary
requests and opaque reasoning blobs retain their existing representation. Cache hits can also
supply earlier history for delta continuations;
after a cache miss, the proxy returns previous_response_not_found before upstream dispatch so the
client can resend the complete conversation without previous_response_id. Stateless
repair labels orphan results and missing tool results; it cannot reconstruct lost history or
prove whether a missing tool execution succeeded.
An explicit statelessResponses: false is preserved. Existing canonical preset configurations
receive the default only when the setting is absent; custom renamed entries keep their configured
value and do not acquire this default by destination matching. Chat model routes keep their
existing protocol. The stateless flag does not force Responses streaming into JSON.
OpenCode Go session affinity
Section titled “OpenCode Go session affinity”Every request opencodex routes to an OpenCode Go destination carries an x-opencode-session header.
The upstream began rejecting requests without it on 2026-09-06, so the header is not an optimization.
The value depends on what the request already knows about itself:
- An operator-configured
x-opencode-sessionon the provider is preserved exactly as written. - A request that carries conversation identity — Codex thread headers, a Claude
metadata.user_id, asession_id, or an inboundx-opencode-session— is hashed into a stable per-conversation value, so every turn of one conversation reaches Go under the same session. - A request with no identity at all, such as a model-availability probe or a first request before any conversation metadata exists, receives a value allocated once for that request. It is isolated from other requests rather than shared, and it survives the places opencodex rebuilds the request: the translation to the internal Responses shape, compaction, combo children, and the policy-fallback retry that hands the turn to the next candidate.
Non-Go destinations are unaffected: opencodex never derives or adds the session header for them. A header an operator configured on such a provider is still sent, because opencodex leaves that configuration alone.
OpenCode Go reasoning efforts
Section titled “OpenCode Go reasoning efforts”Go catalog rows preserve their configured reasoning efforts exactly, including during
catalog sync. OpenCodex does not append synthetic max or ultra choices to these rows.
Use modelReasoningEfforts and modelDefaultReasoningEfforts for each model’s accepted
upstream values. Key these per-provider maps by upstream model ID, not the routed
opencode-go/<model-id> catalog slug. For example, a configured ["high", "max"] list
remains exactly those two choices; a configured ["high", "xhigh"] list does not gain max.
See the OpenCode Go model list for the current roster.
A configured subset can exclude the lower tiers. Other providers retain their existing behavior.
For a native-first picker, include native ids in modelPickerOrder followed by the
routed ids. This orders the complete picker while preserving OpenCodex’s separate natural-priority
guidance calculation. Native Codex’s advertised five follow picker priority and may change;
exact-name override eligibility is not limited to that advertisement. Routed-only orders keep
their previous behavior. See the
ordering migration note.
modelDisplayNames on a provider controls readable labels without changing wire ids.
Routed agent messages
Section titled “Routed agent messages”With the openai-responses adapter, Codex
agent_message items containing nonempty arrays of supported plaintext parts become user messages when authMode is not "forward"
(for example, "key"). Providers using authMode: "forward" retain these items unchanged.
agent_message is private to the ChatGPT Codex backend, and the routed destinations
reported so far answer the whole request with
422 unknown item type "agent_message"; Codex replays sub-agent history on every
subsequent turn, so the thread keeps failing until the item is converted.
Author and recipient remain explicit text metadata, and the content parts are preserved.
For HTTPS api.x.ai and cli-chat-proxy.grok.com on the standard port, non-forward
Responses dispatch also accepts a nonblank string child result and turns it into one
input_text part. The original string, including leading/trailing whitespace and newlines,
is preserved. Other destinations keep string-valued agent messages unchanged. Empty or
whitespace-only strings remain unchanged, as do incomplete and mixed encrypted/unknown shapes.
Encrypted and unknown content is not normalized; native encrypted tasks still require the
separate opt-in task recovery.
With task recovery enabled, replayed NEW_TASK and MESSAGE items reuse a cached assignment only
after validating the caller and matching the parent-thread scope. Replay restoration
does not make a new recovery request or extend cache expiry. Expired or unseen
ciphertext is not replaced. Fresh encrypted NEW_TASK and MESSAGE items use the same
opt-in recovery path, including native-parent send_message delivery. Message type,
sender, recipient, parent scope and caller credentials remain part of validation or cache identity.
When a request contains several agent messages, cached replay restoration checks each
message independently. The cache separates message type, sender, recipient and ciphertext
within the admitted caller/account and parent scope. Fresh recovery only handles the
current tail message (ignoring trailing compaction_trigger or additional_tools metadata).
It does not batch-recover unseen historical messages; those remain unchanged. A cache miss
or expiry does not extend the history-recovery contract.
Sender and recipient on routed Responses are context for the receiving model, not a new machine-readable routing protocol. Tool routing continues to use the existing collaboration contracts.
Per-model capability declarations
Section titled “Per-model capability declarations”modelCapabilities stores explicit declarations keyed by exact upstream model ID. IDs preserve case and must not contain surrounding whitespace. Each entry may contain inputModalities (text, image, audio, video), contextTier (default, long_context) and video.processing (static, agentic). These are operator declarations, not proof of provider support. Context-tier and video fields currently record intent only and do not activate upstream behavior or increase catalog windows.
The raw provider editor and provider API expose this map. POST/PUT replace an explicitly supplied map and reject null entries. PATCH merges individual axes; null clears a map, model, axis or video processing value, while {} makes no change. Omitted provider overwrites preserve the existing map. Malformed hand-edited files retain valid independent axes and treat malformed explicit input modalities as text-only, with a diagnostic.
An explicit modelCapabilities.<id>.inputModalities now takes precedence over legacy modality hints for that exact routed model. A text-only declaration uses the existing vision sidecar to replace images with descriptions; if no sidecar is available, the request receives an explicit omission marker before dispatch. Native Chat image requests divert through this path. The catalog can still advertise image attachment support because the proxy provides the description step. Context-tier and video processing declarations remain inert pending their transport support.
Renamed API-key presets
Section titled “Renamed API-key presets”A provider saved under another name, such as CommandCode, inherits missing reasoning-effort metadata when its adapter and fixed API-key endpoint match a registry preset. Your explicit per-model lists, including [], remain authoritative. An omitted provider-wide list inherits the preset default; an explicit list remains unchanged. This does not match OAuth, unrelated endpoints, or templated/custom endpoint presets.

