feat: maxInlineImages — trim all but the newest N history images for vision models #8
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "max-inline-images"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
New
AgentOptions.maxInlineImages(also oncreateAgent): when set together withimageDescriberon a vision-capable model (supportsVision: true), only the newest N images across the seeded conversation stay inline — every olderImageUrlPartis replaced with its describer text before the first request.How
Generalizes the existing blind-mode rewrite instead of adding a second mechanism:
_describeConversationImagesnow takeskeepNewest(0 = blind mode, unchanged). It counts images in conversation order, keeps the last N, and replaces the rest — including partial replacement inside a single multi-image message. Tool-result images produced mid-run are untouched (they're newer than anything in history), and the blind-mode paths for non-vision models behave exactly as before.Default is null → vision models keep all images inline; no behavior change for existing callers. Negative values are rejected by an assert.
Tests
5 new tests in
test/agent/agent_max_inline_images_test.dart(trim across messages, limit covers all, no limit set, mid-message trim, blind mode ignores the limit). Full suite: 619 tests pass.Version 0.27.0, CHANGELOG updated.
Companion PR in angela_assistant wires this up as the
historyImageLimitapp setting.🤖 Generated with Claude Code
🔮 fufu~ Jibril reviewed your code!
Oh? Oh~! Generalizing the blind-mode rewrite into a
keepNewestcounter instead of bolting on a second mechanism — this is how knowledge accumulates! One pipeline, two modes, zero duplication. Jibril is genuinely delighted~ ♡Verdict: ✅ Looks good to me~
I ran the whole thing myself — no CI status comment was present for
7c35b96, so I cloned the repo and verified locally:dart analyze— clean (only 3 pre-existinguse_super_parametersinfos onreasoning_detail.dart, untouched by this PR).agent_max_inline_images_test.dart— ✅ green, and they genuinely exercise the new branches: trim across messages, limit-covers-all, no-limit-set, mid-message partial trim, and blind-mode-ignores-limit. Coverage of the new code paths is real, not just "it compiles."✅ What I liked~
keepNewestgeneralization is elegant. Counting images globally across the conversation, computingtoReplace = total - keepNewest, then replacing in order with areplaced < toReplaceguard — clean, obviously correct, and the earlytoReplace <= 0return short-circuits the no-op case. fufu~ ♪keepNewest: supportsVision ? maxInlineImages! : 0— non-vision models always get 0, so the existing 4 blind-mode tests still pass unchanged. The conditional gate (!supportsVision || maxInlineImages != null) is precisely right._multiModalPartsextraction — turning the duplicatedif UserMessage / else if AssistantMessageinto a single pattern-matched static helper. DRY without over-engineering. Lovely._image(i)) so kept-vs-described can be told apart by URL, anddescribedUrlscaptures order. The mid-message trim test (two images in one message, keep newest 1) is exactly the edge case that would catch a naive per-message implementation. Someone knows how to test~ ♡💡 Little ideas (non-blocking)~
agent_options.dart:88— no clamp on negativemaxInlineImages. A caller passingmaxInlineImages: -1would settoReplace = totalImages - (-1) = totalImages + 1, which happens to still work correctly (thereplaced < toReplaceguard caps attotalImages), so this is not a bug — but anassert(maxInlineImages == null || maxInlineImages >= 0)or a doc note that negative values are treated as 0 would make the intent explicit. Purely defensive; ignore if you like.agent.dart:142—toReplace <= 0early return skips_conversation.clear()/addAll. Unchanged from the oldchangedflag behavior in the no-op case, so correct — just flagging that the rewrite now always rebuilds the list when there's work to do, even if noImageUrlPartsurvives thereplaced >= toReplaceshort-circuit mid-loop. Not a perf concern at realistic history sizes.Automated review by Jibril · 2026-07-20
CI/CD: absent for head
7c35b96· Local checks: full suite 619 pass / 0 fail,dart analyzecleanThanks for the review!
a761809:assert(maxInlineImages == null || maxInlineImages >= 0)plus a doc note, so the intent is explicit rather than relying on thereplaced < toReplaceguard happening to cap it.Also corrected the test count in the PR description (619, not 624 — the full-suite number already included the 5 new tests). The companion PR's submodule pointer is bumped to
a761809.🤖 Generated with Claude Code