feat: support cached input token billing

This commit is contained in:
2026-06-23 17:17:57 +08:00
parent 6089aa6085
commit 7f32446466
16 changed files with 570 additions and 45 deletions
+44 -2
View File
@@ -62,6 +62,7 @@ func (s *Service) billings(ctx context.Context, user *auth.User, kind string, bo
if isTextBillingKind(kind) {
inputTokens := response.Usage.InputTokens
outputTokens := response.Usage.OutputTokens
cachedInputTokens := response.Usage.CachedInputTokens
if isTextInputOnlyKind(kind) && inputTokens == 0 && response.Usage.TotalTokens > 0 {
inputTokens = response.Usage.TotalTokens
}
@@ -71,8 +72,37 @@ func (s *Service) billings(ctx context.Context, user *auth.User, kind string, bo
outputTokens = 1
}
}
inputAmount := roundPrice(float64(inputTokens) / 1000 * resourcePrice(config, "text", "textInputPer1k", "inputTokenPrice", "basePrice") * discount)
lines := []any{billingLine(candidate, "text_input", "1k_tokens", inputTokens, inputAmount, discount, simulated)}
if cachedInputTokens > inputTokens && inputTokens > 0 {
cachedInputTokens = inputTokens
}
uncachedInputTokens := inputTokens - cachedInputTokens
if uncachedInputTokens < 0 {
uncachedInputTokens = 0
}
inputPrice := resourcePrice(config, "text", "textInputPer1k", "inputTokenPrice", "basePrice")
cachedInputPrice := resourcePrice(config, "text", "textCachedInputPer1k", "cachedInputTokenPrice", "cacheInputTokenPrice", "textInputCacheHitPer1k", "inputCacheHitTokenPrice")
if cachedInputPrice <= 0 {
cachedInputPrice = inputPrice
}
inputAmount := roundPrice(float64(uncachedInputTokens) / 1000 * inputPrice * discount)
lines := []any{}
if uncachedInputTokens > 0 || cachedInputTokens == 0 {
lines = append(lines, billingLineWithDetails(candidate, "text_input", "1k_tokens", uncachedInputTokens, inputAmount, discount, simulated, map[string]any{
"inputTokens": inputTokens,
"uncachedInputTokens": uncachedInputTokens,
"cachedInputTokens": cachedInputTokens,
"pricePer1k": inputPrice,
}))
}
if cachedInputTokens > 0 {
cachedInputAmount := roundPrice(float64(cachedInputTokens) / 1000 * cachedInputPrice * discount)
lines = append(lines, billingLineWithDetails(candidate, "text_cached_input", "1k_tokens", cachedInputTokens, cachedInputAmount, discount, simulated, map[string]any{
"inputTokens": inputTokens,
"uncachedInputTokens": uncachedInputTokens,
"cachedInputTokens": cachedInputTokens,
"pricePer1k": cachedInputPrice,
}))
}
if isTextGenerationKind(kind) {
outputAmount := roundPrice(float64(outputTokens) / 1000 * resourcePrice(config, "text", "textOutputPer1k", "outputTokenPrice", "basePrice") * discount)
lines = append(lines, billingLine(candidate, "text_output", "1k_tokens", outputTokens, outputAmount, discount, simulated))
@@ -234,10 +264,22 @@ func resourcePrice(config map[string]any, resource string, keys ...string) float
return value
}
}
if formulaConfig, ok := resourceConfig["formulaConfig"].(map[string]any); ok {
for _, key := range keys {
if value := floatFromAny(formulaConfig[key]); value > 0 {
return value
}
}
}
if value := floatFromAny(resourceConfig["basePrice"]); value > 0 {
return value
}
}
if resource == "text" {
if value := resourcePrice(config, "text_total", keys...); value > 0 {
return value
}
}
if resource == "image_edit" {
return resourcePrice(config, "image", keys...)
}