Skip to content

MiniMax M3.1 Flash Preview offers no thinking depth variants despite a catalog-declared effort ladder #54801

Open

Description

@Gobutah

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

  1. Configure a provider that exposes MiniMax-M3.1-Flash-Preview (for example minimax-coding-plan).
  2. Select the model.
  3. Press the variant_cycle keybind (default ctrl+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 at packages/core/src/models-dev.ts:16, regenerated by packages/core/script/update-models-snapshot.ts.
  • Live — https://models.opencode.ai/api.json, fetched at runtime by packages/core/src/models-dev.ts:345 and 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:

  1. The catalog entry declares the reasoning_options shown above.
  2. packages/core/src/models-dev.ts:196 (supports()) maps it into Variant.Support[].
  3. packages/core/src/models-dev.ts:115 calls Variant.resolve(...).
  4. packages/ai routes the provider to @opencode/ai/providers/minimax/messages, mapped via packages/core/src/aisdk-native.ts:113.
  5. packages/core/src/variant.ts:337 returns [].

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.effort with 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"} and effort: "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 effort defaults to max.

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

  1. opencode-agent commented on Oct 12, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I reproduced this on v2.0.27: with a dummy MINIMAX_API_KEY, opencode api --standalone model.list shows MiniMax-M3.1-Flash-Preview with variants: [] on both minimax-coding-plan and minimax-cn-coding-plan. MiniMax-M3 shows none/thinking on 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 the minimaxMessages code in packages/core/src/variant.ts hasn't changed since 2.0.27. The script below reproduces it with the released CLI.

    Until this is addressed, declaring the variants manually in opencode.json, as you did, is the workaround.

    Reproduction script (repro.sh)
    #!/usr/bin/env bash
    # Reproduces #54801: MiniMax-M3.1-Flash-Preview resolves zero variants.
    # Usage: ./repro.sh            (uses `opencode` from PATH)
    #        OC_DEV=/path/to/opencode-checkout ./repro.sh   (runs from source)
    set -euo pipefail
    proj=$(mktemp -d)
    export HOME=$(mktemp -d)
    cd "$proj"
    if [ -n "${OC_DEV:-}" ]; then
      run() { bun --conditions=browser --preload="$OC_DEV/packages/tui/node_modules/@opentui/solid/scripts/preload.js" "$OC_DEV/packages/cli/src/index.ts" "$@"; }
    else
      run() { opencode "$@"; }
    fi
    run --version
    MINIMAX_API_KEY=fake run api --standalone model.list \
      | jq -c '.data[] | select(.id|test("^MiniMax-M3")) | {provider: .providerID, id, variants: [.variants[].id]}'

    Run it: bash repro.sh

  2. drakeo338 commented on Oct 12, 2026

    @drakeo338

    I'd like to take this one. I can reproduce it and plan to open a fix shortly.

  3. argszero commented on Oct 12, 2026

    @argszero
    Contributor

    I'd like to work on this.

    I confirmed the analysis on the current dev tip (bcf23b6) and traced where the guard came from. Both gaps are real:

    • packages/core/src/models-dev.ts:196 (supports()) turns the catalog's reasoning_options into [{ type: "effort", values: [low, medium, high, xhigh, max] }]. Nothing adds a toggle support for this model.
    • Variant.resolve (packages/core/src/variant.ts:19) therefore resolves toggle = [] and calls minimaxMessages with the ladder, where configurable is false (support.type === "effort" but support.values !== undefined), so the function returns [] at line 339.

    git log -S'support.values === undefined' -- packages/core/src/variant.ts points at 2226afaea5 (refactor(core): refactor model resolving logic, fix missing variant logic, #48943) — the guard was introduced together with minimaxMessages itself, 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 minimaxMessages an effort branch that maps each declared value to the output_config.effort overlay, leave the existing toggle branch intact for MiniMax-M3, and add a case in packages/core/test/variant.test.ts next to the existing MiniMax-M3 toggle assertion. Two details I want to get right rather than guess:

    • where the overlay belongs for this route — settings.output_config, as the other protocols in this file use, or body.output_config as in your example;
    • no none/disabled variant: since the catalog declares no toggle support 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.
  4. argszero commented on Oct 12, 2026

    @argszero
    Contributor

    Withdrawing my claim: @drakeo338's comment landed 21 ms before mine (6125034816 vs 6125034837), 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:196 produces only [{ type: "effort", values: [...] }] for this model, so Variant.resolve (packages/core/src/variant.ts:19) has no toggle branch and hands the ladder straight to minimaxMessages, where support.values === undefined is false and the function returns [].
    • Because Variant.resolve prepends the toggle none variant when a protocol has one, the ladder branch has to take precedence over the toggle branch rather than merge with it — the API rejects thinking.type="disabled" on this model, so a none variant must not be emitted.
  5. drakeo338 commented on Oct 12, 2026

    @drakeo338

    Releasing this; someone else has picked it up, so I'm stepping back.

  6. drakeo338 commented on Oct 12, 2026

    @drakeo338

    @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.

  7. isHeSatoshi commented on Nov 6, 2026

    @isHeSatoshi

    Adding the shipping-model case here so the thread has it: the same branch also drops the ladder for MiniMax-M3.1-Flash on the minimax (minimax.io API) and minimax-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 effort option 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions