The gap
usage_unreadable (added in #205) is reachable for a sniffed JSON response and structurally unreachable for a sniffed stream — which is the one place a truncated window is most likely to hide a usage block.
parseSSEUsageWhy only calls parseUsageWhy with a block usagePresent has already accepted, and parseUsageWhy reaches its ValidBytes branch only when no block was found anywhere. So worst ranges over absent / all_zero / unparsed_dialect and nothing else. A data: line cut mid-JSON yields no present block, so worst stays absent and the response reads as "the provider streamed no usage" — no counter, no shape record.
That is #200's own defect in the corner #205's classification does not cover: a measurement outage indistinguishable from a provider that said nothing.
Why it is narrow, and why it is still worth closing
Narrow because Anthropic puts input_tokens in message_start, at the front of the head window — so found is true and the classification is parsed. The reachable case needs a stream whose only usage block lands in the spliced middle.
Worth closing because the current detection is inferred rather than known. The buffered path decides "the bytes were not a whole document" from gjson.ValidBytes, which is a proxy for the fact it actually wants. The sniffer knows: sniffer.bytes() already computes s.total > len(s.head) to decide whether to splice at all.
Suggested fix
Give sniffer a spliced() bool and pass it into responseUsageWhy. Then:
- an
absent classification on a spliced window becomes unreadable_body, closing the streamed gap;
- the buffered case stops inferring from
ValidBytes and becomes exact;
valid_json in the shape record keeps its current meaning and gains a companion that says the window was cut.
It is a small change, but it threads a transport fact into a parser, which is a boundary #205 deliberately did not cross — the classification there is dialect-agnostic and reads only the bytes it is given. That is the decision this issue is for, not the code.
Not part of this
Adding the camelCase or nested dialects. Still blocked on a real response body or a shape record from a deployment that hits the gap, for the reason #200 gives: guessing between cacheWriteInputTokens and cacheCreationInputTokens produces a parser that looks correct and reads zero.
How it was found
Review of #205 (round 3), by the reviewer noticing that the two parsers are not symmetric and that a reader would assume they are. A comment at parseSSEUsageWhy's worst initialiser now states the asymmetry so the next reader does not have to rediscover it; this issue is the closure.
Related: #200 (the rule), #205 (the classification), #199 (waits on the shape record).
The gap
usage_unreadable(added in #205) is reachable for a sniffed JSON response and structurally unreachable for a sniffed stream — which is the one place a truncated window is most likely to hide a usage block.parseSSEUsageWhyonly callsparseUsageWhywith a blockusagePresenthas already accepted, andparseUsageWhyreaches itsValidBytesbranch only when no block was found anywhere. Soworstranges overabsent/all_zero/unparsed_dialectand nothing else. Adata:line cut mid-JSON yields no present block, soworststaysabsentand the response reads as "the provider streamed no usage" — no counter, no shape record.That is #200's own defect in the corner #205's classification does not cover: a measurement outage indistinguishable from a provider that said nothing.
Why it is narrow, and why it is still worth closing
Narrow because Anthropic puts
input_tokensinmessage_start, at the front of the head window — sofoundis true and the classification isparsed. The reachable case needs a stream whose only usage block lands in the spliced middle.Worth closing because the current detection is inferred rather than known. The buffered path decides "the bytes were not a whole document" from
gjson.ValidBytes, which is a proxy for the fact it actually wants. The sniffer knows:sniffer.bytes()already computess.total > len(s.head)to decide whether to splice at all.Suggested fix
Give
snifferaspliced() booland pass it intoresponseUsageWhy. Then:absentclassification on a spliced window becomesunreadable_body, closing the streamed gap;ValidBytesand becomes exact;valid_jsonin the shape record keeps its current meaning and gains a companion that says the window was cut.It is a small change, but it threads a transport fact into a parser, which is a boundary #205 deliberately did not cross — the classification there is dialect-agnostic and reads only the bytes it is given. That is the decision this issue is for, not the code.
Not part of this
Adding the camelCase or nested dialects. Still blocked on a real response body or a shape record from a deployment that hits the gap, for the reason #200 gives: guessing between
cacheWriteInputTokensandcacheCreationInputTokensproduces a parser that looks correct and reads zero.How it was found
Review of #205 (round 3), by the reviewer noticing that the two parsers are not symmetric and that a reader would assume they are. A comment at
parseSSEUsageWhy'sworstinitialiser now states the asymmetry so the next reader does not have to rediscover it; this issue is the closure.Related: #200 (the rule), #205 (the classification), #199 (waits on the shape record).