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>
49 lines
1.4 KiB
Go
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)
|
|
}
|