vibe-proxy/backend/sdk/cliproxy/auth/conductor_models.go
2026-08-24 00:10:41 +02:00

927 lines
30 KiB
Go

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
}