ExtractJSONTokenUsage extracts token usage from JSON data
(data map[string]any)
| 89 | |
| 90 | // ExtractJSONTokenUsage extracts token usage from JSON data |
| 91 | func ExtractJSONTokenUsage(data map[string]any) int { |
| 92 | // Prefer explicit input+output sums at the top-level |
| 93 | inputTop := typeutil.ConvertToInt(data["input_tokens"]) |
| 94 | outputTop := typeutil.ConvertToInt(data["output_tokens"]) |
| 95 | if inputTop > 0 || outputTop > 0 { |
| 96 | totalTokens := inputTop + outputTop |
| 97 | if metricsLog.Enabled() { |
| 98 | metricsLog.Printf("Token usage extracted: input=%d, output=%d, total=%d", inputTop, outputTop, totalTokens) |
| 99 | } |
| 100 | return totalTokens |
| 101 | } |
| 102 | |
| 103 | // Check top-level token fields that represent a single total value |
| 104 | tokenFields := []string{"tokens", "token_count", "total_tokens"} |
| 105 | for _, field := range tokenFields { |
| 106 | if val, exists := data[field]; exists { |
| 107 | if tokens := typeutil.ConvertToInt(val); tokens > 0 { |
| 108 | return tokens |
| 109 | } |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | // Check nested usage objects (Claude and OpenAI API formats) |
| 114 | if usage, exists := data["usage"]; exists { |
| 115 | if usageMap, ok := usage.(map[string]any); ok { |
| 116 | // Claude format: {"usage": {"input_tokens": 10, "output_tokens": 5, "cache_creation_input_tokens": 100, "cache_read_input_tokens": 200}} |
| 117 | inputTokens := typeutil.ConvertToInt(usageMap["input_tokens"]) |
| 118 | outputTokens := typeutil.ConvertToInt(usageMap["output_tokens"]) |
| 119 | cacheCreationTokens := typeutil.ConvertToInt(usageMap["cache_creation_input_tokens"]) |
| 120 | cacheReadTokens := typeutil.ConvertToInt(usageMap["cache_read_input_tokens"]) |
| 121 | |
| 122 | // OpenAI format: {"usage": {"prompt_tokens": 100, "completion_tokens": 50}} |
| 123 | // If Claude fields are not present, try OpenAI fields |
| 124 | if inputTokens == 0 { |
| 125 | inputTokens = typeutil.ConvertToInt(usageMap["prompt_tokens"]) |
| 126 | } |
| 127 | if outputTokens == 0 { |
| 128 | outputTokens = typeutil.ConvertToInt(usageMap["completion_tokens"]) |
| 129 | } |
| 130 | |
| 131 | totalTokens := inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens |
| 132 | if totalTokens > 0 { |
| 133 | return totalTokens |
| 134 | } |
| 135 | |
| 136 | // Generic token count fields inside usage |
| 137 | for _, field := range tokenFields { |
| 138 | if val, exists := usageMap[field]; exists { |
| 139 | if tokens := typeutil.ConvertToInt(val); tokens > 0 { |
| 140 | return tokens |
| 141 | } |
| 142 | } |
| 143 | } |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | // Check for delta structures (streaming format) |
| 148 | if delta, exists := data["delta"]; exists { |