@@ -1006,7 +1006,7 @@ func (e *KiroExecutor) executeWithRetry(ctx context.Context, auth *cliproxyauth.
10061006 }
10071007
10081008 // 3. Update TotalTokens
1009- usageInfo . TotalTokens = usageInfo . InputTokens + usageInfo . OutputTokens
1009+ finalizeKiroUsageTotal ( & usageInfo )
10101010
10111011 appendAPIResponseChunk (ctx , e .cfg , []byte (content ))
10121012 reporter .publish (ctx , usageInfo )
@@ -1638,6 +1638,140 @@ type eventStreamMessage struct {
16381638// NOTE: Request building functions moved to internal/translator/kiro/claude/kiro_claude_request.go
16391639// The executor now uses kiroclaude.BuildKiroPayload() instead
16401640
1641+ func applyKiroTokenUsage (detail * usage.Detail , tokenUsage map [string ]interface {}) bool {
1642+ if detail == nil || tokenUsage == nil {
1643+ return false
1644+ }
1645+ updated := false
1646+ if outputTokens , ok := kiroTokenUsageInt64 (tokenUsage , "outputTokens" ); ok {
1647+ detail .OutputTokens = outputTokens
1648+ updated = true
1649+ }
1650+ if totalTokens , ok := kiroTokenUsageInt64 (tokenUsage , "totalTokens" ); ok {
1651+ detail .TotalTokens = totalTokens
1652+ updated = true
1653+ }
1654+ if uncachedInputTokens , ok := kiroTokenUsageInt64 (tokenUsage , "uncachedInputTokens" ); ok {
1655+ detail .InputTokens = uncachedInputTokens
1656+ updated = true
1657+ }
1658+ if cacheReadTokens , ok := kiroTokenUsageInt64 (tokenUsage , "cacheReadInputTokens" ); ok {
1659+ detail .CacheReadTokens = cacheReadTokens
1660+ detail .CachedTokens = cacheReadTokens
1661+ updated = true
1662+ }
1663+ if cacheCreationTokens , ok := kiroTokenUsageInt64 (tokenUsage , "cacheWriteInputTokens" ); ok {
1664+ detail .CacheCreationTokens = cacheCreationTokens
1665+ if detail .CachedTokens == 0 {
1666+ detail .CachedTokens = cacheCreationTokens
1667+ }
1668+ updated = true
1669+ }
1670+ return updated
1671+ }
1672+
1673+ func finalizeKiroUsageTotal (detail * usage.Detail ) {
1674+ if detail == nil || detail .TotalTokens != 0 {
1675+ return
1676+ }
1677+ detail .TotalTokens = detail .InputTokens +
1678+ detail .OutputTokens +
1679+ detail .ReasoningTokens +
1680+ detail .CacheReadTokens +
1681+ detail .CacheCreationTokens
1682+ }
1683+
1684+ func applyKiroContextUsageFallback (detail * usage.Detail , contextUsagePercentage float64 , hasPreciseTokenUsage bool ) (int64 , bool ) {
1685+ if detail == nil || contextUsagePercentage <= 0 || hasPreciseTokenUsage {
1686+ return 0 , false
1687+ }
1688+ calculatedInputTokens := int64 (contextUsagePercentage * 200000 / 100 )
1689+ if calculatedInputTokens <= 0 {
1690+ return 0 , false
1691+ }
1692+ detail .InputTokens = calculatedInputTokens
1693+ finalizeKiroUsageTotal (detail )
1694+ return calculatedInputTokens , true
1695+ }
1696+
1697+ func kiroTokenUsageInt64 (tokenUsage map [string ]interface {}, key string ) (int64 , bool ) {
1698+ raw , ok := tokenUsage [key ]
1699+ if ! ok || raw == nil {
1700+ return 0 , false
1701+ }
1702+ switch value := raw .(type ) {
1703+ case int :
1704+ return int64 (value ), true
1705+ case int8 :
1706+ return int64 (value ), true
1707+ case int16 :
1708+ return int64 (value ), true
1709+ case int32 :
1710+ return int64 (value ), true
1711+ case int64 :
1712+ return value , true
1713+ case uint :
1714+ return int64 (value ), true
1715+ case uint8 :
1716+ return int64 (value ), true
1717+ case uint16 :
1718+ return int64 (value ), true
1719+ case uint32 :
1720+ return int64 (value ), true
1721+ case uint64 :
1722+ return int64 (value ), true
1723+ case float32 :
1724+ return int64 (value ), true
1725+ case float64 :
1726+ return int64 (value ), true
1727+ case json.Number :
1728+ if n , err := value .Int64 (); err == nil {
1729+ return n , true
1730+ }
1731+ if n , err := value .Float64 (); err == nil {
1732+ return int64 (n ), true
1733+ }
1734+ }
1735+ return 0 , false
1736+ }
1737+
1738+ func kiroTokenUsageFloat64 (tokenUsage map [string ]interface {}, key string ) (float64 , bool ) {
1739+ raw , ok := tokenUsage [key ]
1740+ if ! ok || raw == nil {
1741+ return 0 , false
1742+ }
1743+ switch value := raw .(type ) {
1744+ case int :
1745+ return float64 (value ), true
1746+ case int8 :
1747+ return float64 (value ), true
1748+ case int16 :
1749+ return float64 (value ), true
1750+ case int32 :
1751+ return float64 (value ), true
1752+ case int64 :
1753+ return float64 (value ), true
1754+ case uint :
1755+ return float64 (value ), true
1756+ case uint8 :
1757+ return float64 (value ), true
1758+ case uint16 :
1759+ return float64 (value ), true
1760+ case uint32 :
1761+ return float64 (value ), true
1762+ case uint64 :
1763+ return float64 (value ), true
1764+ case float32 :
1765+ return float64 (value ), true
1766+ case float64 :
1767+ return value , true
1768+ case json.Number :
1769+ n , err := value .Float64 ()
1770+ return n , err == nil
1771+ }
1772+ return 0 , false
1773+ }
1774+
16411775// parseEventStream parses AWS Event Stream binary format.
16421776// Extracts text content, tool uses, and stop_reason from the response.
16431777// Supports embedded [Called ...] tool calls and input buffering for toolUseEvent.
@@ -1655,6 +1789,7 @@ func (e *KiroExecutor) parseEventStream(body io.Reader) (string, []kiroclaude.Ki
16551789
16561790 // Upstream usage tracking - Kiro API returns credit usage and context percentage
16571791 var upstreamContextPercentage float64 // Context usage percentage from upstream (e.g., 78.56)
1792+ var hasPreciseTokenUsage bool // Whether official tokenUsage supplied precise counts
16581793
16591794 for {
16601795 msg , eventErr := e .readEventStreamMessage (reader )
@@ -1827,33 +1962,13 @@ func (e *KiroExecutor) parseEventStream(body io.Reader) (string, []kiroclaude.Ki
18271962
18281963 // Check for nested tokenUsage object (official format)
18291964 if tokenUsage , ok := metadata ["tokenUsage" ].(map [string ]interface {}); ok {
1830- // outputTokens - precise output token count
1831- if outputTokens , ok := tokenUsage ["outputTokens" ].(float64 ); ok {
1832- usageInfo .OutputTokens = int64 (outputTokens )
1833- log .Infof ("kiro: parseEventStream found precise outputTokens in tokenUsage: %d" , usageInfo .OutputTokens )
1834- }
1835- // totalTokens - precise total token count
1836- if totalTokens , ok := tokenUsage ["totalTokens" ].(float64 ); ok {
1837- usageInfo .TotalTokens = int64 (totalTokens )
1838- log .Infof ("kiro: parseEventStream found precise totalTokens in tokenUsage: %d" , usageInfo .TotalTokens )
1839- }
1840- // uncachedInputTokens - input tokens not from cache
1841- if uncachedInputTokens , ok := tokenUsage ["uncachedInputTokens" ].(float64 ); ok {
1842- usageInfo .InputTokens = int64 (uncachedInputTokens )
1843- log .Infof ("kiro: parseEventStream found uncachedInputTokens in tokenUsage: %d" , usageInfo .InputTokens )
1844- }
1845- // cacheReadInputTokens - tokens read from cache
1846- if cacheReadTokens , ok := tokenUsage ["cacheReadInputTokens" ].(float64 ); ok {
1847- // Add to input tokens if we have uncached tokens, otherwise use as input
1848- if usageInfo .InputTokens > 0 {
1849- usageInfo .InputTokens += int64 (cacheReadTokens )
1850- } else {
1851- usageInfo .InputTokens = int64 (cacheReadTokens )
1852- }
1853- log .Debugf ("kiro: parseEventStream found cacheReadInputTokens in tokenUsage: %d" , int64 (cacheReadTokens ))
1965+ if applyKiroTokenUsage (& usageInfo , tokenUsage ) {
1966+ hasPreciseTokenUsage = true
1967+ log .Infof ("kiro: parseEventStream found tokenUsage input=%d cache_read=%d cache_creation=%d output=%d total=%d" ,
1968+ usageInfo .InputTokens , usageInfo .CacheReadTokens , usageInfo .CacheCreationTokens , usageInfo .OutputTokens , usageInfo .TotalTokens )
18541969 }
18551970 // contextUsagePercentage - can be used as fallback for input token estimation
1856- if ctxPct , ok := tokenUsage [ "contextUsagePercentage" ].( float64 ); ok {
1971+ if ctxPct , ok := kiroTokenUsageFloat64 ( tokenUsage , "contextUsagePercentage" ); ok {
18571972 upstreamContextPercentage = ctxPct
18581973 log .Debugf ("kiro: parseEventStream found contextUsagePercentage in tokenUsage: %.2f%%" , ctxPct )
18591974 }
@@ -2103,15 +2218,10 @@ func (e *KiroExecutor) parseEventStream(body io.Reader) (string, []kiroclaude.Ki
21032218 // Use contextUsagePercentage to calculate more accurate input tokens
21042219 // Kiro model has 200k max context, contextUsagePercentage represents the percentage used
21052220 // Formula: input_tokens = contextUsagePercentage * 200000 / 100
2106- if upstreamContextPercentage > 0 {
2107- calculatedInputTokens := int64 (upstreamContextPercentage * 200000 / 100 )
2108- if calculatedInputTokens > 0 {
2109- localEstimate := usageInfo .InputTokens
2110- usageInfo .InputTokens = calculatedInputTokens
2111- usageInfo .TotalTokens = usageInfo .InputTokens + usageInfo .OutputTokens
2112- log .Infof ("kiro: parseEventStream using contextUsagePercentage (%.2f%%) to calculate input tokens: %d (local estimate was: %d)" ,
2113- upstreamContextPercentage , calculatedInputTokens , localEstimate )
2114- }
2221+ localEstimate := usageInfo .InputTokens
2222+ if calculatedInputTokens , ok := applyKiroContextUsageFallback (& usageInfo , upstreamContextPercentage , hasPreciseTokenUsage ); ok {
2223+ log .Infof ("kiro: parseEventStream using contextUsagePercentage (%.2f%%) to calculate input tokens: %d (local estimate was: %d)" ,
2224+ upstreamContextPercentage , calculatedInputTokens , localEstimate )
21152225 }
21162226
21172227 return cleanedContent , toolUses , usageInfo , stopReason , nil
@@ -2344,6 +2454,7 @@ func (e *KiroExecutor) streamToChannel(ctx context.Context, body io.Reader, out
23442454 var upstreamCreditUsage float64 // Credit usage from upstream (e.g., 1.458)
23452455 var upstreamContextPercentage float64 // Context usage percentage from upstream (e.g., 78.56)
23462456 var hasUpstreamUsage bool // Whether we received usage from upstream
2457+ var hasPreciseTokenUsage bool // Whether official tokenUsage supplied precise counts
23472458
23482459 // Translator param for maintaining tool call state across streaming events
23492460 // IMPORTANT: This must persist across all TranslateStream calls
@@ -3165,36 +3276,14 @@ func (e *KiroExecutor) streamToChannel(ctx context.Context, body io.Reader, out
31653276
31663277 // Check for nested tokenUsage object (official format)
31673278 if tokenUsage , ok := metadata ["tokenUsage" ].(map [string ]interface {}); ok {
3168- // outputTokens - precise output token count
3169- if outputTokens , ok := tokenUsage ["outputTokens" ].(float64 ); ok {
3170- totalUsage .OutputTokens = int64 (outputTokens )
3171- hasUpstreamUsage = true
3172- log .Infof ("kiro: streamToChannel found precise outputTokens in tokenUsage: %d" , totalUsage .OutputTokens )
3173- }
3174- // totalTokens - precise total token count
3175- if totalTokens , ok := tokenUsage ["totalTokens" ].(float64 ); ok {
3176- totalUsage .TotalTokens = int64 (totalTokens )
3177- log .Infof ("kiro: streamToChannel found precise totalTokens in tokenUsage: %d" , totalUsage .TotalTokens )
3178- }
3179- // uncachedInputTokens - input tokens not from cache
3180- if uncachedInputTokens , ok := tokenUsage ["uncachedInputTokens" ].(float64 ); ok {
3181- totalUsage .InputTokens = int64 (uncachedInputTokens )
3279+ if applyKiroTokenUsage (& totalUsage , tokenUsage ) {
3280+ hasPreciseTokenUsage = true
31823281 hasUpstreamUsage = true
3183- log .Infof ("kiro: streamToChannel found uncachedInputTokens in tokenUsage: %d" , totalUsage .InputTokens )
3184- }
3185- // cacheReadInputTokens - tokens read from cache
3186- if cacheReadTokens , ok := tokenUsage ["cacheReadInputTokens" ].(float64 ); ok {
3187- // Add to input tokens if we have uncached tokens, otherwise use as input
3188- if totalUsage .InputTokens > 0 {
3189- totalUsage .InputTokens += int64 (cacheReadTokens )
3190- } else {
3191- totalUsage .InputTokens = int64 (cacheReadTokens )
3192- }
3193- hasUpstreamUsage = true
3194- log .Debugf ("kiro: streamToChannel found cacheReadInputTokens in tokenUsage: %d" , int64 (cacheReadTokens ))
3282+ log .Infof ("kiro: streamToChannel found tokenUsage input=%d cache_read=%d cache_creation=%d output=%d total=%d" ,
3283+ totalUsage .InputTokens , totalUsage .CacheReadTokens , totalUsage .CacheCreationTokens , totalUsage .OutputTokens , totalUsage .TotalTokens )
31953284 }
31963285 // contextUsagePercentage - can be used as fallback for input token estimation
3197- if ctxPct , ok := tokenUsage [ "contextUsagePercentage" ].( float64 ); ok {
3286+ if ctxPct , ok := kiroTokenUsageFloat64 ( tokenUsage , "contextUsagePercentage" ); ok {
31983287 upstreamContextPercentage = ctxPct
31993288 log .Debugf ("kiro: streamToChannel found contextUsagePercentage in tokenUsage: %.2f%%" , ctxPct )
32003289 }
@@ -3407,22 +3496,13 @@ func (e *KiroExecutor) streamToChannel(ctx context.Context, body io.Reader, out
34073496 // Kiro model has 200k max context, contextUsagePercentage represents the percentage used
34083497 // Formula: input_tokens = contextUsagePercentage * 200000 / 100
34093498 // Note: The effective input context is ~170k (200k - 30k reserved for output)
3410- if upstreamContextPercentage > 0 {
3411- // Calculate input tokens from context percentage
3412- // Using 200k as the base since that's what Kiro reports against
3413- calculatedInputTokens := int64 (upstreamContextPercentage * 200000 / 100 )
3414-
3415- // Only use calculated value if it's significantly different from local estimate
3416- // This provides more accurate token counts based on upstream data
3417- if calculatedInputTokens > 0 {
3418- localEstimate := totalUsage .InputTokens
3419- totalUsage .InputTokens = calculatedInputTokens
3420- log .Debugf ("kiro: using contextUsagePercentage (%.2f%%) to calculate input tokens: %d (local estimate was: %d)" ,
3421- upstreamContextPercentage , calculatedInputTokens , localEstimate )
3422- }
3499+ localEstimate := totalUsage .InputTokens
3500+ if calculatedInputTokens , ok := applyKiroContextUsageFallback (& totalUsage , upstreamContextPercentage , hasPreciseTokenUsage ); ok {
3501+ log .Debugf ("kiro: using contextUsagePercentage (%.2f%%) to calculate input tokens: %d (local estimate was: %d)" ,
3502+ upstreamContextPercentage , calculatedInputTokens , localEstimate )
34233503 }
34243504
3425- totalUsage . TotalTokens = totalUsage . InputTokens + totalUsage . OutputTokens
3505+ finalizeKiroUsageTotal ( & totalUsage )
34263506
34273507 // Log upstream usage information if received
34283508 if hasUpstreamUsage {
0 commit comments