Skip to content

[Provider compatibility] command-code/mimo-v2.6-pro: tool-call markup after prose in the same text block bypasses CommandCodeToolTextFilter #5698

Description

@marciodps

Client or integration

Codex CLI

Provider or upstream service

command-code and opencode-go, both routing Xiaomi MiMo models (xiaomi/mimo-v2.6-pro, mimo-v2.6-pro, mimo-v2.6-flash)

OpenCodex version

2.64.0

Endpoint or capability

Tool calls over streaming (/v1/responses bridged to the provider adapters); MiMo native tool-call markup reaching the assistant text channel

Current behaviour

When the gateway cannot translate MiMo's native tool-call envelope into a structured call, the envelope reaches the client as visible assistant text. Two distinct gaps:

  1. command-code adapter: CommandCodeToolTextFilter only holds a text block that opens with <tool_call>. The gateway echo can arrive after ordinary prose inside the same text block, so the probe mismatches on the first prose character, the block commits as ordinary text, and the markup streams to the client verbatim. A native tool-call carrying the same input follows, so the call executes and the raw markup also renders — a duplicate in the content channel (same shape as [Provider compatibility] opencode-go/mimo-v2.6: native XML tool-call envelope reaches visible assistant text #5499).

  2. openai-chat adapter (used by opencode-go): no tool-text handling at all, so the same echo always reaches the client (see [Provider compatibility] opencode-go/mimo-v2.6: native XML tool-call envelope reaches visible assistant text #5499).

Observed echo shapes: the complete envelope in a single text-delta after prose, and the variant that omits </function> (<tool_call><function=exec>RAW</parameter></tool_call>).

Expected behaviour

A recognized <tool_call> envelope is either consumed into the structured call or stripped from the content channel. It must never reach the client as assistant text, on any adapter that serves MiMo models.

Minimal redacted request or reproduction

Stream shape observed (simplified):

text-start
text-delta   "ordinary prose..."            <- probe mismatches, block commits as text
text-delta   "<tool_call><function=exec>…</parameter></function></tool_call>"
tool-call    (native, same input as the markup)
finish       stop / tool_calls

Any request that leads the MiMo gateway to echo the envelope (for example the Codex freeform exec custom tool lowered to a single input string parameter) reproduces it.

Actual response or error

The client renders the raw envelope as assistant text while the native call also executes:

...ordinary prose...<tool_call><function=exec>const results = await Promise.all([
  tools.exec_command({ cmd: "..." })
]);
...</parameter></function></tool_call>

Upstream documentation

No public specification exists for the command-code /alpha/generate stream or the opencode-go gateway; the MiMo envelope grammar is the one described in the vLLM and SGLang MiMo tool parsers (<tool_call><function=NAME><parameter=KEY>VALUE</parameter></function></tool_call>, freeform bodies raw without parameter tags). Cross-reference: #5499.

Suggested mapping or implementation notes

Validated local patch on 2.64.0 (both adapters):

  1. CommandCodeToolTextFilter.textDelta (command-code-tool-text.ts): scan deltas for TOOL_CALL_MARKER whenever the block state is not probing/held. Split at the marker (index > 0, or index 0 on a non-probing block): prose keeps its queued/streamed path, the markup starts a fresh probing block and follows the normal hold/drop/restore route. Extracted the incremental probe into a helper reused by both paths. Known limitation: a marker split across deltas after prose is still released as text (the gateway echoes the whole envelope in one delta in practice).

  2. openai-chat.ts: for MiMo-routed requests (/(^|\/)mimo-v2/i on the routed model id), content and reasoning deltas route through the same filter. Two wire differences mattered: the chat stream has no text-start/text-end events, so the open content block must be ended explicitly before the native-call flush (otherwise the filter interrupts the held block instead of matching it), and stopReasonFor() here never returns stop/tool_calls, so the restore verdict reads the raw finish_reason.

Validated with an SSE replay against the real filter/adapter: prose+markup in separate deltas with a native duplicate (dropped, no leak), prose+markup clean stop (restored), prose+markup in one delta (restored), pure-markup block (restored), plain prose (unchanged), non-MiMo model (gate off, passthrough), truncated stream (released as text). Happy to open a PR.

Additional context and attachments

Checks

  • I searched existing provider and compatibility issues.
  • The request and response were redacted.
  • The expected behaviour is based on an upstream specification or a concrete client requirement.

Activity

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

    providerProvider adapters, OpenAI-compat presets, upstream API quirksprovider-compatibilityProvider compatibility reportstoolstool_calls, MCP, web-search / sidecar tools

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions