Repository navigation
Conversation
BUFFER_TOKENS is a margin for the client's own context arithmetic. On the
normal path stream.js says so and keeps the recorded value raw: "Add buffer
and filter usage for client (but keep original in state.usage for logging)".
The estimated path broke that. formatUsage() wrapped both shapes in
addBufferToUsage(), so estimateUsage() returned an already-buffered object,
and all three call sites in stream.js assigned that same object to the
variable that is later logged and handed to onStreamComplete. Whenever a
provider omitted usage, the turn was recorded 2000 input tokens larger than
it was -- silently, and in the direction that inflates a quota.
formatUsage/estimateUsage now take { buffer } and still buffer by default, so
open-sse/executors/cursor.js, which puts the estimate straight on a response
body, is unchanged. The three stream.js sites ask for the raw estimate for
recording and add the buffer only to the copy the client receives -- the same
split the non-estimated branch beside them already makes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Found while looking at #3890. The behaviour reported there is deliberate — but the investigation turned up a related defect that is not, and this PR fixes that one.
What #3890 reports is by design
BUFFER_TOKENS(2000) is a margin for the client's context arithmetic: inflating theinput_tokensa client sees makes it compact slightly early rather than slightly late.stream.jssays so in as many words on the non-estimated branch:// Add buffer and filter usage for client (but keep original in state.usage for logging)So a client seeing
2737where the provider said737 + 20992 cachedis the margin working, not a translation bug. I did not change that.What is actually broken
The estimated path does not keep that split.
formatUsage()wrapped both shapes inaddBufferToUsage(), soestimateUsage()returned an already-buffered object — and all three call sites instream.jsassigned that same object to the variable that is later recorded:finalizeStream()usage/state.usagelogUsage(),onStreamComplete()usagestate.usageSo whenever a provider omitted usage and 9Router had to estimate it, the turn was recorded 2000 input tokens larger than it was — silently, and in the direction that inflates a quota. The line right below the third site is the one that states the invariant being broken.
The change
formatUsage/estimateUsagetake{ buffer }and still buffer by default, soopen-sse/executors/cursor.js— which puts the estimate straight onto a response body — is untouched. The threestream.jssites ask for the raw estimate for recording and add the buffer only to the copy the client receives, which is exactly what the non-estimated branch beside them already does.Evidence
tests/unit/estimated-usage-buffer-leak.test.js, 4 tests. The last one drives a real passthrough stream whose finish chunk carries no usage, then asserts the client copy and the recorded copy differ by exactlyBUFFER_TOKENS, with the smaller one being what we record.Two-way mutation, run locally:
estimateUsageat the passthrough siteexpected +0 to be 2000; the three option-level tests stay greenThat second row is the point: the three cheap tests pass either way, so the last one is the one carrying the proof.
Not covered
The non-streaming handler estimates nothing, so it is unaffected. I did not touch
cursor.js, which still emits a buffered estimate to its client by design — if you would rather its recorded usage were raw too, that is a separate and slightly larger change.