Skip to content

fix(cli): preserve output capacity for encoded image requests - #11891

Merged
marius-kilocode merged 5 commits into
mainfrom
caramel-turner
Jul 3, 2026
Merged

fix(cli): preserve output capacity for encoded image requests#11891
marius-kilocode merged 5 commits into
mainfrom
caramel-turner

Conversation

@marius-kilocode

@marius-kilocode marius-kilocode commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

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_tokens so 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.

Value
Model context window 1,000,000
Configured output budget 32,000
What the provider actually charged for the input 298,152 tokens
What Kilo estimated (counting base64 as text) 996,194 tokens
Output budget Kilo sent 1,758

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:

  1. Provider-reported context size from the last finished turn is now the primary input to the cap. It is exact for everything sent so far, images included.
  2. The client-side text estimate (with image bytes replaced by a placeholder, so base64 size never counts) is kept as a floor. It covers what the report cannot see yet: the very first request of a session, and new text or tool schemas added since the last turn.
  3. The cap uses the larger of the two, so it never under-counts what it can see.

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

  • Small-context models are still protected: oversized text and tool schemas still reduce the output budget exactly as before.
  • If the input alone truly exceeds the context window, Kilo still sends the request unchanged so the provider returns a normal overflow error and existing compaction handles it.
  • Compaction summary messages are excluded as the reported source, because their reported input describes the pre-compaction history, not the freshly trimmed context.
  • All other request paths (compaction, commit message generation, etc.) pass no reported value and behave exactly as before.

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.

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.
@kilo-code-bot

kilo-code-bot Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (6 files)
  • .changeset/preserve-image-output-capacity.md
  • packages/opencode/src/kilocode/session/llm.ts
  • packages/opencode/src/session/llm.ts
  • packages/opencode/src/session/prompt.ts
  • packages/opencode/test/kilocode/session-overflow.test.ts
  • bun.lock
Incremental review notes

Reviewed the latest commit (a0a6a29e) since the previous review (commit a909efe2): it changes reportedContextTokens in packages/opencode/src/session/prompt.ts to skip summary messages (lastFinished.summary !== true) when deriving the provider-reported context size, matching the pre-existing isOverflow check a few lines above in the same function. This is a small, correct consistency fix — summary messages' reported input reflects pre-compaction history rather than the trimmed context of the next request, so excluding them from the cap basis is appropriate. No bugs, style violations, or fork-hygiene issues found in the changed code.

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)
  • .changeset/preserve-image-output-capacity.md
  • packages/opencode/src/kilocode/session/llm.ts
  • packages/opencode/src/session/llm.ts
  • packages/opencode/src/session/prompt.ts
  • packages/opencode/test/kilocode/session-overflow.test.ts
  • bun.lock
Incremental review notes

Reviewed changes since the previous review (commit 9857c986): the two follow-up commits that switch the output cap to prefer provider-reported context (input + output + cache via KiloSessionOverflow.count) over the client-side media-normalized estimate, threading reportedContextTokens through StreamInput from the prompt loop. Verified the Math.max(reported, estimated) floor logic, the new capOutputTokens test cases (including the 17_952 expected cap for context - reported - SAFETY), and the KiloSessionOverflow.count usage against the existing lastFinished message pattern already used for compaction.isOverflow. No bugs, style violations, or fork-hygiene issues found in the added code.

Previous review (commit 9857c98)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (4 files)
  • .changeset/preserve-image-output-capacity.md
  • packages/opencode/src/kilocode/session/llm.ts
  • packages/opencode/src/session/llm.ts
  • packages/opencode/test/kilocode/session-overflow.test.ts

Reviewed by claude-sonnet-5-20260630 · Input: 20 · Output: 3.1K · Cached: 356.8K

Review guidance: REVIEW.md from base branch main

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.
@marius-kilocode
marius-kilocode enabled auto-merge July 3, 2026 10:31
@marius-kilocode
marius-kilocode merged commit a8d9cff into main Jul 3, 2026
27 checks passed
@marius-kilocode
marius-kilocode deleted the caramel-turner branch July 3, 2026 10:36
t7tran pushed a commit to t7tran/kilocode that referenced this pull request Aug 14, 2026
fix(cli): preserve output capacity for encoded image requests
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.

Encoded images can collapse the dynamic output token allowance

2 participants