Repository navigation
Description
Description
Summary
The models.dev catalog declares a five-level reasoning-effort ladder for MiniMax-M3.1-Flash-Preview, but OpenCode resolves zero variants for it, so no thinking depth is selectable in the TUI. Users have to hand-write the entire variants array in opencode.json to get depth control that the catalog says should exist by default.
Per MiniMax's own docs the model supports five depths, so this is an OpenCode gap rather than a catalog error.
Environment
- opencode version: 2.0.27
- opencode commit: bcf23b6 (branch
dev) - OS: Linux (x86_64)
- Terminal: TERM=xterm-256color, COLORTERM=truecolor, TERM_PROGRAM unset
- Shell: fish
- Install/channel: release (~/.opencode/bin/opencode)
- Active plugins: none found in config
All source references below are line numbers at the commit above.
Reproduction
- Configure a provider that exposes
MiniMax-M3.1-Flash-Preview(for exampleminimax-coding-plan). - Select the model.
- Press the
variant_cyclekeybind (defaultctrl+t) to cycle thinking depth.
Expected Behavior
The catalog entry declares:
"reasoning_options": [{ "type": "effort", "values": ["low","medium","high","xhigh","max"] }]so cycling should step through low / medium / high / xhigh / max without any configuration.
Actual Behavior
There are no variants at all, so ctrl+t does nothing:
$ opencode api model.list | jq '.data[] | select(.id=="MiniMax-M3.1-Flash-Preview") | .variants'
[]The only way to get depth control is to declare the whole ladder manually:
{
"providers": {
"minimax-coding-plan": {
"models": {
"MiniMax-M3.1-Flash-Preview": {
"variants": [
{ "id": "low", "body": { "output_config": { "effort": "low" } } },
{ "id": "medium", "body": { "output_config": { "effort": "medium" } } },
{ "id": "high", "body": { "output_config": { "effort": "high" } } },
{ "id": "xhigh", "body": { "output_config": { "effort": "xhigh" } } },
{ "id": "max", "body": { "output_config": { "effort": "max" } } }
]
}
}
}
}
}Additional Context
Where the ladder comes from
This is catalog data, not source in this repo. Two copies, which I verified agree:
- Bundled —
packages/core/src/models-dev/snapshot.txt, a 5.3 MB minified JSON blob committed at this commit. Imported atpackages/core/src/models-dev.ts:16, regenerated bypackages/core/script/update-models-snapshot.ts. - Live — https://models.opencode.ai/api.json, fetched at runtime by
packages/core/src/models-dev.ts:345and cached in KV for 5 minutes, with the bundled snapshot as the offline floor.
Upstream source of truth: https://github.com/anomalyco/models.dev (see CONTRIBUTING.md:21 — "New providers should rarely require OpenCode changes. Add the provider to models.dev first.").
Exactly two entries declare this ladder, both the same model on the two coding-plan hosts:
| Provider | Model ID | reasoning_options |
|---|---|---|
minimax-coding-plan |
MiniMax-M3.1-Flash-Preview |
[{"type":"effort","values":["low","medium","high","xhigh","max"]}] |
minimax-cn-coding-plan |
MiniMax-M3.1-Flash-Preview |
[{"type":"effort","values":["low","medium","high","xhigh","max"]}] |
Both carry npm: "@ai-sdk/anthropic" at the provider level, which is what routes them to minimax/messages.
Why the variants list is empty
The declared ladder survives every stage of the pipeline and is discarded in the last one:
- The catalog entry declares the
reasoning_optionsshown above. packages/core/src/models-dev.ts:196(supports()) maps it intoVariant.Support[].packages/core/src/models-dev.ts:115callsVariant.resolve(...).packages/airoutes the provider to@opencode/ai/providers/minimax/messages, mapped viapackages/core/src/aisdk-native.ts:113.packages/core/src/variant.ts:337returns[].
Two independent gaps in that last step:
No ladder path exists. minimaxMessages has exactly one return that produces variants, and it is a binary toggle:
// packages/core/src/variant.ts:337
const minimaxMessages: Protocol = (model, support) => {
if (/minimax[-.]?m2(?:[.-]|$)/i.test(modelID(model))) return []
const configurable = support.type === "toggle" || (support.type === "effort" && support.values === undefined)
if (!configurable) return []
return toggle({ settings: { thinking: { type: "disabled" } } }, { settings: { thinking: { type: "adaptive" } } })
}toggle(...) yields [none, thinking]. There is no efforts(...) call in this function, so a multi-level ladder is unreachable.
The guard suppresses the working toggle too. support.values === undefined means an explicitly specified ladder is discarded before line 341 is reached, so M3.1 loses even the none/thinking pair that M3 gets. The guard appears intended as a fallback for catalogs that mark a model reasoning-capable without enumerating depths, but it suppresses the specified case rather than the unspecified one.
Contrast with other MiniMax M3 entries
Every other MiniMax M3 entry in the catalog declares something other than a ladder:
| Provider | Model | reasoning_options |
|---|---|---|
minimax |
MiniMax-M3 |
[{"type":"toggle"}] |
minimax-cn |
MiniMax-M3 |
[{"type":"toggle"}] |
opencode (Zen) |
minimax-m3 |
[] |
opencode-go |
minimax-m3 |
[{"type":"toggle"}] |
MiniMax-M3 takes the toggle branch and resolves correctly:
[{"id":"none","settings":{"thinking":{"type":"disabled"}}},
{"id":"thinking","settings":{"thinking":{"type":"adaptive"}}}]So the difference is not the model family, it is whether the catalog enumerated depth values.
Only the Messages protocol drops ladders
| Package | Protocol | Effort handling |
|---|---|---|
minimax/messages |
minimaxMessages (packages/core/src/variant.ts:337) |
toggle only, ladder dropped |
minimax/chat |
openaiChat (packages/core/src/variant.ts:77) |
maps declared values to reasoningEffort |
minimax/responses |
openaiResponses (packages/core/src/variant.ts:93) |
maps declared values to reasoningEffort |
For scale, ~4000 entries in the current catalog declare an explicit effort ladder, so the mechanism is routine — variant.ts handles it for other protocols, just not this one.
Note that only the two coding-plan hosts declare a ladder at all, so it is not possible to tell from the repo alone whether minimaxMessages was written for the toggle-only models and never revisited, or whether coding-plan was deliberately excluded. The verification below suggests the former.
Verification
Per the MiniMax docs (https://platform.minimax.io/docs/guides/text-generation#tunable-thinking-depth-effort), MiniMax-M3.1-Flash-Preview accepts low, medium, high, xhigh, and max for thinking depth, defaulting to max when omitted. On the Anthropic-compatible endpoint the field is output_config.effort.
The catalog's five values match the documented set exactly, in the same order, and correctly omit none.
I confirmed the API behavior against the live endpoint:
output_config.effortwith each declared level returns HTTP 200 and a normal completion.- An invalid level is rejected:
400 invalid reasoning_effort: "bananas" (allowed: low, medium, high, xhigh, max)
- Thinking cannot be disabled.
thinking: {"type":"disabled"}andeffort: "none"both return:
400 model "MiniMax-M3.1-Flash-Preview" requires adaptive thinking; thinking.type="disabled" (including reasoning.effort=none) is not allowed
Thinking is on by default and none must not be offered as a variant.
- Omitting
effortdefaults tomax.
So the ladder is exactly the five declared levels, with no off state.
Suggested fix
Add an efforts(...) branch to minimaxMessages mirroring anthropicMessages (packages/core/src/variant.ts:314), mapping each declared effort to { body: { output_config: { effort } } } — and no none variant, since thinking cannot be disabled on this model.
There is existing precedent for output_config.effort in this file: bedrockConverse already emits { output_config: { effort } } at packages/core/src/variant.ts:429.
One subtlety worth calling out: Variant.resolve at packages/core/src/variant.ts:19 prepends the toggle branch's none variant whenever a protocol has one. For M3.1 that would emit a none variant the API rejects, so the ladder branch must take precedence over the toggle branch rather than being merged with it.
Separately, the guard on line 339 should not suppress the working toggle when a ladder is declared.
For reference, packages/ai/src/providers/minimax.ts:17 documents the Messages thinking type as only { type: "adaptive" | "disabled" }, which may need widening for an output_config overlay. packages/ai/src/providers/minimax.ts:30 notes the Responses route's other values "enable thinking without changing its depth" — that does not apply here, since the Anthropic-compatible route does vary with effort.
Plugins
No response
OpenCode version
No response
Steps to reproduce
No response
Screenshot and/or share link
No response
Operating System
No response
Terminal
No response
Activity
opencode-agent commentedon Oct 12, 2026
Thanks for the detailed report. I reproduced this on v2.0.27: with a dummy
MINIMAX_API_KEY,opencode api --standalone model.listshowsMiniMax-M3.1-Flash-Previewwithvariants: []on bothminimax-coding-planandminimax-cn-coding-plan.MiniMax-M3showsnone/thinkingon the same providers.On the latest
dev(bcf23b6) I couldn't start the standalone server from source, so I called the variant resolution directly instead. It still returns no variants for the five-level effort ladder, and theminimaxMessagescode inpackages/core/src/variant.tshasn't changed since 2.0.27. The script below reproduces it with the released CLI.Until this is addressed, declaring the
variantsmanually inopencode.json, as you did, is the workaround.Reproduction script (repro.sh)
Run it:
bash repro.shdrakeo338 commentedon Oct 12, 2026
I'd like to take this one. I can reproduce it and plan to open a fix shortly.
argszero commentedon Oct 12, 2026
I'd like to work on this.
I confirmed the analysis on the current
devtip (bcf23b6) and traced where the guard came from. Both gaps are real:packages/core/src/models-dev.ts:196(supports()) turns the catalog'sreasoning_optionsinto[{ type: "effort", values: [low, medium, high, xhigh, max] }]. Nothing adds atogglesupport for this model.Variant.resolve(packages/core/src/variant.ts:19) therefore resolvestoggle = []and callsminimaxMessageswith the ladder, whereconfigurableis false (support.type === "effort"butsupport.values !== undefined), so the function returns[]at line 339.git log -S'support.values === undefined' -- packages/core/src/variant.tspoints at2226afaea5(refactor(core): refactor model resolving logic, fix missing variant logic, #48943) — the guard was introduced together withminimaxMessagesitself, so it reads as "the Messages route only has a thinking toggle" rather than a decision to reject declared ladders. Every sibling protocol (openaiChat,openaiResponses,anthropicMessages,bedrockConverse) maps declared effort values, so this one is the odd one out rather than the exception.Plan: give
minimaxMessagesaneffortbranch that maps each declared value to theoutput_config.effortoverlay, leave the existing toggle branch intact forMiniMax-M3, and add a case inpackages/core/test/variant.test.tsnext to the existingMiniMax-M3toggle assertion. Two details I want to get right rather than guess:settings.output_config, as the other protocols in this file use, orbody.output_configas in your example;none/disabled variant: since the catalog declares notogglesupport for M3.1 the toggle branch is empty and nothing is prepended today, and I'll keep it that way instead of adding a variant the API rejects.argszero commentedon Oct 12, 2026
Withdrawing my claim: @drakeo338's comment landed 21 ms before mine (
6125034816vs6125034837), so this issue is theirs. I had written no code for it and have no branch to clean up.For whoever implements it, the two facts from my trace that matter most:
packages/core/src/models-dev.ts:196produces only[{ type: "effort", values: [...] }]for this model, soVariant.resolve(packages/core/src/variant.ts:19) has no toggle branch and hands the ladder straight tominimaxMessages, wheresupport.values === undefinedis false and the function returns[].Variant.resolveprepends the togglenonevariant when a protocol has one, the ladder branch has to take precedence over the toggle branch rather than merge with it — the API rejectsthinking.type="disabled"on this model, so anonevariant must not be emitted.drakeo338 commentedon Oct 12, 2026
Releasing this; someone else has picked it up, so I'm stepping back.
drakeo338 commentedon Oct 12, 2026
@argszero our messages crossed: I released this a few minutes after your withdrawal and have no branch for it. It is yours if you want it; your trace is the right fix.
isHeSatoshi commentedon Nov 6, 2026
Adding the shipping-model case here so the thread has it: the same branch also drops the ladder for
MiniMax-M3.1-Flashon theminimax(minimax.io API) andminimax-coding-plan(Token Plan) providers — all four MiniMax hosts serve it through the Messages route.That is filed as #62264 with a PR that maps the ladder through the protocol's
effortoption instead of a body overlay, and that covers both providers end to end (request body assertion): #62265.@rudycelekli's #56510 solves the same branch; the two are equivalent on the wire. Closing either in favour of the other is fine.