Skip to content

fix: MiMo MCP image/telemetry fixes (#38) - #95

Merged
ltmoerdani merged 5 commits into
mainfrom
fix/issue-38-top-level-image-size-guard
Aug 2, 2026
Merged

fix: MiMo MCP image/telemetry fixes (#38)#95
ltmoerdani merged 5 commits into
mainfrom
fix/issue-38-top-level-image-size-guard

Conversation

@ltmoerdani

Copy link
Copy Markdown
Owner

Summary

Kumpulan fix untuk Issue #38 — menangani 3 error berbeda yang muncul dari MiMo MCP di Copilot Chat:

1. Top-level image size guard

Cegah 400 Bad Request dari provider saat user paste gambar terlalu besar (>20MB). Guard ditambahkan di extension.ts sebelum request dikirim, dengan compress/resize otomatis untuk gambar yang melebihi batas.

2. Trim old images from history

Fix screenshot loops — MiMo MCP terus kirim screenshot lama yang sudah ada di history, bikin request makin besar dan trigger error. Sekarang image > threshold di-trim dari conversation history sebelum dikirim.

3. Omit reasoning_content echo in tool_call

Fix 400 Upstream request failed — reasoning_content dari MiMo yang ter-echo di tool-call history bikin provider reject request. Sekarang reasoning_content di-strip dari tool-call messages.

4. Flatten list-type tool message content

Fix terkait upstream #32613 —有些 provider kirim content sebagai array of objects (bukan string) di tool messages. Sekarang di-flatten ke string sebelum dikirim ke provider yang butuh plain string.

Changes

File Change
src/extension.ts Image size guard, history trimming, reasoning_content stripping, content flattening
package.json Version bump → 0.4.4
CHANGELOG.md Release notes untuk 0.4.4
docs/issues/38-* Dokumentasi lengkap issue #38

Testing

  • Paste gambar besar (>20MB) → harus compress, bukan 400 error
  • MiMo MCP loop screenshot → history trimmed, no loop
  • MiMo tool-call reasoning → no 400 error
  • Provider yang expect string content → no type mismatch

Merge strategy

Merge commit (bukan squash) — preserve semua commit history.

Closes #38

…ed pasted images (#38)

Top-level image attachments (pasted/dragged into chat) had no size guard, unlike
MCP tool-result images which were capped at 1 MB by MAX_TOOL_RESULT_IMAGE_BYTES
(PR #79). A large screenshot/photo produced multi-MB base64 payloads that the
OpenCode Go gateway rejected with HTTP 400 'Upstream request failed' — observed
on mimo-v2.5 with payloadBytes=3182845.

Adds MAX_TOP_LEVEL_IMAGE_BYTES = 2_000_000 (2 MB raw) in convertMessage().
Threshold intentionally more liberal than the 1 MB tool-result cap because user
screenshots/photos are typically larger than pre-compressed MCP screenshots, while
staying under observed rejection (~3.18 MB) and Anthropic's published 5-10 MB
per-image limit. Vision-capable models auto-resize upstream to a 1568-2576px
patch budget anyway, so forwarding raw multi-MB images has no fidelity benefit.

Oversized images are replaced with an actionable placeholder text part: model still
knows an image was attached, user gets byte count + limit + resize hint.

Not a regression — top-level handler never had a size cap since dee9634; latent
bug surfaced because users now attach larger images.

- src/extension.ts: +38 lines (constant + guard in convertMessage)
- CHANGELOG.md: [Unreleased] entry
- docs/issues/38-20260725-top-level-image-size-guard.md: full investigation
Promote [Unreleased] -> [0.4.4] with 2026-07-25 release date.

Release notes:
- fix(vision): top-level image size guard (#38)
…hot loops (#38)

Root cause of MiMo + MCP 'Upstream request failed' was NOT per-image size
(documented in docs/issues/34 line 264+): agent screenshot loops accumulate
multi-MB base64 data URIs in conversation history. VS Code Copilot Chat is
supposed to trim history based on advertisedMaxInputTokens, but our local
estimator under-counts base64 image data (IMAGE_TOKEN_ESTIMATE=1024 per image
vs realistic ~80K tokens/MB), so VS Code never sees the true payload weight
and forwards multi-MB requests that OpenCode Go rejects.

Verified case from docs/issues/34:
  mimo-v2.5 + chrome-devtools-mcp after 8 screenshots:
  payloadBytes=4665383 (4.6 MB) -> 400 Upstream request failed
  every subsequent retry in the same agent loop also failed.

Fix: new trimOldImagesFromHistoryInPlace() keeps only the most recent
MAX_HISTORY_IMAGES_KEPT = 2 images and replaces older ones with a placeholder
text note. The model retains conversation structure and the latest screenshots
for immediate agentic context (compare current vs previous), while cumulative
payload stays bounded. Vision-capable upstream models auto-resize each image
to a 1568-2576 px patch budget, so old screenshots lose most pixel value once
a newer one arrives.

Applied AFTER vision proxy (so proxy text descriptions are preserved) and
BEFORE promptTokens estimation (so the output budget reflects the trimmed
payload). Diagnostic log line '[history-trim] Replaced N old image(s)...'
appears in the Output channel when trimming fires.

- src/extension.ts: +128 lines (constant + trimOldImagesFromHistoryInPlace)
- CHANGELOG.md: [0.4.5] entry
- package.json: 0.4.4 -> 0.4.5
…t 400 Upstream request failed (#38)

Root cause finally identified from Output channel logs (2026-07-25 12:23):
  messages=67 payloadBytes=244401 -> 200 OK (finishReason=tool_calls, reasoningChars=110)
  messages=70 payloadBytes=359146 -> 400 Upstream request failed
  every retry in same session -> 400

MiMo upstream (Xiaomi) uses strict Pydantic-style validator that rejects
assistant tool_call messages carrying a 'reasoning_content' field once they
appear in conversation history. The extension was echoing reasoning_content
back into history for ALL models (including MiMo) via reasoningForToolCalls()
in convertMessage(). This mirrors the DeepSeek V4 tool-call issue (#36354
upstream): 'OpenCode backend does not correctly handle reasoning_content
echoing for DeepSeek V4 tool calls (and possibly MiMo), causing 400 errors'.

Fix: gate reasoning_content injection by model family. For MiMo (/^mimo-/i),
omit reasoning_content in the echoed assistant tool_call history. The current
live response still surfaces reasoning_content to the user via the thinking
panel — only the history echo is dropped. Other families (DeepSeek, Kimi, GLM,
Qwen, MiniMax) tolerate the echo and keep it for cross-turn reasoning
continuity.

- src/extension.ts: convertMessage() takes optional rawModelId, skips
  reasoning_content injection for MiMo family in assistant tool_call branch.
… #32613)

Root cause finally verified via upstream issue anomalyco/opencode#32613
'Xiaomi MiMo rejects list-type tool message content (400 text is not set)':

Xiaomi MiMo API requires role:'tool' message content to be a plain string,
NOT a list of content parts. MiMo accepts multimodal content in user/assistant
messages but strictly rejects list-type content in tool messages. The OpenCode
Go gateway passes list-type content through unchanged, so we must flatten it
client-side.

Verified in user log (2026-07-25 13:05):
  messages=3 payloadBytes=151626 -> 200 OK (tool result without image)
  messages=5 payloadBytes=426159 -> 400 Upstream request failed
    (same + tool result WITH image_url list-type content)

Previous 4 fix attempts (top-level guard, history trim, reasoning echo, TDZ)
were all red herrings — the real issue is the tool message shape itself.

Fix: for MiMo family, when a tool result contains image parts, emit plain
string content by joining text parts and replacing each image with a short
placeholder note (the model cannot see tool images on MiMo upstream anyway).
Other providers (Kimi, GLM-5.1, MiniMax, Qwen) keep the multimodal array
because they accept list-type tool content.

Upstream PR anomalyco/opencode#32966 exists but was closed by automated
cleanup before merge, so this workaround is necessary until the gateway
handles the translation.

- src/extension.ts: convertMessage() tool-result branch gates multimodal
  array on model family; MiMo gets flattened string.
@ltmoerdani
ltmoerdani merged commit 8518a56 into main Aug 2, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant