@@ -753,6 +753,24 @@ function normalizeUsage(u) {
753753 }
754754}
755755
756+ // CC 的 inputTokens 是「总数」(含缓存命中部分),而 Anthropic 的 input_tokens 只计
757+ // 非缓存部分 —— 官方 SDK 注释:Total input tokens in a request is the summation of
758+ // `input_tokens`, `cache_creation_input_tokens`, and `cache_read_input_tokens`。
759+ // 直接把 CC 的 inputTokens 当 input_tokens 转发,会让下游把两者当成互不重叠的两部分,
760+ // 相加后约为真实输入的两倍(issue #25)。
761+ //
762+ // CC 实际已经算好:inputTokenDetails.noCacheTokens(实测 noCacheTokens + cacheReadTokens
763+ // === inputTokens)。优先采用该字段;缺失时回退到减法,保证老版本上游也能得到正确值。
764+ function anthropicInputTokens ( usage , noCacheOverride ) {
765+ const u = usage || { } ;
766+ if ( typeof noCacheOverride === 'number' && noCacheOverride >= 0 ) return noCacheOverride ;
767+ const noCache = u . inputTokenDetails && u . inputTokenDetails . noCacheTokens ;
768+ if ( typeof noCache === 'number' && noCache >= 0 ) return noCache ;
769+ const cacheRead = u . cachedInputTokens || ( u . inputTokenDetails && u . inputTokenDetails . cacheReadTokens ) || 0 ;
770+ const cacheWrite = ( u . inputTokenDetails && u . inputTokenDetails . cacheWriteTokens ) || 0 ;
771+ return Math . max ( 0 , ( u . inputTokens || 0 ) - cacheRead - cacheWrite ) ;
772+ }
773+
756774function mapFinishReason ( reason ) {
757775 switch ( reason ) {
758776 case 'tool-calls' : return 'tool_calls' ;
@@ -1384,7 +1402,8 @@ function buildAnthropicResponse(model, fullText, toolCalls, finishReason, usage,
13841402 const estOut = Math . max ( 1 ,
13851403 Math . ceil ( ( ( fullText || '' ) . length + ( thinkingText || '' ) . length ) / 4 ) + ( toolCalls ? toolCalls . length * 20 : 0 ) ) ;
13861404 return {
1387- input_tokens : usage ?. inputTokens ?? 0 ,
1405+ // input_tokens 只计非缓存部分(Anthropic 语义),与 cache_* 相加才等于总输入
1406+ input_tokens : anthropicInputTokens ( usage ) ,
13881407 output_tokens : usage ?. outputTokens || estOut ,
13891408 cache_creation_input_tokens : usage ?. inputTokenDetails ?. cacheWriteTokens ?? 0 ,
13901409 cache_read_input_tokens : usage ?. cachedInputTokens ?? 0 ,
@@ -1551,6 +1570,7 @@ async function* createAnthropicSseTranslator(response, model, messageId, ctx) {
15511570 let outputTokens = 0 ;
15521571 let cachedInputTokens = 0 ;
15531572 let cacheWriteTokens = 0 ;
1573+ let noCacheTokens = - 1 ; // -1 = 上游未提供该字段,改用减法兜底
15541574 let stopReason = null ;
15551575 let hasError = false ;
15561576 let currentThinkingText = '' ; // accumulated thinking text for the open block
@@ -1690,6 +1710,9 @@ async function* createAnthropicSseTranslator(response, model, messageId, ctx) {
16901710 outputTokens = u . outputTokens ?? outputTokens ;
16911711 cachedInputTokens = u . cachedInputTokens ?? cachedInputTokens ;
16921712 cacheWriteTokens = u . inputTokenDetails ?. cacheWriteTokens ?? cacheWriteTokens ;
1713+ if ( typeof u . inputTokenDetails ?. noCacheTokens === 'number' ) {
1714+ noCacheTokens = u . inputTokenDetails . noCacheTokens ;
1715+ }
16931716 ctx . inputTokens = inputTokens ;
16941717 ctx . outputTokens = outputTokens ;
16951718 ctx . cachedInputTokens = cachedInputTokens ;
@@ -1717,7 +1740,9 @@ async function* createAnthropicSseTranslator(response, model, messageId, ctx) {
17171740 }
17181741 }
17191742
1720- // 无论上游是否回报 usage,都把本地计数同步进 ctx(零输出判定与 message_delta 账单依赖它)
1743+ // 无论上游是否回报 usage,都把本地计数同步进 ctx(零输出判定与超时日志依赖它)。
1744+ // 注意:ctx.inputTokens 保存的是上游原始总数,仅供日志排查;
1745+ // message_delta 的 input_tokens 走 anthropicInputTokens / noCacheTokens 换算,不读它。
17211746 ctx . inputTokens = inputTokens ;
17221747 ctx . outputTokens = outputTokens ;
17231748 ctx . cachedInputTokens = cachedInputTokens ;
@@ -1735,7 +1760,15 @@ async function* createAnthropicSseTranslator(response, model, messageId, ctx) {
17351760 yield `event: message_delta\ndata: ${ JSON . stringify ( {
17361761 type : 'message_delta' ,
17371762 delta : { stop_reason : stopReason || 'end_turn' } ,
1738- usage : { output_tokens : outputTokens , cache_read_input_tokens : cachedInputTokens , cache_creation_input_tokens : cacheWriteTokens || 0 , input_tokens : inputTokens } ,
1763+ usage : {
1764+ output_tokens : outputTokens ,
1765+ cache_read_input_tokens : cachedInputTokens ,
1766+ cache_creation_input_tokens : cacheWriteTokens || 0 ,
1767+ // 只计非缓存部分;否则下游把 input 与 cache_read 相加会得到约两倍(issue #25)
1768+ input_tokens : noCacheTokens >= 0
1769+ ? noCacheTokens
1770+ : Math . max ( 0 , inputTokens - cachedInputTokens - ( cacheWriteTokens || 0 ) ) ,
1771+ } ,
17391772 } ) } \n\n`;
17401773
17411774 yield `event: message_stop\ndata: ${ JSON . stringify ( { type : 'message_stop' } ) } \n\n` ;
0 commit comments