Summary
At v2 HEAD (v2-test, 529d55b1c3), toolChoice is fully plumbed through the internal LLM layer (packages/core/src/aisdk.ts:421, packages/opencode/src/session/llm.ts:47/234/319, native-request/native-runtime, provider-specific lowerings) but is not wired to either the config layer or the HTTP prompt API. packages/opencode/src/session/prompt.ts:1285 still hard-codes it:
toolChoice: format.type === "json_schema" ? "required" : undefined,
PromptInput (packages/opencode/src/session/prompt.ts:1499) exposed via the HTTP POST /session/:id/prompt endpoint (packages/opencode/src/server/routes/instance/httpapi/groups/session.ts:302) has no toolChoice field either.
Net effect: providers always get "auto" for regular turns. Models that follow auto conservatively (Kimi K3 in particular) frequently answer from context instead of invoking a tool that is available.
Not a duplicate — an update
Filing fresh against v2 HEAD, referencing the above.
Reproduction (v2 daemon + runtime MCP)
Environment: opencode v2 daemon (v2-test, 529d55b1c3), kimi-for-coding/k3, a remote MCP registered via POST /mcp exposing one tool (update_field).
Client sends a user turn like:
"My favorite color is blue. Record it now via update_field (field=\"favorite_color\", value=\"blue\") before replying."
Even with a strict system prompt ("You MUST call update_field before replying. This is mandatory."), Kimi K3 replies "Got it — blue is a great choice" with zero tool calls. Anthropic models on the same MCP entry invoke the tool reliably.
Daemon logs confirm the MCP entry is connected, initialize + tools/list complete cleanly, and update_field is in the returned tool list. The model just declines to call it.
Directly hitting our controller with a synthetic tools/call mutates state correctly, proving the transport is fine — the model simply never emits the call.
Ask
Two-part, both small:
1) Config path. Re-land the fix from #32521/#32518 against v2: add tool_choice: \"auto\" | \"required\" | \"none\" to the agent v2 config schema, map it onto AgentInfo.toolChoice, and use it at prompt.ts:1285 instead of the current ternary. Backwards compatible when unset.
2) HTTP path. Add optional toolChoice: \"auto\" | \"required\" | \"none\" to PromptInput (prompt.ts:1499) so external orchestrators calling POST /session/:id/prompt (runtime MCP bridges like ours, subagent runners) can force tool invocation per-turn without editing global config. Precedence: request > agent config > current default.
The plumbing already exists internally; both changes are essentially the missing edges of a wire that's otherwise complete.
Why this matters more than it used to
v2 promotes runtime MCP as a core feature (#37308). Runtime MCP orchestrators are exactly the callers that need per-turn toolChoice: \"required\" — they know the current turn is a form-fill / structured-mutation step where a tool call is not optional. Without the HTTP surface, orchestrators can't reliably drive Kimi (or any model that's conservative under auto) through MCP-mediated flows.
Willing to help
Happy to open a PR against v2 if there's interest — the diff is small and follows the shape of the two closed PRs. Would appreciate a signal that a review will land before the auto-cleanup fires again.
Summary
At v2 HEAD (
v2-test,529d55b1c3),toolChoiceis fully plumbed through the internal LLM layer (packages/core/src/aisdk.ts:421,packages/opencode/src/session/llm.ts:47/234/319, native-request/native-runtime, provider-specific lowerings) but is not wired to either the config layer or the HTTP prompt API.packages/opencode/src/session/prompt.ts:1285still hard-codes it:PromptInput(packages/opencode/src/session/prompt.ts:1499) exposed via the HTTPPOST /session/:id/promptendpoint (packages/opencode/src/server/routes/instance/httpapi/groups/session.ts:302) has notoolChoicefield either.Net effect: providers always get
"auto"for regular turns. Models that followautoconservatively (Kimi K3 in particular) frequently answer from context instead of invoking a tool that is available.Not a duplicate — an update
dev) both closed by automated 1-month cleanup with no review, not by rejection.Filing fresh against v2 HEAD, referencing the above.
Reproduction (v2 daemon + runtime MCP)
Environment: opencode v2 daemon (
v2-test,529d55b1c3),kimi-for-coding/k3, a remote MCP registered viaPOST /mcpexposing one tool (update_field).Client sends a user turn like:
Even with a strict system prompt ("You MUST call update_field before replying. This is mandatory."), Kimi K3 replies "Got it — blue is a great choice" with zero tool calls. Anthropic models on the same MCP entry invoke the tool reliably.
Daemon logs confirm the MCP entry is
connected,initialize+tools/listcomplete cleanly, andupdate_fieldis in the returned tool list. The model just declines to call it.Directly hitting our controller with a synthetic
tools/callmutates state correctly, proving the transport is fine — the model simply never emits the call.Ask
Two-part, both small:
1) Config path. Re-land the fix from #32521/#32518 against v2: add
tool_choice: \"auto\" | \"required\" | \"none\"to the agent v2 config schema, map it ontoAgentInfo.toolChoice, and use it atprompt.ts:1285instead of the current ternary. Backwards compatible when unset.2) HTTP path. Add optional
toolChoice: \"auto\" | \"required\" | \"none\"toPromptInput(prompt.ts:1499) so external orchestrators callingPOST /session/:id/prompt(runtime MCP bridges like ours, subagent runners) can force tool invocation per-turn without editing global config. Precedence: request > agent config > current default.The plumbing already exists internally; both changes are essentially the missing edges of a wire that's otherwise complete.
Why this matters more than it used to
v2 promotes runtime MCP as a core feature (#37308). Runtime MCP orchestrators are exactly the callers that need per-turn
toolChoice: \"required\"— they know the current turn is a form-fill / structured-mutation step where a tool call is not optional. Without the HTTP surface, orchestrators can't reliably drive Kimi (or any model that's conservative underauto) through MCP-mediated flows.Willing to help
Happy to open a PR against v2 if there's interest — the diff is small and follows the shape of the two closed PRs. Would appreciate a signal that a review will land before the auto-cleanup fires again.