Skip to content

[Feature]: CLI - ACP: expose the session context-window selection (parity with the /model picker) #384

Open

Description

@minmax

Product or interface

CLI - ACP


中文摘要:session/set_config_option 目前只支持 permissionMode / model / thinkingEffort 三项,ACP 客户端无法设置上下文窗口。模型目录里已经带有 contextWindowOptions: [512000, 1000000] 和 contextWindowOptionHints,TUI 的 /model 选择器也在用(Context 512K 1M (Tab to switch)),但这条能力没有暴露到 ACP 和 headless exec。建议补上第四个 config option,并把当前生效的窗口大小结构化暴露出来 —— 与 TUI 对齐,属于补齐接口,不是新增能力。

Use case and problem

I build an ACP client, and per the guidance in #176 I take model values from the session's advertised configOptions and treat them as opaque.

The catalog already carries a per-model context-window choice:

{ "providerId": "minimax", "modelId": "MiniMax-M3.1-Flash-Preview",
  "contextLimit": 512000,
  "contextWindowOptions": [512000, 1000000],
  "contextWindowOptionHints": { "1000000": "higher_usage" } }

and the TUI already consumes it — packages/tui/src/tui/features/model/picker.ts:295 renders Context [ 512K ] [ 1M ] (Tab to switch) plus the Higher usage with this context window. warning, and packages/config/src/local-model-provider-write.ts:205 persists the choice.

ACP has no way to reach it. ACP_CONFIG_* defines exactly three options — permissionMode, model, thinkingEffort (packages/tui/src/acp/control-state.ts:14-16) — and the set_config_option handler falls through to Unsupported Session configuration option for anything else (packages/tui/src/acp/agent.ts:1044). Observed on mcode 0.5.8, after initialize + session/new:

configId Result
contextWindow Invalid params: Unsupported Session configuration option: contextWindow
contextLimit same
context same
contextWindowSize same

Headless exec has no equivalent either: --model accepts provider/model or provider/model#variant only (packages/tui/src/headless/model-selection.ts:34), and there is no --context flag in mcode exec --help. The model value encoding m:<provider>:<model>:u|v:<variant> (packages/tui/src/acp/control-state.ts:99) has no slot for it either.

So an ACP client that needs the 1M window has two options today, both bad: it silently runs at 512K, or it writes the user's global ~/.minimax/config.yaml (defaultModelContextWindow / minimaxModelContextLimits), which also changes the interactive TUI and every other session on the machine. mcode exec --config <path> exists for a per-process config, but mcode acp has no such flag.

Reading is barely better. The effective window is not in any structured ACP field. The only way to get it is to send /context as a session/prompt, which on a fresh session answers No Runtime context snapshot is available for this session yet. and otherwise costs a prompt round-trip (packages/tui/src/acp/commands.ts:224).

Current chain

The session-scoped selection the ACP handler already calls does carry a context limit — the ACP layer just never populates it:

  • packages/tui/src/acp/agent.ts:995 — options.runtime.selectSessionModel(selection, sessionId)
  • packages/tui/src/runtime/adapters/product-access.ts:123 — forwards to service("model.select").selectModel({ ...modelRequest(model), sessionId })
  • packages/tui/src/runtime/adapters/product-access.ts:432 — modelRequest() already maps contextLimit?: number onto the runtime request

The value is dropped at the edge, in parseModelConfigValue (packages/tui/src/acp/control-state.ts:75), which parses only providerId / modelId / variant. So this is a CLI-surface gap only.

Desired behavior

A fourth select config option next to thinkingEffort, so ACP clients get the same choice the TUI picker already offers.

Acceptance criteria

  1. session/set_config_option accepts the new configId, returns the updated configOptions, and emits a config_option_update notification — consistent with the three existing options.
  2. Advertised values are opaque and come from the selected model's contextWindowOptions; a value that is not advertised is rejected the same way an unadvertised model selection is. Clients must not construct the value (same rule as model).
  3. The higher_usage hint from contextWindowOptionHints is carried into the option description, so a client can warn the user before switching to 1M.
  4. The selection is session-scoped and does not write the user's global config.yaml — the same criterion PR feat: support session-scoped interactive model overrides #227 held the interactive --model flag to.
  5. Nice to have: expose the effective context window in a structured field (e.g. on session/new / session/load, or next to the existing usage_update size), so a client can read it without spending a prompt on /context.

Interaction example

session/new
  → configOptions: permissionMode, model, thinkingEffort, contextWindow
                   current=m:minimax:MiniMax-M3.1-Flash-Preview:v:thinking

session/set_config_option { configId: "contextWindow", value: <opaque 1M value> }
  → { configOptions: [... contextWindow ... current=<opaque 1M value>] }
  ← notification session/update { sessionUpdate: "config_option_update", ... }

Platform

macOS (macOS 15 arm64, mcode 0.5.8). The gap is protocol-level, so it should apply to every platform.

Alternatives and additional context

Verification: mcode 0.5.8, source at c593d3d, all observations from live mcode acp handshakes over stdio. No private project content included.

Activity

  1. hetaoBackend commented on Sep 28, 2026

    @hetaoBackend
    Collaborator

    This was generated by AI during triage.

    This is an excellent, precisely-traced report — and I verified the whole chain line-for-line. Confirmed: this is a CLI-surface gap where the runtime plumbing already exists and only the ACP/headless edge drops the value. The same producer/consumer asymmetry pattern as #288.

    Verification (against current main)

    • ACP really does define exactly three config options. packages/tui/src/acp/control-state.ts:14-16 declares permissionMode, model, thinkingEffort — no context-window option. So any other configId falls through to Unsupported Session configuration option, matching your observed error.
    • The value is dropped exactly where you say. parseModelConfigValue (control-state.ts:78-98) parses only the m:<provider>:<model>:<u|v>:<variant> shape and returns only { providerId, modelId, variant? } — there is no contextLimit slot, so it cannot survive the ACP edge.
    • The runtime already carries it. modelRequest() in packages/tui/src/runtime/adapters/product-access.ts:432-435 already maps model.contextLimit onto the runtime request (...(model.contextLimit !== undefined ? { contextLimit: model.contextLimit } : {})). And the TUI picker already consumes the catalog's contextWindowOptions / contextWindowOptionHints, with the choice persisted in local-model-provider-write.ts. So the TUI path is complete; only ACP/headless is missing the hop.

    Your conclusion — "this is a CLI-surface gap only" — is correct and well-earned.

    Classification

    • Category: enhancement (interface parity for an existing capability; additive, no change to existing behavior)
    • State: fully specified, verified, and the runtime side already exists. This is a well-scoped ACP/headless change (add a fourth config option, carry it through parseModelConfigValue → modelRequest's existing contextLimit, keep it session-scoped). Parked in needs-triage for prioritization. (This tracker has no ready-for-agent label, so needs-triage + this note is the routing.)

    Agreement on the acceptance criteria

    All five of your criteria are the right ones. Two I'd underline:

    • (2) Opaque values — clients must not construct the value; it must come from the selected model's contextWindowOptions, and an unadvertised value is rejected like an unadvertised model. This keeps the protocol client-agnostic and is consistent with how model already works.
    • (4) Session-scoped, no global write — exactly right, and matches the constraint PR feat: support session-scoped interactive model overrides #227 held the interactive --model flag to. Falling back to "write the user's ~/.minimax/config.yaml" is precisely the footgun you're avoiding, so preserving session scoping is important.

    Your point that a fresh ACP session's only way to read the effective window is to spend a prompt on /context (which then says "No Runtime context snapshot…") also stands — the structured read exposure (criterion 5) is a reasonable nice-to-have but genuinely useful for clients.

    The mcode acp --config <path> fallback (if a new config option isn't wanted) is a sensible minimum, and pairs naturally with the full option.

    No credentials or private project content echoed; the observations are protocol-level and already public in the report.

  2. prawesh-12 commented on Sep 28, 2026

    @prawesh-12

    Hello @hetaoBackend , here is my proposal for this feature. The issue and your triage already explain the problem. I only cover the extra design points and my test plan. Please let me know what you think.

    Design points

    Category. The ACP spec has a standard category called model_config. The spec says it is for settings like context size. I want to use it for this option. The Claude ACP adapter uses the same category for its fast mode setting. Our SDK version (1.3.0) already supports it.

    Position. Clients show the options in the order mcode sends them. When a client has little space, it drops the last ones first. So I want to put the context window option last, after thinking effort.

    Warning. In SDK 1.3.0 each choice in the list can have its own description. I want to put the text "Higher usage with this context window." only on the sizes that the model marks as higher_usage. The 512K choice will have no warning.

    When the option is hidden. If the model has only one size, the option is not shown. It is also not shown if the current size of the session is not in the model's list. mcode already does the same thing for thinking effort.

    A new option, not a change to the model value. The triage suggests passing the size through parseModelConfigValue. I think a separate option is better. The model value format stays the same. Clients that already saved a model value will not break. The user can also change the size without changing the model.

    Value. The value is the number of tokens as a string, for example "1000000". Clients should only send values from the list that mcode gives them. mcode will refuse any other value.

    Here is how the new option would look:

    {
      "type": "select",
      "id": "contextWindow",
      "name": "Context window",
      "description": "Sets the context window for the selected model in this Session.",
      "category": "model_config",
      "currentValue": "512000",
      "options": [
        { "value": "512000", "name": "512K" },
        { "value": "1000000", "name": "1M", "description": "Higher usage with this context window." }
      ]
    }

    What the code already does

    I read the code on main. Most of the work is already there.

    • listModels(sessionId) already returns the current size of the session. It is set in projectStoredParameters() in model-provider-application.ts. So the option can show the right size as soon as a session starts or loads. I think this also covers most of point 5 in the issue.
    • validateContextLimit() in the same file already refuses sizes that the model does not support. So there is a second check after the ACP one.
    • When the user switches to another model, sessionLimitUpdate() sets the size back to that model's default. When the user only changes thinking effort, the size stays the same.
    • mcode rebuilds the option list after every change. So the sizes in the list always belong to the current model.
    • formatContextWindow() in application/context-window.ts already makes the "512K" and "1M" labels. The /model picker uses it too. So ACP and the terminal app will show the same labels.

    No change is needed in local-runtime-v2.

    Code changes

    In packages/tui/src/acp/control-state.ts:

    • Add a constant for the new option id.
    • Add a function that builds the option. It will work the same way as thinkingEffortOption().
    • Add a small function that reads the value. It only accepts a whole number above zero.
    • Add the new option at the end of configOptions().

    In packages/tui/src/acp/agent.ts:

    • Add a case for contextWindow in the setConfigOption handler. It runs inside runSessionConfigMutation(), like the model and thinking effort cases.
    • The new case checks that the session has a model. It then checks that the value is in the model's list of sizes. After that it calls selectSessionModel() with the same provider, model and variant, plus the new size.
    • It does not send thinking. So the thinking effort does not change.

    I will not move or add any files.

    Tests

    I will add tests to packages/tui/test/unit/acp-agent.test.ts. This file is already part of the capability test group.

    • The option comes last. It shows the right current size and both choices. The 1M choice has the warning.
    • A model with only one size has no option.
    • Choosing 1M sends contextLimit: 1000000 without thinking. The reply and the config_option_update message both show 1M.
    • mcode refuses a size that is not in the list. It also refuses a value that is not a number. It also refuses the change if the session has no model.
    • If the runtime refuses the change, the option list stays the same.
    • After a switch to a model with one size, the option goes away.

    I will also test it by hand with mcode acp and in Zed. Before I share anything, I will run pnpm verify. @minmax , you build an ACP client. If you have time, it would help a lot if you could try my branch with it.

    Not in this change

    • A context window flag for mcode exec. It uses different code, so I would keep it for a separate change.
    • The mcode acp --config <path> idea from the issue. With the new option, it is not needed for this case.

    I can work on either of these later if you want them.

    Questions

    1. Is model_config fine as the category? Or do you prefer model?
    2. Is the token count fine as the value? Or do you prefer a form like ctx:1000000?
    3. For a model with one size, should the option be hidden? That is my plan. The other choice is to show it with one fixed value.

    I am not a collaborator, so I cannot open a pull request. I will do the work on the feat/acp-context-window branch in my fork. When it passes the checks, I will post the commit link here.

    References:

  3. hetaoBackend commented on Sep 28, 2026

    @hetaoBackend
    Collaborator

    This was generated by AI during triage.

    @prawesh-12 — thanks for the detailed proposal. I checked your factual claims against main and against the ACP spec: everything you cite is accurate, and your central design correction is better than what I suggested in the triage note. Adopting it.

    Your claims, verified

    • validateContextLimit() exists and is a real second check — packages/local-runtime-v2/src/application/session/model-provider-application.ts:434-455. It already rejects any size not in the model's contextWindowOptions (and enforces safe-integer, > 0, and an upper bound) with context_limit must be supported by the selected model. So the ACP layer doesn't need to be the only guard.
    • projectStoredParameters() and sessionLimitUpdate() are in the same file, and contextLimit is already threaded through selection, including effectiveModelContextWindow (:308). Confirmed: no change needed in local-runtime-v2, exactly as you said.
    • formatContextWindow() already produces the 512K / 1M labels in packages/tui/src/application/context-window.ts, and the /model picker consumes it. Reusing it means ACP and the terminal UI show identical labels.
    • thinkingEffortOption() (control-state.ts:186-214) is the right template, and notably it already does the two hiding rules you propose — it returns undefined when the option list is empty, and also when the persisted value isn't in the list. Your "hide for a single-size model / hide when the current size isn't in the list" matches the existing pattern rather than inventing one.

    Your design correction is the right call — I'm withdrawing my earlier suggestion

    I suggested carrying the size through parseModelConfigValue. That was the worse option, for exactly the reason you give: it would change the m:<provider>:<model>:<u|v>:<variant> value format, breaking clients that already persisted a model value, and it would couple "change the size" to "change the model". A separate config option is correct on both counts.

    Answers to your three questions

    1. Category: model_config — yes, go with it. The ACP spec defines model_config for exactly this kind of setting and says clients SHOULD render model_config options near the model selector, with "no capability negotiation … required for category values". It also confirms your ordering reasoning: array order is significant, and clients that must drop options should prefer the beginning of the list — so putting contextWindow last (after thinkingEffort) is right. It also documents that each option entry can carry its own description, which supports putting the warning on the 1M choice only.
    2. Value: plain token count as a string ("1000000") — fine. The spec requires a select value to be one of the strings in the advertised options array, and we reject anything else, so there is no ambiguity for clients. No need for a ctx:-style prefix.
    3. Hiding for a single-size model: hide it — agreed. It's consistent with thinkingEffortOption(), and the spec's "Agents MUST always provide a default value" is about defaults for options you do advertise, not a requirement to advertise a meaningless single-choice selector. Keeping it hidden also avoids a UI affordance that does nothing.

    One addition worth folding in

    The spec notes the set_config_option response "always contains the complete configuration state" precisely so dependent options can be reflected. Your plan already rebuilds the list after every change — please make sure the rebuild also runs when the model changes (so the offered sizes follow the newly selected model), and that it is covered by the "after a switch to a model with one size, the option goes away" test you listed. That dependent-recompute case is the one most likely to be missed.

    Process

    As you note, you're not a collaborator and can't open a PR here. That's fine — the same path as #149 and #364: do the work on a branch in your fork, and post the commit link on this issue when it passes checks. A maintainer can then bring it in with attribution. Please run pnpm verify before posting, and include the ACP handshake evidence (mcode acp and ideally Zed) as you planned.

    Also, @minmax — @prawesh-12 is asking you to try their branch with your ACP client, which would be a useful independent check before adoption.

    Keeping the issue in needs-triage pending the commit. Good contribution — the spec grounding and the explicit "no change needed in local-runtime-v2" scope statement made this easy to review.

    No credentials or sensitive data echoed.

  4. prawesh-12 commented on Sep 29, 2026

    @prawesh-12

    @hetaoBackend the implementation is ready on my fork.

    Commit: prawesh-12@f067774
    Diff against main: main...prawesh-12:minimax-code:feat/acp-context-window
    Branch: https://github.com/prawesh-12/minimax-code/tree/feat/acp-context-window

    The commit is based on main at 4fdf523 and changes 4 files.

    What changed

    ACP sessions now have a fourth setting called contextWindow. It follows the plan you approved.

    • It uses the model_config category and comes last, after thinking effort.
    • The choices come from the selected model's list of sizes. Each size shows a label like "512K" or "1M".
    • Sizes that the model marks as higher_usage show the same warning as the /model picker.
    • The option is hidden when the model has only one size. It is also hidden when the current size is not in the model's list.
    • Changing the size only changes the size. The model and the thinking effort stay the same.
    • After a switch to another model, the list is built again. If the new model has only one size, the option goes away.
    • The change only applies to the current Session. Nothing is written to config.yaml. A test checks that the global default model is never changed.
    • The model value format did not change.

    Only two source files changed: packages/tui/src/acp/control-state.ts and packages/tui/src/acp/agent.ts. The size list and the labels reuse the helpers from the /model picker. The error messages follow the wording of the existing model and thinking effort messages. I also added a short "ACP context window" section to docs/tui-capabilities.md, next to "ACP Skill commands". Nothing changed in local-runtime-v2. No files were added or moved.

    Evidence

    Base: main at 4fdf523

    Check Result
    pnpm typecheck Passed
    pnpm lint:tui Passed
    ACP tests (acp-agent.test.ts) 109 passed
    pnpm test:capabilities 199 files passed. 5106 tests passed, 18 skipped.
    New tests without the source change 3 of the 4 new tests fail, so they really test the new code
    pnpm build Passed
    pnpm test:smoke 20 passed, 0 failed, 1 skipped (macOS only)
    pnpm check:source Passed
    pnpm verify Passed, 14 gates on Linux.test:windows and test:sandbox are not run on Linux, and test:release-package needs a release archive.

    The fourth test passes on the old code too. It checks that the option stays hidden when the current size is not in the model's list. The old code never shows the option, so that is true there as well. The test is there to protect this rule in the future.

    ACP handshake with the built CLI

    I ran node dist/cli.js acp and sent real JSON-RPC messages over stdio. I used a new temp data folder, a fake MiniMax API key and the network block from pnpm test:smoke. Both runs made no network calls. I ran the same script on main and on my branch.

    On main, session/new returns ["permissionMode", "model"]. Setting the context window fails:

    {"code":-32602,"message":"Invalid params: Unsupported Session configuration option: contextWindow"}

    On my branch, session/new returns ["permissionMode", "model", "contextWindow"]. The new option:

    {
      "type": "select",
      "id": "contextWindow",
      "name": "Context window",
      "description": "Sets the context window for the selected model in this Session.",
      "category": "model_config",
      "currentValue": "512000",
      "options": [
        { "value": "512000", "name": "512K" },
        { "value": "1000000", "name": "1M" }
      ]
    }

    Then:

    • Setting "1000000" works. mcode sends config_option_update and the reply shows "currentValue": "1000000".
    • "2000000" is refused: Context window is not available for the selected model: 2000000
    • "1M" is refused: Invalid context window value: 1M
    • No config_option_update is sent for the two refused values.
    • After a switch to MiniMax-M2.7, which has one size, the list is ["permissionMode", "model"].
    • After the run, config.yaml had no context window keys, and the default model was still minimax/MiniMax-M3.

    The 1M choice has no warning in this run. The runtime only sends the higher_usage hint for Token Plan accounts, and I used an API key. The terminal /model picker reads the same field. The unit tests cover the warning.

    I can share the full logs and the script if you want them.

    Live test in Zed

    I added the built CLI to Zed 1.21 as a custom agent and used a real OpenRouter model. mcode used a separate test data folder. The first model, poolside/laguna-s-2.1:free, got two sizes (128K and 200K) that I added by hand in config.yaml. The second model, cohere/north-mini-code:free, had one size.

    What I saw:

    • The Context window menu shows up next to the model menu with 128K and 200K.
    • After I chose 200K and sent a prompt, /context showed Budget: 13,439 / 200,000 tokens. So the runtime really uses the chosen size.
    • After I closed and reopened the thread, it was still on 200K and /context still showed 200,000.
    • After a switch to the Cohere model, the menu was gone and /context showed 128,000. After a switch back, the menu came back on 128K.
    • After the test, config.yaml was unchanged.

    The ACP log from Zed confirms it:

    • Every session/new reply had contextWindow at 128000. So a new session does not take the choice of another session.
    • Zed remembers the last choice and sends session/set_config_option again right after session/new. That is why a new thread in Zed can show 200K. mcode's own reply to session/new is always the default.
    • In one case Zed first switched to the Cohere model and then sent contextWindow=200000 again. mcode refused it with Invalid params: Context window is not available for the selected model: 200000, and Zed kept working.
    • The only other errors were two OpenRouter rate limits on the free model (HTTP 429).

    Screenshots:

    The menu in Zed (128K and 200K):
    Image

    After reopening the thread, still 200K, and /context shows 200,000:

    Image

    Model with one size: no menu, and /context shows 128,000:

    Image
    ACP log from Zed (only the context window and model messages)

    Replies come in the same order as the requests. In sessions 2 and 3, the first reply answers the model change and the second answers the context window.

    session 1: session/new reply -> contextWindow 128000
    session 1: Zed sets model = poolside/laguna-s-2.1:free
    session 1: Zed sets contextWindow = 128000
    session 1:   reply -> contextWindow 128000
    session 1:   reply -> contextWindow 128000
    session 1: Zed sets contextWindow = 200000
    session 1:   reply -> contextWindow 200000
    session 2: session/new reply -> contextWindow 128000
    session 2: Zed sets model = poolside/laguna-s-2.1:free
    session 2: Zed sets contextWindow = 200000
    session 2:   reply -> contextWindow 128000
    session 2:   reply -> contextWindow 200000
    session 3: session/new reply -> contextWindow 128000
    session 3: Zed sets model = poolside/laguna-s-2.1:free
    session 3: Zed sets contextWindow = 200000
    session 3:   reply -> contextWindow 128000
    session 3:   reply -> contextWindow 200000
    session 3: Zed sets model = cohere/north-mini-code:free
    session 3:   reply -> contextWindow none
    session 4: session/new reply -> contextWindow 128000
    session 4: Zed sets model = cohere/north-mini-code:free
    session 4: Zed sets contextWindow = 200000
    session 4:   reply -> contextWindow none
    session 4:   reply -> error: Invalid params: Context window is not available for the selected model: 200000
    

    Not tested

    • I did not test with a MiniMax account, so I did not see the "higher usage" warning live. The runtime only sends that hint for Token Plan accounts. The unit tests cover it.
    • The two sizes of the OpenRouter model were added by hand in config.yaml for the test.
    • I did not test it on macOS or Windows.

    @minmax if you have time, please try the branch with your client. Your feedback would help before it gets merged.

  5. jaywedgeworth22 commented on Oct 1, 2026

    @jaywedgeworth22

    +1 from another ACP client. We integrate MiniMax Code through mcode acp from a multi-bot desktop app. ACP exposes permissionMode, model and thinkingEffort, but no context-window option, so the per-session 512K vs 1M choice can only follow the TUI's /model setting (defaultModelContextWindow in config.yaml), and a model switch drops it back to 512K. A session config option (for example configId contextWindow with values 512k and 1m) or a CLI flag for mcode acp, mirroring thinkingEffort, would solve this for us.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestneeds-triageAwaiting maintainer assessment

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions