package auth import ( "bytes" "strconv" "strings" "time" internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config" "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking" "github.com/router-for-me/CLIProxyAPI/v7/internal/util" cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor" ) func (m *Manager) lookupAPIKeyUpstreamModel(authID, requestedModel string) string { return lookupAPIKeyUpstreamModel(m.loadAPIKeyModelRouting(), authID, requestedModel) } func lookupAPIKeyUpstreamModel(routing *apiKeyModelRoutingSnapshot, authID, requestedModel string) string { if routing == nil { return "" } authID = strings.TrimSpace(authID) if authID == "" { return "" } requestedModel = strings.TrimSpace(requestedModel) if requestedModel == "" { return "" } byAlias := routing.aliases[authID] if len(byAlias) == 0 { return "" } keys := []string{strings.ToLower(requestedModel)} baseKey := strings.ToLower(strings.TrimSpace(thinking.ParseSuffix(requestedModel).ModelName)) if baseKey != "" && baseKey != keys[0] { keys = append(keys, baseKey) } for _, key := range keys { if resolved := strings.TrimSpace(byAlias[key]); resolved != "" { return preserveRequestedModelSuffix(requestedModel, resolved) } } return "" } func isAPIKeyAuth(auth *Auth) bool { if auth == nil { return false } return auth.AuthKind() == AuthKindAPIKey } func isConfiguredOpenAICompatAuth(auth *Auth) bool { if !isConfiguredModelRoutingAuth(auth) { return false } if strings.EqualFold(strings.TrimSpace(auth.Provider), "openai-compatibility") { return true } if auth.Attributes == nil { return false } return strings.TrimSpace(auth.Attributes["compat_name"]) != "" } func openAICompatProviderKey(auth *Auth) string { if auth == nil { return "" } if auth.Attributes != nil { if providerKey := strings.TrimSpace(auth.Attributes["provider_key"]); providerKey != "" { return util.OpenAICompatibleProviderKey(providerKey) } if compatName := strings.TrimSpace(auth.Attributes["compat_name"]); compatName != "" { return util.OpenAICompatibleProviderKey(compatName) } } return util.OpenAICompatibleProviderKey(auth.Provider) } func openAICompatModelPoolKey(auth *Auth, requestedModel string) string { base := strings.TrimSpace(thinking.ParseSuffix(requestedModel).ModelName) if base == "" { base = strings.TrimSpace(requestedModel) } return strings.ToLower(strings.TrimSpace(auth.ID)) + "|" + openAICompatProviderKey(auth) + "|" + strings.ToLower(base) } func (m *Manager) nextModelPoolOffset(key string, size int) int { if m == nil || size <= 1 { return 0 } key = strings.TrimSpace(key) if key == "" { return 0 } m.mu.Lock() defer m.mu.Unlock() if m.modelPoolOffsets == nil { m.modelPoolOffsets = make(map[string]int) } offset := m.modelPoolOffsets[key] if offset >= 2_147_483_640 { offset = 0 } m.modelPoolOffsets[key] = offset + 1 if size <= 0 { return 0 } return offset % size } func rotateStrings(values []string, offset int) []string { if len(values) <= 1 { return values } if offset <= 0 { out := make([]string, len(values)) copy(out, values) return out } offset = offset % len(values) out := make([]string, 0, len(values)) out = append(out, values[offset:]...) out = append(out, values[:offset]...) return out } func (m *Manager) resolveOpenAICompatUpstreamModelPool(auth *Auth, requestedModel string) []string { return resolveOpenAICompatUpstreamModelPool(m.loadAPIKeyModelRouting().config, auth, requestedModel) } func resolveOpenAICompatUpstreamModelPool(cfg *internalconfig.Config, auth *Auth, requestedModel string) []string { if !isConfiguredOpenAICompatAuth(auth) { return nil } requestedModel = strings.TrimSpace(requestedModel) if requestedModel == "" { return nil } if cfg == nil { cfg = &internalconfig.Config{} } providerKey := "" compatName := "" if auth.Attributes != nil { providerKey = strings.TrimSpace(auth.Attributes["provider_key"]) compatName = strings.TrimSpace(auth.Attributes["compat_name"]) } entry := resolveOpenAICompatConfigForAuth(cfg, auth, providerKey, compatName) if entry == nil { return nil } return resolveModelAliasPoolFromConfigModels(requestedModel, asModelAliasEntries(entry.Models)) } func preserveRequestedModelSuffix(requestedModel, resolved string) string { return preserveResolvedModelSuffix(resolved, thinking.ParseSuffix(requestedModel)) } func (m *Manager) executionModelCandidates(auth *Auth, routeModel string) []string { if auth != nil && auth.Attributes != nil { if homeModel := strings.TrimSpace(auth.Attributes[homeUpstreamModelAttributeKey]); homeModel != "" { return []string{homeModel} } } requestedModel := rewriteModelForAuth(routeModel, auth) requestedModel = m.applyOAuthModelAlias(auth, requestedModel) if pool := m.resolveOpenAICompatUpstreamModelPool(auth, requestedModel); len(pool) > 0 { if len(pool) == 1 { return pool } offset := m.nextModelPoolOffset(openAICompatModelPoolKey(auth, requestedModel), len(pool)) return rotateStrings(pool, offset) } resolved := m.applyAPIKeyModelAlias(auth, requestedModel) if strings.TrimSpace(resolved) == "" { resolved = requestedModel } return []string{resolved} } // ResolveExecutionModel returns the credential-aware upstream model used by // normal execution. It strips auth prefixes, applies configured aliases, and // prefers Home-dispatched upstream models when present. func (m *Manager) ResolveExecutionModel(auth *Auth, routeModel string) string { routeModel = strings.TrimSpace(routeModel) if m == nil { return routeModel } candidates := m.executionModelCandidates(auth, routeModel) if len(candidates) == 0 { return routeModel } if resolved := strings.TrimSpace(candidates[0]); resolved != "" { return resolved } return routeModel } func (m *Manager) selectionModelForAuth(auth *Auth, routeModel string) string { requestedModel := rewriteModelForAuth(routeModel, auth) if strings.TrimSpace(requestedModel) == "" { requestedModel = strings.TrimSpace(routeModel) } resolvedModel := m.applyOAuthModelAlias(auth, requestedModel) if strings.TrimSpace(resolvedModel) == "" { resolvedModel = requestedModel } return resolvedModel } func (m *Manager) selectionModelKeyForAuth(auth *Auth, routeModel string) string { return canonicalModelKey(m.selectionModelForAuth(auth, routeModel)) } func (m *Manager) stateModelForExecution(auth *Auth, routeModel, upstreamModel string, pooled bool) string { if auth != nil && auth.Attributes != nil { if homeModel := strings.TrimSpace(auth.Attributes[homeUpstreamModelAttributeKey]); homeModel != "" { if resolved := strings.TrimSpace(upstreamModel); resolved != "" { return resolved } return homeModel } } stateModel := executionResultModel(routeModel, upstreamModel, pooled) selectionModel := m.selectionModelForAuth(auth, routeModel) if canonicalModelKey(selectionModel) == canonicalModelKey(upstreamModel) && strings.TrimSpace(selectionModel) != "" { return strings.TrimSpace(upstreamModel) } return stateModel } func executionResultModel(routeModel, upstreamModel string, pooled bool) string { if pooled { if resolved := strings.TrimSpace(upstreamModel); resolved != "" { return resolved } } if requested := strings.TrimSpace(routeModel); requested != "" { return requested } return strings.TrimSpace(upstreamModel) } func (m *Manager) filterExecutionModels(auth *Auth, routeModel string, candidates []string, pooled bool) []string { if len(candidates) == 0 { return nil } now := time.Now() out := make([]string, 0, len(candidates)) for _, upstreamModel := range candidates { stateModel := m.stateModelForExecution(auth, routeModel, upstreamModel, pooled) blocked, _, _ := isAuthBlockedForModel(auth, stateModel, now) if blocked { continue } out = append(out, upstreamModel) } return out } func (m *Manager) preparedExecutionModels(auth *Auth, routeModel string) ([]string, bool) { candidates := m.executionModelCandidates(auth, routeModel) pooled := len(candidates) > 1 return m.filterExecutionModels(auth, routeModel, candidates, pooled), pooled } func (m *Manager) preparedExecutionModelsWithAlias(auth *Auth, routeModel string) ([]string, bool, OAuthModelAliasResult, *apiKeyModelRoutingSnapshot) { candidates, pooled, aliasResult, routing := m.executionModelCandidatesWithAlias(auth, routeModel) return m.filterExecutionModels(auth, routeModel, candidates, pooled), pooled, aliasResult, routing } func (m *Manager) executionModelCandidatesWithAlias(auth *Auth, routeModel string) ([]string, bool, OAuthModelAliasResult, *apiKeyModelRoutingSnapshot) { routing := m.loadAPIKeyModelRouting() requestedModel := rewriteModelForAuth(routeModel, auth) aliasResult := m.resolveExecutionAliasResultForRequestedWithRouting(routing, auth, requestedModel) if aliasResult.ForceMapping && auth != nil && auth.Attributes != nil && strings.EqualFold(strings.TrimSpace(auth.Attributes[homeForceMappingAttributeKey]), "true") { aliasResult.OriginalAlias = strings.TrimSpace(routeModel) } upstreamModel := executionAliasPoolModel(auth, requestedModel, aliasResult) var candidates []string if auth != nil && auth.Attributes != nil { if homeModel := strings.TrimSpace(auth.Attributes[homeUpstreamModelAttributeKey]); homeModel != "" { candidates = []string{homeModel} } } if len(candidates) == 0 { if pool := resolveOpenAICompatUpstreamModelPool(routing.config, auth, upstreamModel); len(pool) > 0 { if len(pool) == 1 { candidates = pool } else { offset := m.nextModelPoolOffset(openAICompatModelPoolKey(auth, upstreamModel), len(pool)) candidates = rotateStrings(pool, offset) } } else { resolved := m.applyAPIKeyModelAliasWithRouting(routing, auth, upstreamModel) if strings.TrimSpace(resolved) == "" { resolved = upstreamModel } candidates = []string{resolved} } } pooled := len(candidates) > 1 return candidates, pooled, aliasResult, routing } func (m *Manager) resolveExecutionAliasResult(auth *Auth, routeModel string) OAuthModelAliasResult { requestedModel := rewriteModelForAuth(routeModel, auth) return m.resolveExecutionAliasResultForRequested(auth, requestedModel) } func (m *Manager) resolveExecutionAliasResultForRequested(auth *Auth, requestedModel string) OAuthModelAliasResult { return m.resolveExecutionAliasResultForRequestedWithRouting(m.loadAPIKeyModelRouting(), auth, requestedModel) } func (m *Manager) resolveExecutionAliasResultForRequestedWithRouting(routing *apiKeyModelRoutingSnapshot, auth *Auth, requestedModel string) OAuthModelAliasResult { if result := homeForceMappingAliasResult(auth, requestedModel); result.ForceMapping { return result } if isConfiguredModelRoutingAuth(auth) { return resolveAPIKeyModelAliasWithResult(routing.config, auth, requestedModel) } return m.applyOAuthModelAliasWithResult(auth, requestedModel) } func homeForceMappingAliasResult(auth *Auth, requestedModel string) OAuthModelAliasResult { if auth == nil || auth.Attributes == nil || !strings.EqualFold(strings.TrimSpace(auth.Attributes[homeForceMappingAttributeKey]), "true") { return OAuthModelAliasResult{} } originalAlias := strings.TrimSpace(auth.Attributes[homeOriginalAliasAttributeKey]) canonicalOriginalAlias := canonicalHomeConcurrencyModelKey(auth.Attributes[homeOriginalAliasAttributeKey]) canonicalRequestedModel := canonicalHomeConcurrencyModelKey(requestedModel) if canonicalOriginalAlias == "" || canonicalOriginalAlias != canonicalRequestedModel { return OAuthModelAliasResult{} } upstreamModel := strings.TrimSpace(auth.Attributes[homeUpstreamModelAttributeKey]) if upstreamModel == "" { upstreamModel = strings.TrimSpace(requestedModel) } return OAuthModelAliasResult{ UpstreamModel: upstreamModel, ForceMapping: true, OriginalAlias: originalAlias, } } func executionAliasPoolModel(auth *Auth, requestedModel string, aliasResult OAuthModelAliasResult) string { if isConfiguredModelRoutingAuth(auth) { if strings.TrimSpace(requestedModel) != "" { return requestedModel } } if strings.TrimSpace(aliasResult.UpstreamModel) != "" { return aliasResult.UpstreamModel } return requestedModel } func (m *Manager) resolveAPIKeyModelAliasWithResult(auth *Auth, requestedModel string) OAuthModelAliasResult { return resolveAPIKeyModelAliasWithResult(m.loadAPIKeyModelRouting().config, auth, requestedModel) } func resolveAPIKeyModelAliasWithResult(cfg *internalconfig.Config, auth *Auth, requestedModel string) OAuthModelAliasResult { if auth == nil { return OAuthModelAliasResult{} } requestedModel = strings.TrimSpace(requestedModel) if requestedModel == "" { return OAuthModelAliasResult{} } if cfg == nil { cfg = &internalconfig.Config{} } models := configuredModelAliasEntries(cfg, auth) if len(models) == 0 { return OAuthModelAliasResult{UpstreamModel: requestedModel} } result := resolveModelAliasResultFromConfigModels(requestedModel, models) if strings.TrimSpace(result.UpstreamModel) == "" { return OAuthModelAliasResult{UpstreamModel: requestedModel} } return result } func configuredModelAliasEntries(cfg *internalconfig.Config, auth *Auth) []modelAliasEntry { if cfg == nil || auth == nil { return nil } provider := strings.ToLower(strings.TrimSpace(auth.Provider)) var models []modelAliasEntry switch provider { case "gemini": if entry := resolveGeminiAPIKeyConfig(cfg, auth); entry != nil { models = asModelAliasEntries(entry.Models) } case "gemini-interactions": if entry := resolveInteractionsAPIKeyConfig(cfg, auth); entry != nil { models = asModelAliasEntries(entry.Models) } case "claude": if entry := resolveClaudeAPIKeyConfig(cfg, auth); entry != nil { models = asModelAliasEntries(entry.Models) } case "codex": if entry := resolveCodexAPIKeyConfig(cfg, auth); entry != nil { models = asModelAliasEntries(entry.Models) } case "xai": if entry := resolveXAIAPIKeyConfig(cfg, auth); entry != nil { models = asModelAliasEntries(entry.Models) } case "vertex": if entry := resolveVertexAPIKeyConfig(cfg, auth); entry != nil { models = asModelAliasEntries(entry.Models) } default: providerKey := "" compatName := "" if auth.Attributes != nil { providerKey = strings.TrimSpace(auth.Attributes["provider_key"]) compatName = strings.TrimSpace(auth.Attributes["compat_name"]) } if compatName != "" || strings.EqualFold(strings.TrimSpace(auth.Provider), "openai-compatibility") { if entry := resolveOpenAICompatConfigForAuth(cfg, auth, providerKey, compatName); entry != nil { models = asModelAliasEntries(entry.Models) } } } return models } func resolveModelAliasResultForUpstream(cfg *internalconfig.Config, auth *Auth, requestedModel, upstreamModel string) OAuthModelAliasResult { requestedModel = strings.TrimSpace(requestedModel) upstreamModel = strings.TrimSpace(upstreamModel) if requestedModel == "" || upstreamModel == "" { return OAuthModelAliasResult{} } requestResult := thinking.ParseSuffix(requestedModel) models := configuredModelAliasEntries(cfg, auth) filtered := make([]modelAliasEntry, 0, 1) for _, model := range models { name := strings.TrimSpace(model.GetName()) if name != "" && strings.EqualFold(preserveResolvedModelSuffix(name, requestResult), upstreamModel) { filtered = append(filtered, model) } } if len(filtered) == 0 { return OAuthModelAliasResult{} } return resolveModelAliasResultFromConfigModels(requestedModel, filtered) } func resolveAttemptAliasResult(routing *apiKeyModelRoutingSnapshot, auth *Auth, routeModel, upstreamModel string, fallback OAuthModelAliasResult) OAuthModelAliasResult { if routing == nil || !isConfiguredModelRoutingAuth(auth) { return fallback } requestedModel := rewriteModelForAuth(routeModel, auth) result := resolveModelAliasResultForUpstream(routing.config, auth, requestedModel, upstreamModel) if strings.TrimSpace(result.UpstreamModel) == "" { return fallback } if result.ForceMapping && fallback.ForceMapping && strings.TrimSpace(fallback.OriginalAlias) != "" { result.OriginalAlias = fallback.OriginalAlias } return result } func (m *Manager) prepareExecutionModels(auth *Auth, routeModel string) []string { models, _ := m.preparedExecutionModels(auth, routeModel) return models } func rewriteForceMappedResponse(resp *cliproxyexecutor.Response, aliasResult OAuthModelAliasResult) { if resp == nil || !aliasResult.ForceMapping || strings.TrimSpace(aliasResult.OriginalAlias) == "" { return } resp.Payload = rewriteModelInResponse(resp.Payload, aliasResult.OriginalAlias) } func rewriteForceMappedStreamChunk(rewriter *StreamRewriter, payload []byte) []byte { if rewriter == nil || len(payload) == 0 { return payload } rewritten := rewriter.RewriteChunk(payload) if len(rewritten) > 0 { return rewritten } if bytes.Contains(payload, []byte("data:")) { if lineWise := rewriteSSEPayloadLines(payload, rewriter.options.RewriteModel); len(lineWise) > 0 { return lineWise } } if len(rewriter.pendingBuf) > 0 { return nil } return nil } func finishForceMappedStreamChunks(rewriter *StreamRewriter) []byte { if rewriter == nil { return nil } return rewriter.Finish() } func (m *Manager) rebuildAPIKeyModelAliasFromRuntimeConfig() { if m == nil { return } m.mu.Lock() defer m.mu.Unlock() cfg, _ := m.runtimeConfig.Load().(*internalconfig.Config) if cfg == nil { cfg = &internalconfig.Config{} } m.rebuildAPIKeyModelAliasLocked(cfg) } // RefreshAPIKeyModelAlias rebuilds the API-key model alias table from the current runtime config. func (m *Manager) RefreshAPIKeyModelAlias() { m.rebuildAPIKeyModelAliasFromRuntimeConfig() } func (m *Manager) rebuildAPIKeyModelAliasLocked(cfg *internalconfig.Config) { if m == nil { return } if cfg == nil { cfg = &internalconfig.Config{} } out := make(apiKeyModelAliasTable) capabilities := make(apiKeyModelCapabilityTable) for _, auth := range m.auths { if auth == nil { continue } if strings.TrimSpace(auth.ID) == "" { continue } if !isConfiguredModelRoutingAuth(auth) { continue } byAlias := make(map[string]string) provider := strings.ToLower(strings.TrimSpace(auth.Provider)) switch provider { case "gemini": if entry := resolveGeminiAPIKeyConfig(cfg, auth); entry != nil { compileAPIKeyModelAliasForModels(byAlias, entry.Models) } case "gemini-interactions": if entry := resolveInteractionsAPIKeyConfig(cfg, auth); entry != nil { compileAPIKeyModelAliasForModels(byAlias, entry.Models) } case "claude": if entry := resolveClaudeAPIKeyConfig(cfg, auth); entry != nil { compileAPIKeyModelAliasForModels(byAlias, entry.Models) } case "codex": if entry := resolveCodexAPIKeyConfig(cfg, auth); entry != nil { compileAPIKeyModelAliasForModels(byAlias, entry.Models) } case "xai": if entry := resolveXAIAPIKeyConfig(cfg, auth); entry != nil { compileAPIKeyModelAliasForModels(byAlias, entry.Models) } case "vertex": if entry := resolveVertexAPIKeyConfig(cfg, auth); entry != nil { compileAPIKeyModelAliasForModels(byAlias, entry.Models) } default: // OpenAI-compat uses config selection from auth.Attributes. providerKey := "" compatName := "" if auth.Attributes != nil { providerKey = strings.TrimSpace(auth.Attributes["provider_key"]) compatName = strings.TrimSpace(auth.Attributes["compat_name"]) } if compatName != "" || strings.EqualFold(strings.TrimSpace(auth.Provider), "openai-compatibility") { if entry := resolveOpenAICompatConfigForAuth(cfg, auth, providerKey, compatName); entry != nil { compileAPIKeyModelAliasForModels(byAlias, entry.Models) } } } if len(byAlias) > 0 { out[auth.ID] = byAlias } if byCapability := compileAPIKeyModelCapabilitiesForAuth(cfg, auth); len(byCapability) > 0 { capabilities[auth.ID] = byCapability } } m.apiKeyModelRouting.Store(&apiKeyModelRoutingSnapshot{ config: cfg, aliases: out, capabilities: capabilities, }) } func compileAPIKeyModelAliasForModels[T interface { GetName() string GetAlias() string }](out map[string]string, models []T) { if out == nil { return } add := func(key, name string) { key = strings.ToLower(strings.TrimSpace(key)) if key == "" { return } if _, exists := out[key]; !exists { out[key] = name } } for i := range models { alias := strings.TrimSpace(models[i].GetAlias()) name := strings.TrimSpace(models[i].GetName()) if alias == "" || name == "" { continue } // Exact suffix routes are retained alongside first-entry base fallbacks. add(alias, name) add(thinking.ParseSuffix(alias).ModelName, name) // Direct upstream requests use the same exact-first lookup behavior. add(name, name) add(thinking.ParseSuffix(name).ModelName, name) } } func rewriteModelForAuth(model string, auth *Auth) string { if auth == nil || model == "" { return model } prefix := strings.TrimSpace(auth.Prefix) if prefix == "" { return model } needle := prefix + "/" if !strings.HasPrefix(model, needle) { return model } return strings.TrimPrefix(model, needle) } func (m *Manager) applyAPIKeyModelAlias(auth *Auth, requestedModel string) string { return m.applyAPIKeyModelAliasWithRouting(m.loadAPIKeyModelRouting(), auth, requestedModel) } func (m *Manager) applyAPIKeyModelAliasWithRouting(routing *apiKeyModelRoutingSnapshot, auth *Auth, requestedModel string) string { if auth == nil { return requestedModel } if auth.AuthKind() != AuthKindAPIKey { return requestedModel } requestedModel = strings.TrimSpace(requestedModel) if requestedModel == "" { return requestedModel } // Fast path: lookup per-auth mapping table (keyed by auth.ID). if resolved := lookupAPIKeyUpstreamModel(routing, auth.ID, requestedModel); resolved != "" { return resolved } // Slow path: scan the same config snapshot used to compile the alias table. cfg := routing.config if cfg == nil { cfg = &internalconfig.Config{} } provider := strings.ToLower(strings.TrimSpace(auth.Provider)) upstreamModel := "" switch provider { case "gemini": upstreamModel = resolveUpstreamModelForGeminiAPIKey(cfg, auth, requestedModel) case "gemini-interactions": upstreamModel = resolveUpstreamModelForInteractionsAPIKey(cfg, auth, requestedModel) case "claude": upstreamModel = resolveUpstreamModelForClaudeAPIKey(cfg, auth, requestedModel) case "codex": upstreamModel = resolveUpstreamModelForCodexAPIKey(cfg, auth, requestedModel) case "xai": upstreamModel = resolveUpstreamModelForXAIAPIKey(cfg, auth, requestedModel) case "vertex": upstreamModel = resolveUpstreamModelForVertexAPIKey(cfg, auth, requestedModel) default: upstreamModel = resolveUpstreamModelForOpenAICompatAPIKey(cfg, auth, requestedModel) } // Return upstream model if found, otherwise return requested model. if upstreamModel != "" { return upstreamModel } return requestedModel } // APIKeyConfigEntry is a generic interface for API key configurations. type APIKeyConfigEntry interface { GetAPIKey() string GetBaseURL() string GetPrefix() string GetProxyURL() string } func resolveAPIKeyConfig[T APIKeyConfigEntry](entries []T, auth *Auth) *T { if auth == nil || len(entries) == 0 { return nil } attrKey, attrBase := "", "" if auth.Attributes != nil { attrKey = strings.TrimSpace(auth.Attributes[AttributeAPIKey]) attrBase = strings.TrimSpace(auth.Attributes["base_url"]) } matchesCredentials := func(entry T) bool { cfgKey := strings.TrimSpace(entry.GetAPIKey()) cfgBase := strings.TrimSpace(entry.GetBaseURL()) if attrKey != "" && attrBase != "" { return strings.EqualFold(cfgKey, attrKey) && strings.EqualFold(cfgBase, attrBase) } if attrKey != "" { return strings.EqualFold(cfgKey, attrKey) && (cfgBase == "" || strings.EqualFold(cfgBase, attrBase)) } return attrBase != "" && strings.EqualFold(cfgBase, attrBase) } if auth.AuthSourceKind() == AuthSourceConfig && auth.Attributes != nil { if index, errIndex := strconv.Atoi(strings.TrimSpace(auth.Attributes[AttributeConfigIndex])); errIndex == nil && index >= 0 && index < len(entries) && matchesCredentials(entries[index]) { return &entries[index] } } for i := range entries { entry := entries[i] if matchesCredentials(entry) && strings.EqualFold(strings.TrimSpace(entry.GetPrefix()), strings.TrimSpace(auth.Prefix)) && strings.EqualFold(strings.TrimSpace(entry.GetProxyURL()), strings.TrimSpace(auth.ProxyURL)) { return &entries[i] } } for i := range entries { if matchesCredentials(entries[i]) { return &entries[i] } } if attrKey != "" { for i := range entries { if strings.EqualFold(strings.TrimSpace(entries[i].GetAPIKey()), attrKey) { return &entries[i] } } } return nil } func resolveGeminiAPIKeyConfig(cfg *internalconfig.Config, auth *Auth) *internalconfig.GeminiKey { if cfg == nil { return nil } return resolveAPIKeyConfig(cfg.GeminiKey, auth) } func resolveInteractionsAPIKeyConfig(cfg *internalconfig.Config, auth *Auth) *internalconfig.GeminiKey { if cfg == nil { return nil } return resolveAPIKeyConfig(cfg.InteractionsKey, auth) } func resolveClaudeAPIKeyConfig(cfg *internalconfig.Config, auth *Auth) *internalconfig.ClaudeKey { if cfg == nil { return nil } return resolveAPIKeyConfig(cfg.ClaudeKey, auth) } func resolveCodexAPIKeyConfig(cfg *internalconfig.Config, auth *Auth) *internalconfig.CodexKey { if cfg == nil { return nil } return resolveAPIKeyConfig(cfg.CodexKey, auth) } func resolveXAIAPIKeyConfig(cfg *internalconfig.Config, auth *Auth) *internalconfig.XAIKey { if cfg == nil { return nil } return resolveAPIKeyConfig(cfg.XAIKey, auth) } func resolveVertexAPIKeyConfig(cfg *internalconfig.Config, auth *Auth) *internalconfig.VertexCompatKey { if cfg == nil { return nil } return resolveAPIKeyConfig(cfg.VertexCompatAPIKey, auth) } func resolveUpstreamModelForGeminiAPIKey(cfg *internalconfig.Config, auth *Auth, requestedModel string) string { entry := resolveGeminiAPIKeyConfig(cfg, auth) if entry == nil { return "" } return resolveModelAliasFromConfigModels(requestedModel, asModelAliasEntries(entry.Models)) } func resolveUpstreamModelForInteractionsAPIKey(cfg *internalconfig.Config, auth *Auth, requestedModel string) string { entry := resolveInteractionsAPIKeyConfig(cfg, auth) if entry == nil { return "" } return resolveModelAliasFromConfigModels(requestedModel, asModelAliasEntries(entry.Models)) } func resolveUpstreamModelForClaudeAPIKey(cfg *internalconfig.Config, auth *Auth, requestedModel string) string { entry := resolveClaudeAPIKeyConfig(cfg, auth) if entry == nil { return "" } return resolveModelAliasFromConfigModels(requestedModel, asModelAliasEntries(entry.Models)) } func resolveUpstreamModelForCodexAPIKey(cfg *internalconfig.Config, auth *Auth, requestedModel string) string { entry := resolveCodexAPIKeyConfig(cfg, auth) if entry == nil { return "" } return resolveModelAliasFromConfigModels(requestedModel, asModelAliasEntries(entry.Models)) } func resolveUpstreamModelForXAIAPIKey(cfg *internalconfig.Config, auth *Auth, requestedModel string) string { entry := resolveXAIAPIKeyConfig(cfg, auth) if entry == nil { return "" } return resolveModelAliasFromConfigModels(requestedModel, asModelAliasEntries(entry.Models)) } func resolveUpstreamModelForVertexAPIKey(cfg *internalconfig.Config, auth *Auth, requestedModel string) string { entry := resolveVertexAPIKeyConfig(cfg, auth) if entry == nil { return "" } return resolveModelAliasFromConfigModels(requestedModel, asModelAliasEntries(entry.Models)) } func resolveUpstreamModelForOpenAICompatAPIKey(cfg *internalconfig.Config, auth *Auth, requestedModel string) string { providerKey := "" compatName := "" if auth != nil && len(auth.Attributes) > 0 { providerKey = strings.TrimSpace(auth.Attributes["provider_key"]) compatName = strings.TrimSpace(auth.Attributes["compat_name"]) } if compatName == "" && !strings.EqualFold(strings.TrimSpace(auth.Provider), "openai-compatibility") { return "" } entry := resolveOpenAICompatConfigForAuth(cfg, auth, providerKey, compatName) if entry == nil { return "" } return resolveModelAliasFromConfigModels(requestedModel, asModelAliasEntries(entry.Models)) } type apiKeyModelAliasTable map[string]map[string]string func resolveOpenAICompatConfigForAuth(cfg *internalconfig.Config, auth *Auth, providerKey, compatName string) *internalconfig.OpenAICompatibility { if cfg == nil { return nil } if auth != nil && auth.AuthSourceKind() == AuthSourceConfig && auth.Attributes != nil { if index, errIndex := strconv.Atoi(strings.TrimSpace(auth.Attributes[AttributeConfigIndex])); errIndex == nil && index >= 0 && index < len(cfg.OpenAICompatibility) && !cfg.OpenAICompatibility[index].Disabled { return &cfg.OpenAICompatibility[index] } } authProvider := "" if auth != nil { authProvider = auth.Provider } return resolveOpenAICompatConfig(cfg, providerKey, compatName, authProvider) } func resolveOpenAICompatConfig(cfg *internalconfig.Config, providerKey, compatName, authProvider string) *internalconfig.OpenAICompatibility { if cfg == nil { return nil } candidates := make([]string, 0, 3) if v := strings.TrimSpace(compatName); v != "" { candidates = append(candidates, v) } if v := strings.TrimSpace(providerKey); v != "" { candidates = append(candidates, v) } if v := strings.TrimSpace(authProvider); v != "" { candidates = append(candidates, v) } for i := range cfg.OpenAICompatibility { compat := &cfg.OpenAICompatibility[i] if compat.Disabled { continue } for _, candidate := range candidates { if candidate != "" && strings.EqualFold(strings.TrimSpace(candidate), compat.Name) { return compat } } } return nil } func asModelAliasEntries[T interface { GetName() string GetAlias() string GetForceMapping() bool }](models []T) []modelAliasEntry { if len(models) == 0 { return nil } out := make([]modelAliasEntry, 0, len(models)) for i := range models { out = append(out, models[i]) } return out }