Skip to content

fix(opencode): cap images re-sent from history (backport #39929) - #62270

Open
adamdotdevin wants to merge 1 commit into
v1from
v1-bound-images
Open

adamdotdevin wants to merge 1 commit into
v1from
v1-bound-images

Conversation

@adamdotdevin

Copy link
Copy Markdown
Member

Issue for this PR

Backport of #39929 (and the follow-up #56983) to the 1.x release line. No separate issue.

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

1.x keeps every image in a session's history and re-sends all of them as base64 on every request. Auto-compaction only counts tokens, and images cost almost none, so on 1M-context models (e.g. deepseek-v4.1-flash) request bodies reach 100–200 MB before compaction kicks in. On 2026-11-05, 1.x clients sent 23.4k of the 29.4k requests over 64 MB that reached the OpenCode gateway; 2.x sent 232. Those bodies run gateway isolates out of memory and get 413/400s from providers.

Image cap (ported from 2.x boundImages). At the end of MessageV2.toModelMessagesEffect, once inline images in the converted history pass 25 MiB, the oldest are replaced with the same placeholder text 2.x uses until 15 MiB remain. It uses the stable-cut version from #56983: the cut only moves each time another 10 MiB of images arrives, so the replaced prefix and the provider prompt cache stay the same between cuts. It counts user image/file parts, tool-result media/image-data/file-data items, and the synthetic user message that carries tool-result images for providers that can't take them in tool results (OpenAI-compatible and others). Remote URLs don't count.

Differences from 2.x:

  • It runs on AI SDK ModelMessage[], since 1.x has no LLMRequest. It's applied in toModelMessagesEffect, which feeds both the AI SDK and native runtimes, title generation and the compaction size estimate, rather than in ProviderTransform.message (which receives the AI SDK's provider-level prompt shape on one path and ModelMessage on the other).
  • There's no unsupportedParts composition. 1.x handles unsupported media separately in ProviderTransform.message, which runs after this.

Overflow classification. Request body too large and multimodal data size is too large now count as context overflow in @opencode-ai/llm's isContextOverflow, so 1.x compacts and retries instead of failing the turn, the same way it already handles HTTP 413 and context_length_exceeded.

How did you verify your code works?

  • Ported the four boundImages tests from 2.x (below the trigger, oldest-first replacement with identity kept for untouched messages, stable cut across new images, images nested in tool results), plus one end-to-end toModelMessages test for the OpenAI-compatible synthetic user message path. Added both new error strings to the existing overflow tests in packages/llm and message-v2.test.ts.
  • Red proof: with the cut disabled and the two patterns removed, every cap test except the below-trigger one (4 of 5) fails, and both overflow tests fail. With the change, they pass.
  • bun typecheck in packages/opencode and packages/llm. bun test test/session test/provider in packages/opencode (1166 pass, 0 fail). bun test in packages/llm (298 pass, 0 fail).

@rekram1-node: this is your #39929 on the v1 branch. Could you take a look?

Screenshots / recordings

N/A

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

This branch has not been deployed

No deployments
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