From 872339bcebcdab7a7105b97405609766249dcbd8 Mon Sep 17 00:00:00 2001 From: NguyenSiTrung Date: Wed, 24 Dec 2025 09:55:46 +0700 Subject: [PATCH 1/5] feat: add cached token parsing for Gemini API responses --- internal/runtime/executor/usage_helpers.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/runtime/executor/usage_helpers.go b/internal/runtime/executor/usage_helpers.go index 5669d9bc..0d0fa5ab 100644 --- a/internal/runtime/executor/usage_helpers.go +++ b/internal/runtime/executor/usage_helpers.go @@ -289,6 +289,7 @@ func parseGeminiCLIUsage(data []byte) usage.Detail { OutputTokens: node.Get("candidatesTokenCount").Int(), ReasoningTokens: node.Get("thoughtsTokenCount").Int(), TotalTokens: node.Get("totalTokenCount").Int(), + CachedTokens: node.Get("cachedContentTokenCount").Int(), } if detail.TotalTokens == 0 { detail.TotalTokens = detail.InputTokens + detail.OutputTokens + detail.ReasoningTokens @@ -310,6 +311,7 @@ func parseGeminiUsage(data []byte) usage.Detail { OutputTokens: node.Get("candidatesTokenCount").Int(), ReasoningTokens: node.Get("thoughtsTokenCount").Int(), TotalTokens: node.Get("totalTokenCount").Int(), + CachedTokens: node.Get("cachedContentTokenCount").Int(), } if detail.TotalTokens == 0 { detail.TotalTokens = detail.InputTokens + detail.OutputTokens + detail.ReasoningTokens @@ -334,6 +336,7 @@ func parseGeminiStreamUsage(line []byte) (usage.Detail, bool) { OutputTokens: node.Get("candidatesTokenCount").Int(), ReasoningTokens: node.Get("thoughtsTokenCount").Int(), TotalTokens: node.Get("totalTokenCount").Int(), + CachedTokens: node.Get("cachedContentTokenCount").Int(), } if detail.TotalTokens == 0 { detail.TotalTokens = detail.InputTokens + detail.OutputTokens + detail.ReasoningTokens @@ -358,6 +361,7 @@ func parseGeminiCLIStreamUsage(line []byte) (usage.Detail, bool) { OutputTokens: node.Get("candidatesTokenCount").Int(), ReasoningTokens: node.Get("thoughtsTokenCount").Int(), TotalTokens: node.Get("totalTokenCount").Int(), + CachedTokens: node.Get("cachedContentTokenCount").Int(), } if detail.TotalTokens == 0 { detail.TotalTokens = detail.InputTokens + detail.OutputTokens + detail.ReasoningTokens @@ -382,6 +386,7 @@ func parseAntigravityUsage(data []byte) usage.Detail { OutputTokens: node.Get("candidatesTokenCount").Int(), ReasoningTokens: node.Get("thoughtsTokenCount").Int(), TotalTokens: node.Get("totalTokenCount").Int(), + CachedTokens: node.Get("cachedContentTokenCount").Int(), } if detail.TotalTokens == 0 { detail.TotalTokens = detail.InputTokens + detail.OutputTokens + detail.ReasoningTokens @@ -409,6 +414,7 @@ func parseAntigravityStreamUsage(line []byte) (usage.Detail, bool) { OutputTokens: node.Get("candidatesTokenCount").Int(), ReasoningTokens: node.Get("thoughtsTokenCount").Int(), TotalTokens: node.Get("totalTokenCount").Int(), + CachedTokens: node.Get("cachedContentTokenCount").Int(), } if detail.TotalTokens == 0 { detail.TotalTokens = detail.InputTokens + detail.OutputTokens + detail.ReasoningTokens From 969c1a5b724301566a0b784133b40aefa746fcc5 Mon Sep 17 00:00:00 2001 From: NguyenSiTrung Date: Wed, 24 Dec 2025 10:22:31 +0700 Subject: [PATCH 2/5] refactor: extract parseGeminiFamilyUsageDetail helper to reduce duplication --- internal/runtime/executor/usage_helpers.go | 82 +++++----------------- 1 file changed, 18 insertions(+), 64 deletions(-) diff --git a/internal/runtime/executor/usage_helpers.go b/internal/runtime/executor/usage_helpers.go index 0d0fa5ab..78581b82 100644 --- a/internal/runtime/executor/usage_helpers.go +++ b/internal/runtime/executor/usage_helpers.go @@ -275,15 +275,7 @@ func parseClaudeStreamUsage(line []byte) (usage.Detail, bool) { return detail, true } -func parseGeminiCLIUsage(data []byte) usage.Detail { - usageNode := gjson.ParseBytes(data) - node := usageNode.Get("response.usageMetadata") - if !node.Exists() { - node = usageNode.Get("response.usage_metadata") - } - if !node.Exists() { - return usage.Detail{} - } +func parseGeminiFamilyUsageDetail(node gjson.Result) usage.Detail { detail := usage.Detail{ InputTokens: node.Get("promptTokenCount").Int(), OutputTokens: node.Get("candidatesTokenCount").Int(), @@ -297,6 +289,18 @@ func parseGeminiCLIUsage(data []byte) usage.Detail { return detail } +func parseGeminiCLIUsage(data []byte) usage.Detail { + usageNode := gjson.ParseBytes(data) + node := usageNode.Get("response.usageMetadata") + if !node.Exists() { + node = usageNode.Get("response.usage_metadata") + } + if !node.Exists() { + return usage.Detail{} + } + return parseGeminiFamilyUsageDetail(node) +} + func parseGeminiUsage(data []byte) usage.Detail { usageNode := gjson.ParseBytes(data) node := usageNode.Get("usageMetadata") @@ -306,17 +310,7 @@ func parseGeminiUsage(data []byte) usage.Detail { if !node.Exists() { return usage.Detail{} } - detail := usage.Detail{ - InputTokens: node.Get("promptTokenCount").Int(), - OutputTokens: node.Get("candidatesTokenCount").Int(), - ReasoningTokens: node.Get("thoughtsTokenCount").Int(), - TotalTokens: node.Get("totalTokenCount").Int(), - CachedTokens: node.Get("cachedContentTokenCount").Int(), - } - if detail.TotalTokens == 0 { - detail.TotalTokens = detail.InputTokens + detail.OutputTokens + detail.ReasoningTokens - } - return detail + return parseGeminiFamilyUsageDetail(node) } func parseGeminiStreamUsage(line []byte) (usage.Detail, bool) { @@ -331,17 +325,7 @@ func parseGeminiStreamUsage(line []byte) (usage.Detail, bool) { if !node.Exists() { return usage.Detail{}, false } - detail := usage.Detail{ - InputTokens: node.Get("promptTokenCount").Int(), - OutputTokens: node.Get("candidatesTokenCount").Int(), - ReasoningTokens: node.Get("thoughtsTokenCount").Int(), - TotalTokens: node.Get("totalTokenCount").Int(), - CachedTokens: node.Get("cachedContentTokenCount").Int(), - } - if detail.TotalTokens == 0 { - detail.TotalTokens = detail.InputTokens + detail.OutputTokens + detail.ReasoningTokens - } - return detail, true + return parseGeminiFamilyUsageDetail(node), true } func parseGeminiCLIStreamUsage(line []byte) (usage.Detail, bool) { @@ -356,17 +340,7 @@ func parseGeminiCLIStreamUsage(line []byte) (usage.Detail, bool) { if !node.Exists() { return usage.Detail{}, false } - detail := usage.Detail{ - InputTokens: node.Get("promptTokenCount").Int(), - OutputTokens: node.Get("candidatesTokenCount").Int(), - ReasoningTokens: node.Get("thoughtsTokenCount").Int(), - TotalTokens: node.Get("totalTokenCount").Int(), - CachedTokens: node.Get("cachedContentTokenCount").Int(), - } - if detail.TotalTokens == 0 { - detail.TotalTokens = detail.InputTokens + detail.OutputTokens + detail.ReasoningTokens - } - return detail, true + return parseGeminiFamilyUsageDetail(node), true } func parseAntigravityUsage(data []byte) usage.Detail { @@ -381,17 +355,7 @@ func parseAntigravityUsage(data []byte) usage.Detail { if !node.Exists() { return usage.Detail{} } - detail := usage.Detail{ - InputTokens: node.Get("promptTokenCount").Int(), - OutputTokens: node.Get("candidatesTokenCount").Int(), - ReasoningTokens: node.Get("thoughtsTokenCount").Int(), - TotalTokens: node.Get("totalTokenCount").Int(), - CachedTokens: node.Get("cachedContentTokenCount").Int(), - } - if detail.TotalTokens == 0 { - detail.TotalTokens = detail.InputTokens + detail.OutputTokens + detail.ReasoningTokens - } - return detail + return parseGeminiFamilyUsageDetail(node) } func parseAntigravityStreamUsage(line []byte) (usage.Detail, bool) { @@ -409,17 +373,7 @@ func parseAntigravityStreamUsage(line []byte) (usage.Detail, bool) { if !node.Exists() { return usage.Detail{}, false } - detail := usage.Detail{ - InputTokens: node.Get("promptTokenCount").Int(), - OutputTokens: node.Get("candidatesTokenCount").Int(), - ReasoningTokens: node.Get("thoughtsTokenCount").Int(), - TotalTokens: node.Get("totalTokenCount").Int(), - CachedTokens: node.Get("cachedContentTokenCount").Int(), - } - if detail.TotalTokens == 0 { - detail.TotalTokens = detail.InputTokens + detail.OutputTokens + detail.ReasoningTokens - } - return detail, true + return parseGeminiFamilyUsageDetail(node), true } var stopChunkWithoutUsage sync.Map From 6d43a2ff9a553b84830aeb25a2668c196645b085 Mon Sep 17 00:00:00 2001 From: hkfires <10558748+hkfires@users.noreply.github.com> Date: Wed, 24 Dec 2025 21:07:18 +0800 Subject: [PATCH 3/5] refactor(logging): inline request id in log output --- internal/logging/gin_logger.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/internal/logging/gin_logger.go b/internal/logging/gin_logger.go index 9bfef8ad..b45a03d7 100644 --- a/internal/logging/gin_logger.go +++ b/internal/logging/gin_logger.go @@ -73,17 +73,15 @@ func GinLogrusLogger() gin.HandlerFunc { method := c.Request.Method errorMessage := c.Errors.ByType(gin.ErrorTypePrivate).String() - logLine := fmt.Sprintf("%3d | %13v | %15s | %-7s \"%s\"", statusCode, latency, clientIP, method, path) + if requestID == "" { + requestID = "--------" + } + logLine := fmt.Sprintf("%3d | %13v | %15s | %s | %-7s \"%s\"", statusCode, latency, clientIP, requestID, method, path) if errorMessage != "" { logLine = logLine + " | " + errorMessage } - var entry *log.Entry - if requestID != "" { - entry = log.WithField("request_id", requestID) - } else { - entry = log.WithField("request_id", "--------") - } + entry := log.NewEntry(log.StandardLogger()) switch { case statusCode >= http.StatusInternalServerError: From 99238a4b5906cb0d50570c4528b7b5aed78e7b2c Mon Sep 17 00:00:00 2001 From: hkfires <10558748+hkfires@users.noreply.github.com> Date: Wed, 24 Dec 2025 21:11:37 +0800 Subject: [PATCH 4/5] fix(logging): normalize warning level to warn --- internal/logging/global_logger.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/logging/global_logger.go b/internal/logging/global_logger.go index f27f726f..6ece7240 100644 --- a/internal/logging/global_logger.go +++ b/internal/logging/global_logger.go @@ -45,7 +45,11 @@ func (m *LogFormatter) Format(entry *log.Entry) ([]byte, error) { reqID = id } - levelStr := fmt.Sprintf("%-5s", entry.Level.String()) + level := entry.Level.String() + if level == "warning" { + level = "warn" + } + levelStr := fmt.Sprintf("%-5s", level) var formatted string if reqID != "" && entry.Caller != nil { From 5ba325a8fca369f53a997f3f0afdb5c792032820 Mon Sep 17 00:00:00 2001 From: hkfires <10558748+hkfires@users.noreply.github.com> Date: Wed, 24 Dec 2025 22:03:07 +0800 Subject: [PATCH 5/5] refactor(logging): standardize request id formatting and layout --- internal/logging/gin_logger.go | 4 ++-- internal/logging/global_logger.go | 12 ++++-------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/internal/logging/gin_logger.go b/internal/logging/gin_logger.go index b45a03d7..2dfbcfc2 100644 --- a/internal/logging/gin_logger.go +++ b/internal/logging/gin_logger.go @@ -76,12 +76,12 @@ func GinLogrusLogger() gin.HandlerFunc { if requestID == "" { requestID = "--------" } - logLine := fmt.Sprintf("%3d | %13v | %15s | %s | %-7s \"%s\"", statusCode, latency, clientIP, requestID, method, path) + logLine := fmt.Sprintf("%3d | %13v | %15s | %-7s \"%s\"", statusCode, latency, clientIP, method, path) if errorMessage != "" { logLine = logLine + " | " + errorMessage } - entry := log.NewEntry(log.StandardLogger()) + entry := log.WithField("request_id", requestID) switch { case statusCode >= http.StatusInternalServerError: diff --git a/internal/logging/global_logger.go b/internal/logging/global_logger.go index 6ece7240..6ea1963a 100644 --- a/internal/logging/global_logger.go +++ b/internal/logging/global_logger.go @@ -40,7 +40,7 @@ func (m *LogFormatter) Format(entry *log.Entry) ([]byte, error) { timestamp := entry.Time.Format("2006-01-02 15:04:05") message := strings.TrimRight(entry.Message, "\r\n") - reqID := "" + reqID := "--------" if id, ok := entry.Data["request_id"].(string); ok && id != "" { reqID = id } @@ -52,14 +52,10 @@ func (m *LogFormatter) Format(entry *log.Entry) ([]byte, error) { levelStr := fmt.Sprintf("%-5s", level) var formatted string - if reqID != "" && entry.Caller != nil { - formatted = fmt.Sprintf("[%s] [%s] [%s:%d] | %s | %s\n", timestamp, levelStr, filepath.Base(entry.Caller.File), entry.Caller.Line, reqID, message) - } else if reqID != "" { - formatted = fmt.Sprintf("[%s] [%s] | %s | %s\n", timestamp, levelStr, reqID, message) - } else if entry.Caller != nil { - formatted = fmt.Sprintf("[%s] [%s] [%s:%d] %s\n", timestamp, levelStr, filepath.Base(entry.Caller.File), entry.Caller.Line, message) + if entry.Caller != nil { + formatted = fmt.Sprintf("[%s] [%s] [%s] [%s:%d] %s\n", timestamp, reqID, levelStr, filepath.Base(entry.Caller.File), entry.Caller.Line, message) } else { - formatted = fmt.Sprintf("[%s] [%s] %s\n", timestamp, levelStr, message) + formatted = fmt.Sprintf("[%s] [%s] [%s] %s\n", timestamp, reqID, levelStr, message) } buffer.WriteString(formatted)