Spaces:
Running
Running
| package api | |
| import ( | |
| "strconv" | |
| "strings" | |
| "cpa-usage-keeper/internal/models" | |
| "cpa-usage-keeper/internal/redact" | |
| ) | |
| type usageSourceResolver struct { | |
| authIdentities map[string]models.UsageIdentity | |
| providerIdentities map[string]models.UsageIdentity | |
| providerRawByKey map[string]string | |
| } | |
| func newUsageSourceResolver(identities []models.UsageIdentity) usageSourceResolver { | |
| authIdentities := make(map[string]models.UsageIdentity, len(identities)) | |
| providerIdentities := make(map[string]models.UsageIdentity, len(identities)) | |
| providerRawByKey := make(map[string]string, len(identities)) | |
| for _, identity := range identities { | |
| key := strings.TrimSpace(identity.Identity) | |
| if key == "" { | |
| continue | |
| } | |
| switch identity.AuthType { | |
| case models.UsageIdentityAuthTypeAuthFile: | |
| authIdentities[key] = identity | |
| case models.UsageIdentityAuthTypeAIProvider: | |
| providerIdentities[key] = identity | |
| resolved := usageSourceResolutionFromIdentity(identity, key) | |
| if resolved.SourceKey != "" { | |
| providerRawByKey[resolved.SourceKey] = key | |
| } | |
| } | |
| } | |
| return usageSourceResolver{ | |
| authIdentities: authIdentities, | |
| providerIdentities: providerIdentities, | |
| providerRawByKey: providerRawByKey, | |
| } | |
| } | |
| type usageSourceResolution struct { | |
| DisplayName string | |
| SourceType string | |
| SourceKey string | |
| } | |
| func usageSourceResolutionFromIdentity(item models.UsageIdentity, fallbackIdentity string) usageSourceResolution { | |
| identityType := safeAIProviderDisplayValue(item.Type, fallbackIdentity, "") | |
| displayName := firstNonEmptyString( | |
| safeAIProviderDisplayValue(item.Name, fallbackIdentity, ""), | |
| safeAIProviderDisplayValue(item.Provider, fallbackIdentity, ""), | |
| identityType, | |
| redact.APIKeyDisplayName(fallbackIdentity), | |
| ) | |
| sourceKey := "provider:" + uintToString(item.ID) | |
| if item.ID == 0 { | |
| sourceKey = "provider:" + redact.APIKeyDisplayName(fallbackIdentity) | |
| } | |
| return usageSourceResolution{ | |
| DisplayName: displayName, | |
| SourceType: identityType, | |
| SourceKey: sourceKey, | |
| } | |
| } | |
| func (r usageSourceResolver) rawSourceForPublicValue(value string) string { | |
| trimmed := strings.TrimSpace(value) | |
| if trimmed == "" { | |
| return "" | |
| } | |
| if raw, ok := r.providerRawByKey[trimmed]; ok { | |
| return raw | |
| } | |
| return trimmed | |
| } | |
| func (r usageSourceResolver) resolve(rawSource string, authIndex string) usageSourceResolution { | |
| normalizedSource := strings.TrimSpace(rawSource) | |
| if normalizedSource != "" { | |
| if item, ok := r.providerIdentities[normalizedSource]; ok { | |
| return usageSourceResolutionFromIdentity(item, normalizedSource) | |
| } | |
| } | |
| normalizedAuthIndex := strings.TrimSpace(authIndex) | |
| if normalizedAuthIndex != "" { | |
| if identity, ok := r.authIdentities[normalizedAuthIndex]; ok { | |
| displayName := firstNonEmptyString(identity.Name, normalizedAuthIndex) | |
| return usageSourceResolution{ | |
| DisplayName: displayName, | |
| SourceType: firstNonEmptyString(identity.Type, identity.Provider), | |
| SourceKey: "auth:" + normalizedAuthIndex, | |
| } | |
| } | |
| } | |
| if normalizedSource == "" { | |
| return usageSourceResolution{DisplayName: "-", SourceKey: "raw:-"} | |
| } | |
| if looksLikeEmail(normalizedSource) { | |
| return usageSourceResolution{ | |
| DisplayName: normalizedSource, | |
| SourceKey: "email:" + normalizedSource, | |
| } | |
| } | |
| if inferredProvider := inferUsageProviderType(normalizedSource); inferredProvider != "" { | |
| return usageSourceResolution{ | |
| DisplayName: inferredProvider, | |
| SourceType: inferredProvider, | |
| SourceKey: "provider:fallback:" + inferredProvider, | |
| } | |
| } | |
| masked := redact.APIKeyDisplayName(normalizedSource) | |
| return usageSourceResolution{ | |
| DisplayName: masked, | |
| SourceKey: "raw:" + masked, | |
| } | |
| } | |
| func uintToString(value uint) string { | |
| return strconv.FormatUint(uint64(value), 10) | |
| } | |
| func firstNonEmptyString(values ...string) string { | |
| for _, value := range values { | |
| trimmed := strings.TrimSpace(value) | |
| if trimmed != "" { | |
| return trimmed | |
| } | |
| } | |
| return "" | |
| } | |
| func looksLikeEmail(value string) bool { | |
| trimmed := strings.TrimSpace(value) | |
| if trimmed == "" { | |
| return false | |
| } | |
| atIndex := strings.Index(trimmed, "@") | |
| return atIndex > 0 && atIndex < len(trimmed)-1 && strings.Contains(trimmed[atIndex+1:], ".") | |
| } | |
| func inferUsageProviderType(source string) string { | |
| value := strings.ToLower(strings.TrimSpace(source)) | |
| switch { | |
| case value == "": | |
| return "" | |
| case strings.Contains(value, "ampcode"): | |
| return "ampcode" | |
| case strings.HasPrefix(value, "sk-ant-") || strings.Contains(value, "anthropic") || strings.Contains(value, "claude"): | |
| return "claude" | |
| case strings.HasPrefix(value, "sk-proj-") || strings.HasPrefix(value, "sk-") || strings.Contains(value, "openai") || strings.Contains(value, "gpt"): | |
| return "openai" | |
| case strings.HasPrefix(value, "aiza") || strings.Contains(value, "gemini"): | |
| return "gemini" | |
| case strings.Contains(value, "vertex"): | |
| return "vertex" | |
| case strings.Contains(value, "codex"): | |
| return "codex" | |
| default: | |
| return "" | |
| } | |
| } | |