Files
openteam/server/internal/proxy/usage.go
T
SakurasanandClaude Sonnet 5 d0e31b198f feat(server): API relay gateway backend M0-M4
Gin + GORM + pure-Go SQLite. Users/auth (JWT), API key management with
quotas, proxy gateway with weighted channel failover and health checks,
usage/billing ledger, cross-protocol conversion (Anthropic Messages /
OpenAI Chat Completions / OpenAI Responses), and channel/model admin API.
Channels declare native API formats and auto-convert the rest.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 21:05:02 +08:00

49 lines
1.4 KiB
Go

package proxy
import (
"encoding/json"
"openteam/server/internal/proxy/claude"
"openteam/server/internal/proxy/openai"
"openteam/server/internal/proxy/responses"
)
// parseUsageForProtocol extracts token usage from a non-stream response body.
func parseUsageForProtocol(proto Protocol, body []byte) *tokenUsage {
switch proto {
case ProtocolAnthropic:
var mr claude.MessageResponse
if err := json.Unmarshal(body, &mr); err != nil || mr.Usage == nil {
return nil
}
return &tokenUsage{
input: mr.Usage.InputTokens,
output: mr.Usage.OutputTokens,
cacheRead: mr.Usage.CacheReadInputTokens,
cacheCreation: mr.Usage.CacheCreationInputTokens,
}
case ProtocolOpenAIResponses:
var r responses.Response
if err := json.Unmarshal(body, &r); err != nil || r.Usage == nil {
return nil
}
return &tokenUsage{
input: r.Usage.InputTokens,
output: r.Usage.OutputTokens,
cacheRead: r.Usage.InputTokensDetails.CachedTokens,
}
default:
var cc openai.ChatCompletion
if err := json.Unmarshal(body, &cc); err != nil || cc.Usage == nil {
return nil
}
return &tokenUsage{input: cc.Usage.PromptTokens, output: cc.Usage.CompletionTokens}
}
}
// estimateTokensFromText is a coarse fallback used when the upstream omits usage.
func estimateTokensFromText(s string) int64 {
// ~4 chars per token, per OpenAI's common heuristic.
return int64(len(s)/4 + 1)
}