feat: capture Gemini cached content tokens in cost tracking
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Build (push) Has been cancelled

- Add CachedContentTokenCount to UsageMetadata parsing for both
  streaming (helpers.go) and non-streaming (gemini.go) requests
- CacheReadTokens now populated from Gemini cachedContentTokenCount
- Add uint32Ptr helper for nil-safe uint32 pointer creation
This commit is contained in:
2026-04-26 21:14:53 -04:00
parent 1c3b1c6fe9
commit 14e26a4323
3 changed files with 43 additions and 33 deletions
+1 -1
View File
@@ -2,5 +2,5 @@
"files": {}, "files": {},
"turnCycles": 0, "turnCycles": 0,
"maxCycles": 3, "maxCycles": 3,
"lastUpdated": "2026-04-27T01:09:48.183Z" "lastUpdated": "2026-04-27T01:12:44.352Z"
} }
+18 -9
View File
@@ -2,14 +2,14 @@ package providers
import ( import (
"context" "context"
"time"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/go-resty/resty/v2"
"gophergate/internal/config" "gophergate/internal/config"
"gophergate/internal/models" "gophergate/internal/models"
"github.com/go-resty/resty/v2"
) )
type GeminiProvider struct { type GeminiProvider struct {
@@ -54,10 +54,10 @@ type GeminiContent struct {
} }
type GeminiPart struct { type GeminiPart struct {
Text string `json:"text,omitempty"` Text string `json:"text,omitempty"`
InlineData *GeminiInlineData `json:"inlineData,omitempty"` InlineData *GeminiInlineData `json:"inlineData,omitempty"`
FunctionCall *GeminiFunctionCall `json:"functionCall,omitempty"` FunctionCall *GeminiFunctionCall `json:"functionCall,omitempty"`
FunctionResponse *GeminiFunctionResponse `json:"functionResponse,omitempty"` FunctionResponse *GeminiFunctionResponse `json:"functionResponse,omitempty"`
} }
type GeminiInlineData struct { type GeminiInlineData struct {
@@ -265,9 +265,10 @@ func (p *GeminiProvider) ChatCompletion(ctx context.Context, req *models.Unified
FinishReason string `json:"finishReason"` FinishReason string `json:"finishReason"`
} `json:"candidates"` } `json:"candidates"`
UsageMetadata struct { UsageMetadata struct {
PromptTokenCount uint32 `json:"promptTokenCount"` PromptTokenCount uint32 `json:"promptTokenCount"`
CandidatesTokenCount uint32 `json:"candidatesTokenCount"` CandidatesTokenCount uint32 `json:"candidatesTokenCount"`
TotalTokenCount uint32 `json:"totalTokenCount"` TotalTokenCount uint32 `json:"totalTokenCount"`
CachedContentTokenCount uint32 `json:"cachedContentTokenCount"`
} `json:"usageMetadata"` } `json:"usageMetadata"`
} }
@@ -324,6 +325,7 @@ func (p *GeminiProvider) ChatCompletion(ctx context.Context, req *models.Unified
PromptTokens: geminiResp.UsageMetadata.PromptTokenCount, PromptTokens: geminiResp.UsageMetadata.PromptTokenCount,
CompletionTokens: geminiResp.UsageMetadata.CandidatesTokenCount, CompletionTokens: geminiResp.UsageMetadata.CandidatesTokenCount,
TotalTokens: geminiResp.UsageMetadata.TotalTokenCount, TotalTokens: geminiResp.UsageMetadata.TotalTokenCount,
CacheReadTokens: uint32Ptr(geminiResp.UsageMetadata.CachedContentTokenCount),
}, },
} }
@@ -494,3 +496,10 @@ func (p *GeminiProvider) ChatCompletionStream(ctx context.Context, req *models.U
return ch, nil return ch, nil
} }
func uint32Ptr(v uint32) *uint32 {
if v > 0 {
return &v
}
return nil
}
+5 -4
View File
@@ -134,10 +134,10 @@ func BuildOpenAIBody(request *models.UnifiedRequest, messagesJSON []interface{},
} }
type openAIUsage struct { type openAIUsage struct {
PromptTokens uint32 `json:"prompt_tokens"` PromptTokens uint32 `json:"prompt_tokens"`
CompletionTokens uint32 `json:"completion_tokens"` CompletionTokens uint32 `json:"completion_tokens"`
TotalTokens uint32 `json:"total_tokens"` TotalTokens uint32 `json:"total_tokens"`
PromptTokensDetails *struct { PromptTokensDetails *struct {
CachedTokens uint32 `json:"cached_tokens"` CachedTokens uint32 `json:"cached_tokens"`
} `json:"prompt_tokens_details"` } `json:"prompt_tokens_details"`
CompletionTokensDetails *struct { CompletionTokensDetails *struct {
@@ -308,6 +308,7 @@ func StreamGemini(ctx io.ReadCloser, ch chan<- *models.ChatCompletionStreamRespo
PromptTokens: geminiChunk.UsageMetadata.PromptTokenCount, PromptTokens: geminiChunk.UsageMetadata.PromptTokenCount,
CompletionTokens: geminiChunk.UsageMetadata.CandidatesTokenCount, CompletionTokens: geminiChunk.UsageMetadata.CandidatesTokenCount,
TotalTokens: geminiChunk.UsageMetadata.TotalTokenCount, TotalTokens: geminiChunk.UsageMetadata.TotalTokenCount,
CacheReadTokens: uint32Ptr(geminiChunk.UsageMetadata.CachedContentTokenCount),
}, },
} }
} }