Repository navigation
fix: compress count_tokens so Claude Code never trips autocompact - #72
Merged
Merged
Conversation
Route Anthropic /v1/messages/count_tokens through the proxy compression pipeline (read-only processTurn, state discarded) so the relay returns the POST-compression token count. CC sizes its autocompact window off this count; reporting the compressed count keeps CC below its threshold indefinitely, eliminating the autocompact-induced task drift reported in #68. prepareCountTokens mirrors exactly what the next /v1/messages would forward (covered messages pruned, summaries injected, refs tagged) but does NOT mutate session.state, create blocks, or nudge. forward() does plain pipeThrough for the non-streaming JSON response (compressInjected=false). ACP_COUNT_TOKENS_PASSTHROUGH=1 restores the old unchanged-forward behavior.
- Extract isCountTokensRequest() (pure) so the routing-detection + escape hatch are unit-testable without spinning up an HTTP server (repo tests are unit-level). Covers path-variant detection, non-count_tokens rejection, and ACP_COUNT_TOKENS_PASSTHROUGH=1. - Deepen the read-only test to a full JSON snapshot of session.state + stats, catching nested mutations (block.survivedCount/generation, nudge baselines) that the previous field-by-field check missed. - Document the cross-repo read-only invariant (relies on acp-kernel syncBlocks deep-cloning) and the load-bearing compressInjected:false on the Prepared return (prevents the usage-capture tail from poisoning lastInputTokens). - NIT: drop redundant endsWith clause, drop unnecessary `as Prepared` casts. 176 tests pass (173 + 3 new detection/escape-hatch).
This was referenced Aug 9, 2026
Merged
ranxianglei
added a commit
that referenced
this pull request
Aug 9, 2026
Ported master's reasoning-through-compression (#71) and count_tokens (#72) features onto PR#73's layout-based patching architecture: responses.ts: - Removed 'reasoning' from OPAQUE_ITEM_TYPES (master: reasoning is conversation history, routed through compression, not preserved verbatim) - Added droppedReasoning counter + shouldDropAllReasoning() escape hatch (ACP_REASONING_KEEP=none) - Added 'reasoning' case to responsesToCore switch: creates a tracked BiliMessage (contentType 'reasoning', rawResponsesItem carried) so the kernel hides it once its turn is summarized; dropped reasoning uses 'continue' to skip layout.push so it is fully removed - Added reasoning re-emit to coreToResponses (assistant contentType 'reasoning' -> push raw verbatim) - patchOriginalItem needs no change: reasoning items are preserved via slot.original when their turn is live, dropped (nextById miss) when compressed -- correct by construction server.ts: - Added countTokens detection (isCountTokensRequest) + prepareCountTokens (read-only processTurn mirror so count_tokens reports post-compression count; ACP_COUNT_TOKENS_PASSTHROUGH=1 escape hatch) - Merged prepare ternary: countTokens -> anthropic -> openai -> responsesCompact -> responses Validation: typecheck clean, 216 tests pass (incl. 9 reasoning + 3 count_tokens + custom_tool_call fix), build ok.
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.
Problem
Claude Code (CC) through the
biliproxy suffers context explosion + task drift (upstream issue #68). Root cause (full analysis in dog/billion-context-pi#12):/v1/messages/count_tokensAPI call./v1/messagesturn and reports the compressedusage.input_tokensback to CC. So count_tokens was the one remaining leak — the only signal CC had that still reflected the uncompressed size.Fix
Route
/v1/messages/count_tokensthrough the same compression pipeline as/v1/messages, so the relay returns the post-compression token count. CC then sizes its window off the small compressed count and never trips autocompact.prepareCountTokens()runs the read-onlyprocessTurn(identical prune/inject/tag path asprepareAnthropic) but discards the returned state — count_tokens is a query and must not mutate session state, create blocks, or nudge. VerifiedprocessTurndoes not mutate the input state object.compressInjected: false+ emptyprocessedMessages→forward()does plain pipeThrough of the relay's non-streaming{input_tokens}JSON response.ACP_COUNT_TOKENS_PASSTHROUGH=1restores the old unchanged-forward behavior.Why this is correct
CC's autocompact is redundant in the proxy architecture: the proxy already compresses for the upstream model. CC's own compaction is pure double-compression — information loss (task drift) with zero model benefit. Making count_tokens report the compressed count aligns it with the
/v1/messagesusage CC already sees, so CC never believes it is near the limit.Trade-off (explicit): CC's local context now grows unbounded (it never autocompacts; the upstream never returns prompt-too-long since it sees the compressed body). Memory grows at MB scale for long sessions — acceptable and is the intended behavior.
Tests
tests/count-tokens.test.ts(4 new):Full suite: 173 pass, 0 fail.
npm run typecheckclean.npm run buildOK.Note on Codex
Codex does not need an equivalent fix — it triggers compaction off
response.completed.usage.input_tokens(no separate count_tokens call), which the proxy already returns compressed (verifiedstream-responses.ts:227passes the completed event through verbatim).