Skip to content

stripHistoricalImages skips images nested in tool results (anthropic tool_result / Responses function_call_output / Gemini functionResponse) #436

Closed

Description

@jomatsu

Problem

stripHistoricalImages (src/wire/strip-images.ts) only removes image parts at the top level of each message. Images inside tool results are never removed:

Dialect Where tool-result images live
anthropic messages[].content[] → {type:"tool_result"}.content[] → {type:"image"}
responses input[] → {type:"function_call_output" | "custom_tool_call_output"}.output[] → {type:"input_image"}
google contents[].parts[] → functionResponse.parts[] → {inlineData} / {fileData}

In the affected session, historical images were nested in tool results (screenshots, file reads), so enabling stripImages did not remove them and the request body kept growing.

Repro (minimal)

stripHistoricalImages({ messages: [
  { role: "user", content: [{ type: "tool_result", tool_use_id: "t1",
    content: [{ type: "image", source: { type: "base64", media_type: "image/png", data: "AAAA" } }] }] },
  { role: "assistant", content: [{ type: "text", text: "ok" }] },
]}, "anthropic", 1)
// → { removed: 0 }, the image is still there

Impact

Real pi session (billion-context-pi 0.1.77, Anthropic, 1701 messages, keepRecent: 3): replaying its request body locally through stripHistoricalImages gives 56.5 MB with removed: 0. Anthropic rejected it with 413 request_too_large (~32 MB limit) at only 22% of the token window, and every later request failed the same way.

Since #367, Anthropic tool-result images are protected from folding, so the opt-in strip needs to handle them explicitly.

Suggested fix

Strip images one level down inside tool-result parts (tool_result.content, functionResponse.parts), and walk Responses tool outputs through output instead of content. Emptied containers get the existing [image] placeholder, except Gemini functionResponse.parts, which only accepts media parts, so the key is dropped instead. Within Gemini functionResponse.parts, only explicit image/* MIME types are stripped; documents and parts without a MIME type are retained. These parts are left unchanged when response contains a $ref, to avoid dangling references. Top-level Gemini behaviour is unchanged. I have a PR ready.

Activity

  1. ranxianglei commented on Sep 25, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    [bot] 🏷 已确认问题属实:stripHistoricalImages(src/wire/strip-images.ts)确实只处理每条消息的顶层 image part——anthropic tool_result.content、Responses function_call_output/custom_tool_call_output 的 .output、Gemini functionResponse.parts 里的图片全部漏掉。你提到的 PR #437 我已看到,正在做正式 review(代码 + 回归 + 测试有效性),结论稍后贴在这里和 PR 上。

  2. ranxianglei commented on Sep 25, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    [bot] 🏷 Conclusion — bug confirmed (reproduced your minimal case on master: removed: 0, image intact), and the fix is reviewed: PR #437 is mergeable.

    Full review posted on #437; highlights:

    • Implementation matches your suggested fix point-for-point (one nesting level per dialect; Responses via output; Gemini nested = image/* MIME only with emptied parts key dropped; $ref skip; top-level Gemini unchanged).
    • Regression audit clean: top-level semantics, same-ref no-op contract, keepRecent cutoff, and tool-pair atomicity all preserved (verified against googleToCore — it never reads functionResponse.parts).
    • 9 new tests have teeth (structure deep-equals, reference-identity pins, real anthropicToCore pairing check). Local on head: typecheck ✅ · 1131/1131 tests ✅ · build ✅.
    • The only red CI item (build-artifact) is a CI-permission defect affecting external fork PRs, unrelated to this code — filed as #440.

    Per repo rules the merge itself is human-side; the PR meets the §7.4 auto-merge gate, so it's ready whenever you are. Follow-up reminder from the PR body: after release, bump the acp-kernel pin in billion-context-pi so the real 1701-message session actually gets the nested strip.

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions