Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5a635e8
fix(google): keep Gemini progress-history splitting linear
luvs01 Oct 18, 2026
3328f72
fix(openai-chat): reconcile identified MiMo tool-call echoes
fontanierh Oct 19, 2026
e42ddc1
test(openai-chat): cover streamed invalid and paired identified tool …
fontanierh Oct 19, 2026
f3c3708
fix(google): keep Claude tool results contiguous before images
yuanyuan2026 Oct 18, 2026
f12a0ed
feat(google): honor showThinkingSummary on Vertex and direct Gemini
chilung-cgu Oct 18, 2026
ac970ea
fix(google): require a non-empty reasoning ladder for thinking eligib…
chilung-cgu Oct 18, 2026
38a6370
test(google): cover stream parity and adapter reuse for showThinkingS…
chilung-cgu Oct 18, 2026
de5df7a
docs(test): add JSDoc comments to thought-summary test helpers
chilung-cgu Oct 18, 2026
ff7c430
fix(mistral): enable vision sidecar for GLM 5.3
adaurban Oct 17, 2026
84cfa0c
docs(mistral): clarify catalog sync and client restart
adaurban Oct 17, 2026
6e8a758
test(mistral): assert GLM vision override in catalog
adaurban Oct 17, 2026
8a282e6
feat(opencode-go): update model catalog with Gemini 4 Argon, GPT-6 Lu…
yeongjunyoo Oct 18, 2026
097aac6
feat: translate inline audio to chat completions
harryzhou2000 Oct 17, 2026
b18e215
test: cover translated chat audio forwarding
harryzhou2000 Oct 17, 2026
b96d320
docs: describe inline audio on the chat wire
harryzhou2000 Oct 17, 2026
fce0e56
fix: tighten translated audio media guards
harryzhou2000 Oct 17, 2026
2d035ef
docs: define translated audio media contract
harryzhou2000 Oct 18, 2026
283ea13
test: register chat audio layout coverage
harryzhou2000 Oct 18, 2026
2112e53
docs: clarify orphan audio and raw base64
harryzhou2000 Oct 19, 2026
02ef0e0
Keep DeepSeek image replays below the provider body limit
fontanierh Oct 19, 2026
bb0a351
fix: scope image preparation and retain rebuild upload pacing
fontanierh Oct 19, 2026
b128b63
fix(opencode-go): leave GPT-6 Luna tiered pricing unset
lidge-jun Oct 19, 2026
d59d401
docs(structure): move the Mistral GLM vision-sidecar note into the im…
lidge-jun Oct 19, 2026
119d494
fix(chat): carry standalone MiMo freeform recovery
lidge-jun Oct 19, 2026
1c3bb56
docs(chat): describe bounded MiMo bare-call recovery
lidge-jun Oct 19, 2026
c046ad0
fix(deepseek): fence queued image uploads on persistence consent
lidge-jun Oct 19, 2026
1382866
fix(opencode-go): never undercut the declared DeepSeek V4.1 Flash window
lidge-jun Oct 19, 2026
5ce703c
test(responses): inventory the DeepSeek image-file transport owner
lidge-jun Oct 19, 2026
e17eea8
fix(openai-chat): preserve opaque context and bound bare-call scanning
lidge-jun Oct 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions docs-site/src/content/docs/guides/pi.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,14 @@ An explicit reasoning effort of `none` survives Chat conversion. Output limits a
controls are preserved for generic API-key Responses targets; the canonical ChatGPT target
still applies its own restrictions. This does not make all providers' controls equivalent.

**Audio and most file attachments need a native input wire that supports them.** A document
that carries its own base64 bytes in a user message is the exception: it survives translation
and reaches the Anthropic, OpenAI Chat and Google wires as a native document, file part and
inline data part. Everything else still returns an explicit error rather than succeeding
without the attachment — audio, a file-ID or remote reference the proxy cannot dereference, an
attachment in a tool output or a system message, and a document routed to a wire with no byte
Inline WAV/MP3 audio in a user message or tool result survives translation to OpenAI Chat as an
`input_audio` content part; tool-result audio is promoted into a follow-up user message after
the paired tool results because Chat tool messages are text-only. A document that carries its
own base64 bytes in a user message is also carried: it reaches the Anthropic, OpenAI Chat and
Google wires as a native document, file part and inline data part. Everything else still
returns an explicit error rather than succeeding without the attachment — remote or
unsupported audio, a file-ID or remote reference the proxy cannot dereference, an attachment
in a system message, a document in a tool result, and a document routed to a wire with no byte
carrier. File-ID-only images have the same restriction because translated adapters cannot
resolve those IDs. Convert the attachment to text first, or use a native wire and model that
support it. Native Chat and raw Responses (including Azure) retain their existing behavior;
Expand Down
6 changes: 4 additions & 2 deletions docs-site/src/content/docs/guides/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -857,10 +857,12 @@ when a stable upstream session is required.

**MiMo tool-call echoes.** On OpenCode Go and other Chat Completions routes, a bare
`<tool_call>` block is hidden when it duplicates one structured call to the same tool
with the same effective input. If the input differs or several calls could explain
with the same effective input. This includes MiMo wrappers such as
`<tool_call_deadbeef>` with matching closing IDs, including echoes appended to ordinary prose.
Code examples remain visible. If the input differs or several calls could explain
the block, the markup remains visible. For `mimo-v2` and dotted MiMo V2 model IDs such as
`mimo-v2.6-pro`, if the gateway instead sends exactly one empty `{}` call for a declared
freeform tool and puts its input in a standalone bare block, opencodex restores that
freeform tool and puts its input in a standalone bare or identified block, opencodex restores that
input to the call and hides the block. Hyphenated IDs such as `mimo-v2-pro` and
`mimo-v2-omni` are outside this recovery rule. Prose, quoted
examples, ordinary functions, and ambiguous responses remain unchanged. This also handles
Expand Down
13 changes: 13 additions & 0 deletions docs-site/src/content/docs/guides/sidecars.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,19 @@ failures after response headers have started are delivered as `response.failed`

## Vision sidecar

Mistral's `zai-glm-5-3` is enrolled by the provider preset as text-only. With the
vision sidecar enabled and available, OpenCodex describes attached images and
sends their text descriptions to GLM. The Codex catalog advertises image
attachments for this route. After an update, run `ocx sync`, then fully quit and
relaunch the Codex desktop app or reload the VS Code window. Sync without a
restart flag does not restart an existing Codex app-server, which can retain
the old model list. Alternatively, use `ocx sync --restart-codex` to sync and
restart matching Codex processes and the desktop app. Finish active work first:
this restart ends live conversations. See the [sync and restart reference](/reference/cli/lifecycle/#ocx-sync---restart-codex---restart-app-server-only).
After relaunch, select `mistral/zai-glm-5-3` and verify that image attachments
are available. No model modality override is needed. Mistral documents native input in its
[GLM 5.3 reference](https://docs.mistral.ai/models/zai-glm-5-3).

Image routing is capability-aware. Before an image-bearing upstream send, opencodex resolves the selected model's effective input modalities from runtime provider evidence, explicit operator declarations, backend/registry metadata, and generated vendor metadata. A target positively known to be text-only goes through the Vision Sidecar first; the image is described **before** the main call and replaced inline with text. A target positively known to support images receives the image directly. Unknown custom models keep the existing compatibility behavior rather than being guessed text-only.

For the canonical ChatGPT Codex route, opencodex uses the `openai-codex` metadata bundle rather than public OpenAI API metadata, so backend-specific modality differences are respected. The native Chat fast path uses the same gate and cannot bypass a known text-only verdict. Without an available sidecar plan, raw images are stripped before a proven text-only backend.
Expand Down
27 changes: 25 additions & 2 deletions docs-site/src/content/docs/reference/configuration/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,29 @@ description: Provider entries, authentication, endpoints, model catalogs, quotas
A provider tells opencodex where a model lives, which wire adapter it speaks, and how requests are
authenticated.

## DeepSeek: tokens versus image bytes

DeepSeek documents a separate [48 MiB request-body limit](https://api-docs.deepseek.com/guides/files_api/).
A screenshot-heavy session can reach that limit before its token context fills; increasing
`model_auto_compact_token_limit` cannot prevent it.

Uploads are **off by default**. Set `modelCapabilities: { "deepseek-flash": { deepseekImageFiles: true } }`
on your provider if you accept separately persisted images on DeepSeek's servers for one hour.
PATCH `null` on the axis or model entry clears the opt-in.

For opted-in models on first-party Responses URLs, bodies above 40 MiB replace older inline images
with [Files API references](https://api-docs.deepseek.com/guides/files_api/), aiming for 32 MiB.
Image bytes, history and token usage stay unchanged. Files request one-hour expiry; successful
partial uploads remain until expiry even if generation fails. The bounded reference cache is
credential-isolated and lasts at most 50 minutes (less if the provider reports earlier expiry).
A retry can reuse successful uploads; another credential uploads its own copy.

Foreign gateways and Chat Completions are unchanged. Explicit `detail: low` stays inline because
file references ignore it. Failures stop without discarding images or retrying generation. Repairs
have a 60-second upload deadline, a 128 MiB input cap and at most two simultaneous preparations;
provider pacing/concurrency still applies. Oversized text or unconvertible images still need
compaction; this transport repair does not extend the model's context window.

## Context windows on custom providers

When a custom provider omits a model's context window, OpenCodex can infer it from the bundled
Expand Down Expand Up @@ -360,7 +383,7 @@ Providers can expose a built-in shorthand, such as `agy` for `google-antigravity
| `inlineThinkTagModels?` | `string[]` | Opt-in recovery for `openai-chat` gateways without a server-side reasoning parser. A leading `<think>` / `<thinking>` / `<reasoning>` block (optionally after whitespace) activates splitting in streamed and buffered replies. All answer whitespace is preserved. Subsequent tags are delimiters anywhere, including same-line interleaving and code fences; this mode does not interpret Markdown. Ordinary text or a code fence before the first tag keeps the whole reply untouched. Off by default; prefer structured upstream reasoning or `reasoningSplitModels` where supported. |
| `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. A provider save that keeps the destination keeps the stored list, including `[]`; see [What a provider save keeps](#what-a-provider-save-keeps). `PATCH /api/providers?name=<provider>` accepts an array or `null` to clear it. |
| `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. |
| `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. Eligible CCA, Vertex and direct Gemini requests also opt into `generationConfig.thinkingConfig.includeThoughts` when display is enabled; image, Claude, gpt-oss and unladdered Vertex requests do not. This does not change client configuration or global catalog summary defaults. |
| `hideRawReasoning?` | `boolean` | Keep a routed provider's raw chain of thought off the client-facing content channel (openai-chat `reasoning_content`, kiro tags, `response.reasoning_text.*`) while provider-authored summaries still stream on the summary channel. Explicit wire `reasoning.summary: "none"` keeps hiding both. This controls what a client displays; it does not keep the text from the client. A Responses bridge route still sends it inside the `ocxr1` replay envelope in the reasoning item's `encrypted_content`, which is base64 JSON rather than encryption, so the client can echo it back on the next turn. The direct Chat and Messages encoders have no envelope on their wire, so those clients receive no copy and replay comes from the server-side reasoning replay cache. `preserveReasoningContentModels` replay and DeepSeek-style continuations are unaffected either way. Off by default; a native passthrough route relays the upstream's own frames and ignores this option. A save of the same provider name keeps an omitted value even after a destination move; `PATCH` accepts a boolean or `null` to clear it. |
| `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. |
Expand Down Expand Up @@ -1788,7 +1811,7 @@ contracts.

### 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.
`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`), `video.processing` (`static`, `agentic`) and `deepseekImageFiles` (`boolean`; see [DeepSeek image bytes](#deepseek-tokens-versus-image-bytes)). 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.

Expand Down
9 changes: 9 additions & 0 deletions docs-site/src/content/docs/reference/proxy-formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ response safety still happen at the proxy boundary. Configure the listener and a
[Configuration](/reference/configuration/); use [Combos](/guides/combos/) when one public model id
should select among several targets.

## MiMo bare tool-call recovery

On translated OpenAI Chat routes, `mimo-v2` and dotted `mimo-v2.*` model IDs can recover a
complete bare `<tool_call>` block as a call to an exact declared freeform tool when the response
contains no structured tool calls. Prose around the block is preserved; fenced and inline-code
examples, nested headers, undeclared tools and identified wrappers remain literal. Recovery is
bounded to 128 calls, 4 MiB total and 2 MiB per candidate. Long streamed prose may release the
original markup before terminal recovery; errors never dispatch recovered calls.

## Upstream redirects

Credential-bearing model, image, video, and search requests do not automatically follow HTTP redirects, including same-origin redirects. Configure the final upstream API URL instead of a redirecting alias. A redirect does not cause the server to resend credentials or the request body to its destination. The response owner retains its existing error or relay behavior; native Responses and compact routes can return the original 3xx and `Location` to the client. Client redirect behavior is separate from this server transport policy.
Expand Down
154 changes: 138 additions & 16 deletions scripts/model-metadata.source.json
Original file line number Diff line number Diff line change
Expand Up @@ -61584,6 +61584,45 @@
}
},
"opencode-go": {
"beam-free": {
"id": "beam-free",
"name": "Beam Free",
"api": "openai-completions",
"provider": "opencode-go",
"baseUrl": "https://opencode.ai/zen/go/v1",
"reasoning": true,
"input": [
"text",
"image"
],
"contextWindow": 1000000,
"maxTokens": 131072
},
"claude-haiku-5-5": {
"id": "claude-haiku-5-5",
"name": "Anthropic Haiku 5.5",
"api": "openai-completions",
"provider": "opencode-go",
"baseUrl": "https://opencode.ai/zen/go/v1",
"reasoning": true,
"input": [
"text",
"image"
],
"cost": {
"input": 0.1,
"output": 0.5,
"cacheRead": 0.01,
"cacheWrite": 0.125
},
"contextWindow": 1000000,
"maxTokens": 128000,
"thinking": {
"mode": "effort",
"minLevel": "minimal",
"maxLevel": "high"
}
},
"deepseek-v4-flash": {
"id": "deepseek-v4-flash",
"name": "DeepSeek V4 Flash",
Expand Down Expand Up @@ -61642,6 +61681,48 @@
"requiresReasoningContentForToolCalls": true
}
},
"deepseek-v4.1-flash": {
"id": "deepseek-v4.1-flash",
"name": "DeepSeek V4.1 Flash",
"api": "openai-completions",
"provider": "opencode-go",
"baseUrl": "https://opencode.ai/zen/go/v1",
"reasoning": true,
"input": [
"text"
],
"contextWindow": 1048576,
"maxTokens": 384000,
"thinking": {
"mode": "effort",
"minLevel": "minimal",
"maxLevel": "xhigh"
},
"compat": {
"supportsToolChoice": false,
"reasoningContentField": "reasoning_content",
"requiresReasoningContentForToolCalls": true
}
},
"gemini-4-argon-contributor": {
"id": "gemini-4-argon-contributor",
"name": "Gemini 4 Argon Contributor",
"api": "openai-responses",
"provider": "opencode-go",
"baseUrl": "https://opencode.ai/zen/go/v1",
"reasoning": true,
"input": [
"text",
"image"
],
"contextWindow": 1048576,
"maxTokens": 65536,
"thinking": {
"mode": "effort",
"minLevel": "low",
"maxLevel": "high"
}
},
"glm-5": {
"id": "glm-5",
"name": "GLM-5",
Expand Down Expand Up @@ -61766,6 +61847,25 @@
"contextWindow": 1050000,
"maxTokens": 128000
},
"gpt-6-luna": {
"id": "gpt-6-luna",
"name": "GPT-6 Luna",
"api": "openai-responses",
"provider": "opencode-go",
"baseUrl": "https://opencode.ai/zen/go/v1",
"reasoning": true,
"input": [
"text",
"image"
],
"contextWindow": 1050000,
"maxTokens": 128000,
"thinking": {
"mode": "effort",
"minLevel": "low",
"maxLevel": "max"
}
},
"grok-4.5": {
"id": "grok-4.5",
"name": "Grok 4.5",
Expand Down Expand Up @@ -62015,6 +62115,20 @@
"contextWindow": 1000000,
"maxTokens": 131072
},
"longcat-2.5-preview-free": {
"id": "longcat-2.5-preview-free",
"name": "LongCat 2.5 Preview Free",
"api": "openai-completions",
"provider": "opencode-go",
"baseUrl": "https://opencode.ai/zen/go/v1",
"reasoning": true,
"input": [
"text",
"image"
],
"contextWindow": 1000000,
"maxTokens": 131072
},
"mimo-v2-omni": {
"id": "mimo-v2-omni",
"name": "MiMo-V2-Omni",
Expand Down Expand Up @@ -62417,9 +62531,9 @@
"contextWindow": 1000000,
"maxTokens": 131072
},
"union-alpha": {
"id": "union-alpha",
"name": "Union Alpha Free",
"space-bunny": {
"id": "space-bunny",
"name": "Space Bunny",
"api": "openai-completions",
"provider": "opencode-go",
"baseUrl": "https://opencode.ai/zen/go/v1",
Expand All @@ -62428,12 +62542,12 @@
"text",
"image"
],
"contextWindow": 262144,
"contextWindow": 1000000,
"maxTokens": 131072
},
"claude-haiku-5-5": {
"id": "claude-haiku-5-5",
"name": "Anthropic Haiku 5.5",
"step-5-preview": {
"id": "step-5-preview",
"name": "Step 5 Preview",
"api": "openai-completions",
"provider": "opencode-go",
"baseUrl": "https://opencode.ai/zen/go/v1",
Expand All @@ -62442,19 +62556,27 @@
"text",
"image"
],
"cost": {
"input": 0.1,
"output": 0.5,
"cacheRead": 0.01,
"cacheWrite": 0.125
},
"contextWindow": 1000000,
"maxTokens": 128000,
"contextWindow": 256000,
"maxTokens": 64000,
"thinking": {
"mode": "effort",
"minLevel": "minimal",
"maxLevel": "high"
"maxLevel": "xhigh"
}
},
"union-alpha": {
"id": "union-alpha",
"name": "Union Alpha Free",
"api": "openai-completions",
"provider": "opencode-go",
"baseUrl": "https://opencode.ai/zen/go/v1",
"reasoning": true,
"input": [
"text",
"image"
],
"contextWindow": 262144,
"maxTokens": 131072
}
},
"opencode-zen": {
Expand Down
Loading
Loading