fix(cli): preserve output capacity for encoded image requests - #11891
Conversation
capOutputTokens estimated input tokens with the raw byte size of encoded images, which providers do not charge against context (vision input is accounted separately). For large attachments this drove available context to zero and capped maxOutputTokens down to a tiny value unnecessarily. Use the normalized token count, which replaces encoded media with a small placeholder, so output capacity is preserved when requests contain images.
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (6 files)
Incremental review notesReviewed the latest commit ( Previous Review Summaries (2 snapshots, latest commit a909efe)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit a909efe)Status: No Issues Found | Recommendation: Merge Files Reviewed (6 files)
Incremental review notesReviewed changes since the previous review (commit Previous review (commit 9857c98)Status: No Issues Found | Recommendation: Merge Files Reviewed (4 files)
Reviewed by claude-sonnet-5-20260630 · Input: 20 · Output: 3.1K · Cached: 356.8K Review guidance: REVIEW.md from base branch |
Prefer the previous finished turn's provider-reported context size (input + output + cache) when capping maxOutputTokens, falling back to the media- normalized estimate only when no usage has been reported yet. The provider's own tokenization already accounts for image/vision input, so encoded payload bytes no longer distort the output allowance. WIP: tests still to be added.
# Conflicts: # packages/opencode/src/session/llm.ts
Add regression coverage for capOutputTokens preferring the provider-reported context size (image/vision input priced by the provider) and falling back to the media-normalized floor when reported usage is smaller or absent.
The compaction summary's reported input tokens reflect the pre-compaction history, not the trimmed context of the next request. Guard like the adjacent isOverflow check so output is not collapsed right after auto-compaction.
fix(cli): preserve output capacity for encoded image requests
The bug
When a session contains images, Kilo could shrink the model's output budget to almost nothing.
Before every request, Kilo caps
max_output_tokensso that input + output fit inside the model's context window. To do that it estimates how many tokens the input will use. That estimate counted base64 image data as if it were ordinary text. Base64 is huge, but providers do not bill images that way: they decode the image and charge a much smaller, model-specific vision cost.Real example from #11432: a session with five PNG attachments.
The model spent those 1,758 tokens on reasoning and produced no visible answer. The session failed even though roughly 700k tokens of context were actually free.
The fix
Stop guessing what images cost. Ask the provider.
After every turn, the provider reports exactly how many tokens the conversation used, including the real vision cost of images. Kilo already trusts this number to decide when to compact. This PR feeds the same number into the output cap:
In the example above the cap now computes 1,000,000 - 298,152 - safety margin, sees plenty of room, and sends the full 32,000 output budget.
What does not change
Known approximation
The reported number is one turn old, so an image attached in the current turn is priced at ~0 until the next turn reports usage. If that ever causes a real overflow, the provider rejects the request and compaction retries, which is the pre-existing recovery path and the same behavior upstream opencode uses for every request. Exact pre-request pricing would require provider-specific vision token math, which #11432 scopes as separate follow-up work.
Fixes #11432. Supersedes #11497, which approximated image cost with a hardcoded per-image constant instead of using the provider's own count.