Repository navigation
fix(adapters)!: require positive evidence for Anthropic detection - #23
Merged
Merged
Conversation
…ache tokens BREAKING CHANGE: inputTokens changes value for any Anthropic or Bedrock response that reports cache tokens. Anthropic and AWS both document input_tokens/inputTokens as excluding cache tokens (total = cache_read + cache_creation + input_tokens, per each vendor's own prompt-caching docs). This package's own README and types.ts already claimed cache tokens are "already included in inputTokens" for every provider, true for OpenAI, false for Anthropic and Bedrock, whose input_tokens was passed straight through unchanged. The OpenTelemetry GenAI semantic conventions this package targets also say the total input count MAY be published regardless of caching. Every 0.2.x version undercounted inputTokens on cache-heavy Anthropic and Bedrock traffic. See CHANGELOG for the full note. - adapters.ts: anthropic and bedrock extract() now add cache_read/cache_creation (Anthropic) and cacheReadInputTokens/ cacheWriteInputTokens (Bedrock) into inputTokens. openai untouched, already correct. gemini untouched, candidatesTokenCount/ thoughtsTokenCount inclusion is not stated in Google's docs and is gated on a live API check not performed in this branch. - types.ts: cacheWriteTokens doc comment corrected, Bedrock also has cache-write, not Anthropic-only. - test/normalize.test.ts: replaced the input_tokens:20/cache_read:15 fixture (15+5=20 made the wrong reading numerically indistinguishable from the right one) with Anthropic's own doc-example shape, added a golden-invariant test per provider, doubled fixtures/responses.jsonl with cache/reasoning-bearing records so normalizeMany exercises the previously-untested path. - README, CHANGELOG: documented the vendor asymmetry and the fix. - package.json: 0.3.0 (not tagged, not published). Verified locally: npm ci, lint, typecheck, coverage (25/25 tests), build, demo, audit --audit-level=high all pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
BREAKING CHANGE: a bare { input_tokens, output_tokens } payload with no
provider marker on either side now throws instead of defaulting to
Anthropic.
Anthropic's Messages API and OpenAI's Responses/Agents usage report
input_tokens/output_tokens under identical key names. v0.1.1 fixed
detection for the case where OpenAI's object: "response" marker is
present; the fallback direction, no marker either way, still defaulted
to anthropic via a catch-all (r.object !== "response"). This silently
mislabeled any marker-less OpenAI Responses/Agents usage object
(exactly what the OpenAI Agents SDK's usage object looks like, no
top-level object field at all) as Anthropic, dropping its cache and
reasoning tokens with no error and pointing cost tracking at the wrong
price table. Same failure class independently found in a different
project's normalizer during this review.
- adapters.ts: anthropic detect() now requires type: "message"/
"message_start", or a cache_read_input_tokens/cache_creation_input_tokens
field. openai detect() additionally treats input_tokens_details/
output_tokens_details as a hard signal on its own, Anthropic never
emits those key names, so their presence identifies the shape even
without the Responses API envelope.
- adapters.ts, normalize.ts: detect() guards use Object.hasOwn instead
of the `in` operator; the provider lookup table is a null-prototype
object with an explicit "not a supported adapter" error, closing a
__proto__-as-provider-name path (no reachable pollution sink exists
today, but this is a library embedded in someone else's process).
- test/normalize.test.ts: the old bare-Anthropic test asserted the
removed catch-all; converted to assert the new throw (a genuine
Anthropic response with type: "message" is already covered by a
separate, still-passing test). Added a regression test for the exact
marker-less OpenAI Agents/Responses reproduction from the review, and
a test for the __proto__ guard. fixtures/responses.jsonl's bare
Anthropic record now carries type: "message" so normalizeMany's
batch test still succeeds on it.
- README, CHANGELOG: documented the new detection rule and the
behavior change plainly, with the --provider remedy named.
Verified locally: npm ci, lint, typecheck, coverage (27/27 tests),
build, demo, audit --audit-level=high all pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
mizcausevic-dev
changed the base branch from
fix/token-inclusion-semantics
to
main
September 13, 2026 22:04
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.
Summary
Branch 2 of the review's fix plan. Breaking, ships in the same 0.3.0 release as #22 (based on top of it, targets that branch rather than main).
src/adapters.ts's anthropicdetect()ended in a catch-all (r.object !== "response"), making anthropic the default adapter for any bare{ input_tokens, output_tokens }shape, since Anthropic's Messages API and OpenAI's Responses/Agents usage report those under identical key names. v0.1.1 fixed the marker-present direction; the marker-absent direction silently mislabeled things, including exactly the OpenAI Agents SDK's usage object shape (no top-levelobjectfield at all), as Anthropic, dropping cache and reasoning tokens with no error.A note on scope, since this fix has a real side effect the review calls out explicitly (its own "Hazard B"): the existing test
normalizes an Anthropic responsepassed a bare{ input_tokens: 20, output_tokens: 8 }with no marker of any kind, exactly the shape that must now throw. That test is converted to assert the new throw rather than weakened back to a catch-all; a separate, already-passing test (still normalizes a genuine Anthropic response carrying type: message) covers the positive case. This is a deliberate, documented behavior change, not a silent regression, see the CHANGELOG entry and the--provider anthropicremedy named in it.adapters.ts: anthropicdetect()requirestype: "message"/"message_start", or a cache field.openaidetect()additionally treatsinput_tokens_details/output_tokens_detailsas a hard signal on its own, those key names don't exist in Anthropic's API.adapters.ts,normalize.ts:detect()guards switched toObject.hasOwn; the provider lookup table is now a null-prototype object with an explicit error, closing a__proto__-as-provider-name path. No reachable pollution sink exists in this package today, this is hygiene for a library embedded in someone else's process, not a live vulnerability here.__proto__-guard test. Fixedfixtures/responses.jsonl's bare Anthropic record (addedtype: "message") so thenormalizeManybatch test still succeeds on it.README.md,CHANGELOG.md: documented the new detection rule and the behavior change plainly.Test plan
npm cinpm run lintnpm run typechecknpm run coverage— 27/27 testsnpm run buildnpm run demonpm audit --audit-level=high🤖 Generated with Claude Code