fix: propagate CodeWhisperer cache token usage instead of hardcoding zero - #129
Open
q8247990 wants to merge 1 commit into
Open
fix: propagate CodeWhisperer cache token usage instead of hardcoding zero#129q8247990 wants to merge 1 commit into
q8247990 wants to merge 1 commit into
Conversation
…zero The SDK streaming and non-streaming response paths emitted cache_creation_input_tokens and cache_read_input_tokens as hardcoded zero literals, even though the SDK's TokenUsage type carries optional cacheReadInputTokens and cacheWriteInputTokens fields (see @aws/codewhisperer-streaming-client dist-types/models/models_0.d.ts). Capture the cache fields from MetadataEvent.tokenUsage when present in transformSdkStream and ResponseHandler.handleSdkNonStreaming, and propagate them through convertToOpenAI's message_delta usage emission. The non-SDK paths (raw HTTP event stream) cannot report cache tokens because that wire format only exposes contextUsagePercentage; those sites are left at 0 with a comment explaining why. Pure observability: no request payload is changed, no cachePoint is added, no billing behavior is modified.
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
The SDK streaming path (
src/plugin/streaming/sdk-stream-transformer.ts, lines 315-316) and the SDK non-streaming path (src/core/request/response-handler.ts, lines 171-174 / 197-201) emittedcache_creation_input_tokens: 0andcache_read_input_tokens: 0as hardcoded literals, regardless of what the backend actually returned. The streaming loop already capturedcontextUsagePercentagefromevent.metadataEvent, so adding the cache fields alongside it is the natural minimal extension.The CodeWhisperer streaming SDK's
TokenUsagetype carries optionalcacheReadInputTokensandcacheWriteInputTokensfields — confirmed in@aws/codewhisperer-streaming-clientdist-types/models/models_0.d.ts(TokenUsage.cacheReadInputTokens?: number | undefined,cacheWriteInputTokens?: number | undefined) and the corresponding JSON schema indist-es/schemas/schemas_0.js(cacheReadInputTokens,cacheWriteInputTokens). The SDK writes these values intoMetadataEvent.tokenUsageon the wire; we just weren't reading them.Field mapping:
cacheReadInputTokens→usage.cache_read_input_tokenscacheWriteInputTokens→usage.cache_creation_input_tokensImpact
OpenCode's cost and context accounting can never reflect cache reads or writes. A 24-hour / 806-request session against a sonnet-class model showed
cache.read = 0on every single request, even when the backend's prompt prefix was clearly stable — the numbers on the wire were nonzero, the values we emitted were always zero. Users have no signal to tell whether the backend actually hit its cache.Fix
Three changed spots in source, one new test file:
src/plugin/streaming/sdk-stream-transformer.ts— capturecacheReadInputTokens/cacheWriteInputTokensfromevent.metadataEvent.tokenUsagealongside the existingcontextUsagePercentagecapture; replace the hardcoded zeros in the finalmessage_deltausage emission with the captured values.src/core/request/response-handler.ts(handleSdkNonStreaming) — read the same two fields fromevent.metadataEvent.tokenUsagewhile it already readsinputTokens/outputTokens; include them in the OpenAI-shaped usage object on the response.src/plugin/streaming/openai-converter.ts— propagatecache_creation_input_tokens/cache_read_input_tokensthrough themessage_delta→ OpenAI chunk conversion (previously dropped silently).The non-SDK paths (raw HTTP event stream in
src/plugin/streaming/stream-transformer.tsandResponseHandler.handleNonStreaming) deliberately remain at 0 — that wire format only exposescontextUsagePercentage, not per-request token usage. Those sites have a brief comment explaining why, to prevent future contributors from re-introducing the same fix attempt on a path that simply cannot carry the data.This is pure observability. No request payload is changed, no
cachePoint/clientCacheConfigis added, no billing behavior is modified. We only report what the backend already returned on the existing event.Tests
New file:
src/__tests__/cache-token-usage.test.ts— 8 tests covering:metadataEvent.tokenUsagecarries non-zero cache fields → emitted usage reflects them (800 / 1200)metadataEvent.tokenUsageomits cache fields → zeros (no crash)metadataEventhas notokenUsageat all → zerosmetadataEventever arrives → zerosResponseHandler.handleSdkSuccess(streaming=false)reading the JSON bodyRun results (
bun test):Prettier check (
prettier --check 'src/**/*.ts'): all files use Prettier code style.TypeScript (
tsc --noEmit): clean.Build (
npm run build): clean —tsc -p tsconfig.build.json && node scripts/fix-esm-imports.mjsfinishes withfix-esm-imports: patched 40/114 files in dist/.Out of scope
The opus-5 200K-vs-1M context window issue (where
getContextWindowSizereturns 200K but the model actually has 1M) is a separate problem tracked upstream — related PR #121 is already working on window discovery. This PR intentionally does not touchgetContextWindowSize, the model registry, or any context-window logic. It also does not introducecachePoint/clientCacheConfigon outgoing requests; that is a billing-behavior change that belongs in a separate discussion.