From 7be3f1c36c6536c6991c9263d488aa56e983611a Mon Sep 17 00:00:00 2001 From: hkfires <10558748+hkfires@users.noreply.github.com> Date: Tue, 30 Dec 2025 10:14:27 +0800 Subject: [PATCH] refactor(config): rename model-name-mappings to oauth-model-mappings --- config.example.yaml | 52 +++++++++++---------- internal/config/config.go | 25 ++++++----- internal/watcher/config_reload.go | 2 +- sdk/cliproxy/auth/conductor.go | 6 +-- sdk/cliproxy/auth/model_name_mappings.go | 57 ++++++++++++++---------- sdk/cliproxy/builder.go | 2 +- sdk/cliproxy/service.go | 43 +++--------------- 7 files changed, 85 insertions(+), 102 deletions(-) diff --git a/config.example.yaml b/config.example.yaml index a7274126..db735250 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -156,9 +156,9 @@ ws-auth: false # headers: # X-Custom-Header: "custom-value" # models: # optional: map aliases to upstream model names -# - name: "gemini-2.0-flash" # upstream model name +# - name: "gemini-2.5-flash" # upstream model name # alias: "vertex-flash" # client-visible alias -# - name: "gemini-1.5-pro" +# - name: "gemini-2.5-pro" # alias: "vertex-pro" # Amp Integration @@ -188,38 +188,42 @@ ws-auth: false # # Useful when Amp CLI requests models you don't have access to (e.g., Claude Opus 4.5) # # but you have a similar model available (e.g., Claude Sonnet 4). # model-mappings: -# - from: "claude-opus-4.5" # Model requested by Amp CLI -# to: "claude-sonnet-4" # Route to this available model instead -# - from: "gpt-5" -# to: "gemini-2.5-pro" -# - from: "claude-3-opus-20240229" -# to: "claude-3-5-sonnet-20241022" +# - from: "claude-opus-4-5-20251101" # Model requested by Amp CLI +# to: "gemini-claude-opus-4-5-thinking" # Route to this available model instead +# - from: "claude-sonnet-4-5-20250929" +# to: "gemini-claude-sonnet-4-5-thinking" +# - from: "claude-haiku-4-5-20251001" +# to: "gemini-2.5-flash" -# Global model name mappings (per channel) +# Global OAuth model name mappings (per channel) # These mappings rename model IDs for both model listing and request routing. -# NOTE: Mappings do not apply to codex-api-key, claude-api-key, openai-compatibility, vertex-api-key, or ampcode. -# model-name-mappings: -# gemini: +# Supported channels: gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow. +# NOTE: Mappings do not apply to gemini-api-key, codex-api-key, claude-api-key, openai-compatibility, vertex-api-key, or ampcode. +# oauth-model-mappings: +# gemini-cli: # - from: "gemini-2.5-pro" # original model name under this channel # to: "gpt-5" # client-visible alias -# apikey-gemini: -# - from: "gemini-2.5-pro" -# to: "gpt-5" -# claude: -# - from: "claude-sonnet-4" -# to: "gpt-4o" # vertex: # - from: "gemini-2.5-pro" # to: "gpt-5" -# qwen: -# - from: "qwen3-coder-plus" -# to: "gpt-4o-mini" -# iflow: -# - from: "glm-4.7" -# to: "gpt-5.1-mini" +# aistudio: +# - from: "gemini-2.5-pro" +# to: "gpt-5" # antigravity: # - from: "gemini-3-pro-preview" # to: "gpt-5" +# claude: +# - from: "claude-sonnet-4-5-20250929" +# to: "gpt-5" +# codex: +# - from: "gpt-5" +# to: "gemini-2.5-pro" +# qwen: +# - from: "qwen3-coder-plus" +# to: "gpt-5" +# iflow: +# - from: "glm-4.7" +# to: "gpt-5" # OAuth provider excluded models # oauth-excluded-models: diff --git a/internal/config/config.go b/internal/config/config.go index 0c311b70..517b836d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -91,12 +91,13 @@ type Config struct { // OAuthExcludedModels defines per-provider global model exclusions applied to OAuth/file-backed auth entries. OAuthExcludedModels map[string][]string `yaml:"oauth-excluded-models,omitempty" json:"oauth-excluded-models,omitempty"` - // ModelNameMappings defines global per-channel model name mappings. - // These mappings affect both model listing and model routing for supported channels. + // OAuthModelMappings defines global model name mappings for OAuth/file-backed auth channels. + // These mappings affect both model listing and model routing for supported channels: + // gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow. // // NOTE: This does not apply to existing per-credential model alias features under: - // codex-api-key, claude-api-key, openai-compatibility, vertex-api-key, and ampcode. - ModelNameMappings map[string][]ModelNameMapping `yaml:"model-name-mappings,omitempty" json:"model-name-mappings,omitempty"` + // gemini-api-key, codex-api-key, claude-api-key, openai-compatibility, vertex-api-key, and ampcode. + OAuthModelMappings map[string][]ModelNameMapping `yaml:"oauth-model-mappings,omitempty" json:"oauth-model-mappings,omitempty"` // Payload defines default and override rules for provider payload parameters. Payload PayloadConfig `yaml:"payload" json:"payload"` @@ -475,8 +476,8 @@ func LoadConfigOptional(configFile string, optional bool) (*Config, error) { // Normalize OAuth provider model exclusion map. cfg.OAuthExcludedModels = NormalizeOAuthExcludedModels(cfg.OAuthExcludedModels) - // Normalize global model name mappings. - cfg.SanitizeModelNameMappings() + // Normalize global OAuth model name mappings. + cfg.SanitizeOAuthModelMappings() if cfg.legacyMigrationPending { fmt.Println("Detected legacy configuration keys, attempting to persist the normalized config...") @@ -494,15 +495,15 @@ func LoadConfigOptional(configFile string, optional bool) (*Config, error) { return &cfg, nil } -// SanitizeModelNameMappings normalizes and deduplicates global model name mappings. +// SanitizeOAuthModelMappings normalizes and deduplicates global OAuth model name mappings. // It trims whitespace, normalizes channel keys to lower-case, drops empty entries, // and ensures (From, To) pairs are unique within each channel. -func (cfg *Config) SanitizeModelNameMappings() { - if cfg == nil || len(cfg.ModelNameMappings) == 0 { +func (cfg *Config) SanitizeOAuthModelMappings() { + if cfg == nil || len(cfg.OAuthModelMappings) == 0 { return } - out := make(map[string][]ModelNameMapping, len(cfg.ModelNameMappings)) - for rawChannel, mappings := range cfg.ModelNameMappings { + out := make(map[string][]ModelNameMapping, len(cfg.OAuthModelMappings)) + for rawChannel, mappings := range cfg.OAuthModelMappings { channel := strings.ToLower(strings.TrimSpace(rawChannel)) if channel == "" || len(mappings) == 0 { continue @@ -535,7 +536,7 @@ func (cfg *Config) SanitizeModelNameMappings() { out[channel] = clean } } - cfg.ModelNameMappings = out + cfg.OAuthModelMappings = out } // SanitizeOpenAICompatibility removes OpenAI-compatibility provider entries that are diff --git a/internal/watcher/config_reload.go b/internal/watcher/config_reload.go index 4db93fc8..370ee4e1 100644 --- a/internal/watcher/config_reload.go +++ b/internal/watcher/config_reload.go @@ -127,7 +127,7 @@ func (w *Watcher) reloadConfig() bool { } authDirChanged := oldConfig == nil || oldConfig.AuthDir != newConfig.AuthDir - forceAuthRefresh := oldConfig != nil && (oldConfig.ForceModelPrefix != newConfig.ForceModelPrefix || !reflect.DeepEqual(oldConfig.ModelNameMappings, newConfig.ModelNameMappings)) + forceAuthRefresh := oldConfig != nil && (oldConfig.ForceModelPrefix != newConfig.ForceModelPrefix || !reflect.DeepEqual(oldConfig.OAuthModelMappings, newConfig.OAuthModelMappings)) log.Infof("config successfully reloaded, triggering client reload") w.reloadClients(authDirChanged, affectedOAuthProviders, forceAuthRefresh) diff --git a/sdk/cliproxy/auth/conductor.go b/sdk/cliproxy/auth/conductor.go index 125966fd..a6eaf3c5 100644 --- a/sdk/cliproxy/auth/conductor.go +++ b/sdk/cliproxy/auth/conductor.go @@ -413,7 +413,7 @@ func (m *Manager) executeWithProvider(ctx context.Context, provider string, req } execReq := req execReq.Model, execReq.Metadata = rewriteModelForAuth(routeModel, req.Metadata, auth) - execReq.Metadata = m.applyGlobalModelNameMappingMetadata(auth, execReq.Model, execReq.Metadata) + execReq.Metadata = m.applyOAuthModelMappingMetadata(auth, execReq.Model, execReq.Metadata) resp, errExec := executor.Execute(execCtx, auth, execReq, opts) result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil} if errExec != nil { @@ -475,7 +475,7 @@ func (m *Manager) executeCountWithProvider(ctx context.Context, provider string, } execReq := req execReq.Model, execReq.Metadata = rewriteModelForAuth(routeModel, req.Metadata, auth) - execReq.Metadata = m.applyGlobalModelNameMappingMetadata(auth, execReq.Model, execReq.Metadata) + execReq.Metadata = m.applyOAuthModelMappingMetadata(auth, execReq.Model, execReq.Metadata) resp, errExec := executor.CountTokens(execCtx, auth, execReq, opts) result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil} if errExec != nil { @@ -537,7 +537,7 @@ func (m *Manager) executeStreamWithProvider(ctx context.Context, provider string } execReq := req execReq.Model, execReq.Metadata = rewriteModelForAuth(routeModel, req.Metadata, auth) - execReq.Metadata = m.applyGlobalModelNameMappingMetadata(auth, execReq.Model, execReq.Metadata) + execReq.Metadata = m.applyOAuthModelMappingMetadata(auth, execReq.Model, execReq.Metadata) chunks, errStream := executor.ExecuteStream(execCtx, auth, execReq, opts) if errStream != nil { rerr := &Error{Message: errStream.Error()} diff --git a/sdk/cliproxy/auth/model_name_mappings.go b/sdk/cliproxy/auth/model_name_mappings.go index 99215fc4..e7238f67 100644 --- a/sdk/cliproxy/auth/model_name_mappings.go +++ b/sdk/cliproxy/auth/model_name_mappings.go @@ -50,10 +50,10 @@ func compileModelNameMappingTable(mappings map[string][]internalconfig.ModelName return out } -// SetGlobalModelNameMappings updates the global model name mapping table used during execution. +// SetOAuthModelMappings updates the OAuth model name mapping table used during execution. // The mapping is applied per-auth channel to resolve the upstream model name while keeping the // client-visible model name unchanged for translation/response formatting. -func (m *Manager) SetGlobalModelNameMappings(mappings map[string][]internalconfig.ModelNameMapping) { +func (m *Manager) SetOAuthModelMappings(mappings map[string][]internalconfig.ModelNameMapping) { if m == nil { return } @@ -65,8 +65,8 @@ func (m *Manager) SetGlobalModelNameMappings(mappings map[string][]internalconfi m.modelNameMappings.Store(table) } -func (m *Manager) applyGlobalModelNameMappingMetadata(auth *Auth, requestedModel string, metadata map[string]any) map[string]any { - original := m.resolveGlobalUpstreamModelForAuth(auth, requestedModel) +func (m *Manager) applyOAuthModelMappingMetadata(auth *Auth, requestedModel string, metadata map[string]any) map[string]any { + original := m.resolveOAuthUpstreamModel(auth, requestedModel) if original == "" { return metadata } @@ -88,11 +88,11 @@ func (m *Manager) applyGlobalModelNameMappingMetadata(auth *Auth, requestedModel return out } -func (m *Manager) resolveGlobalUpstreamModelForAuth(auth *Auth, requestedModel string) string { +func (m *Manager) resolveOAuthUpstreamModel(auth *Auth, requestedModel string) string { if m == nil || auth == nil { return "" } - channel := globalModelMappingChannelForAuth(auth) + channel := modelMappingChannel(auth) if channel == "" { return "" } @@ -116,7 +116,10 @@ func (m *Manager) resolveGlobalUpstreamModelForAuth(auth *Auth, requestedModel s return original } -func globalModelMappingChannelForAuth(auth *Auth) string { +// modelMappingChannel extracts the OAuth model mapping channel from an Auth object. +// It determines the provider and auth kind from the Auth's attributes and delegates +// to OAuthModelMappingChannel for the actual channel resolution. +func modelMappingChannel(auth *Auth) string { if auth == nil { return "" } @@ -130,32 +133,38 @@ func globalModelMappingChannelForAuth(auth *Auth) string { authKind = "apikey" } } - return globalModelMappingChannel(provider, authKind) + return OAuthModelMappingChannel(provider, authKind) } -func globalModelMappingChannel(provider, authKind string) string { +// OAuthModelMappingChannel returns the OAuth model mapping channel name for a given provider +// and auth kind. Returns empty string if the provider/authKind combination doesn't support +// OAuth model mappings (e.g., API key authentication). +// +// Supported channels: gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow. +func OAuthModelMappingChannel(provider, authKind string) string { + provider = strings.ToLower(strings.TrimSpace(provider)) + authKind = strings.ToLower(strings.TrimSpace(authKind)) switch provider { case "gemini": - if authKind == "apikey" { - return "apikey-gemini" - } - return "gemini" - case "codex": - if authKind == "apikey" { - return "" - } - return "codex" - case "claude": - if authKind == "apikey" { - return "" - } - return "claude" + // gemini provider uses gemini-api-key config, not oauth-model-mappings. + // OAuth-based gemini auth is converted to "gemini-cli" by the synthesizer. + return "" case "vertex": if authKind == "apikey" { return "" } return "vertex" - case "antigravity", "qwen", "iflow": + case "claude": + if authKind == "apikey" { + return "" + } + return "claude" + case "codex": + if authKind == "apikey" { + return "" + } + return "codex" + case "gemini-cli", "aistudio", "antigravity", "qwen", "iflow": return provider default: return "" diff --git a/sdk/cliproxy/builder.go b/sdk/cliproxy/builder.go index ce0517b2..51d5dbac 100644 --- a/sdk/cliproxy/builder.go +++ b/sdk/cliproxy/builder.go @@ -215,7 +215,7 @@ func (b *Builder) Build() (*Service, error) { } // Attach a default RoundTripper provider so providers can opt-in per-auth transports. coreManager.SetRoundTripperProvider(newDefaultRoundTripperProvider()) - coreManager.SetGlobalModelNameMappings(b.cfg.ModelNameMappings) + coreManager.SetOAuthModelMappings(b.cfg.OAuthModelMappings) service := &Service{ cfg: b.cfg, diff --git a/sdk/cliproxy/service.go b/sdk/cliproxy/service.go index a31f3b11..89f5b3c2 100644 --- a/sdk/cliproxy/service.go +++ b/sdk/cliproxy/service.go @@ -553,7 +553,7 @@ func (s *Service) Run(ctx context.Context) error { s.cfg = newCfg s.cfgMu.Unlock() if s.coreManager != nil { - s.coreManager.SetGlobalModelNameMappings(newCfg.ModelNameMappings) + s.coreManager.SetOAuthModelMappings(newCfg.OAuthModelMappings) } s.rebindExecutors() } @@ -844,7 +844,7 @@ func (s *Service) registerModelsForAuth(a *coreauth.Auth) { } } } - models = applyGlobalModelNameMappings(s.cfg, provider, authKind, models) + models = applyOAuthModelMappings(s.cfg, provider, authKind, models) if len(models) > 0 { key := provider if key == "" { @@ -1154,37 +1154,6 @@ func buildVertexCompatConfigModels(entry *config.VertexCompatKey) []*ModelInfo { return out } -func globalModelMappingChannel(provider, authKind string) string { - provider = strings.ToLower(strings.TrimSpace(provider)) - authKind = strings.ToLower(strings.TrimSpace(authKind)) - switch provider { - case "gemini": - if authKind == "apikey" { - return "apikey-gemini" - } - return "gemini" - case "codex": - if authKind == "apikey" { - return "" - } - return "codex" - case "claude": - if authKind == "apikey" { - return "" - } - return "claude" - case "vertex": - if authKind == "apikey" { - return "" - } - return "vertex" - case "antigravity", "qwen", "iflow": - return provider - default: - return "" - } -} - func rewriteModelInfoName(name, oldID, newID string) string { trimmed := strings.TrimSpace(name) if trimmed == "" { @@ -1208,15 +1177,15 @@ func rewriteModelInfoName(name, oldID, newID string) string { return name } -func applyGlobalModelNameMappings(cfg *config.Config, provider, authKind string, models []*ModelInfo) []*ModelInfo { +func applyOAuthModelMappings(cfg *config.Config, provider, authKind string, models []*ModelInfo) []*ModelInfo { if cfg == nil || len(models) == 0 { return models } - channel := globalModelMappingChannel(provider, authKind) - if channel == "" || len(cfg.ModelNameMappings) == 0 { + channel := coreauth.OAuthModelMappingChannel(provider, authKind) + if channel == "" || len(cfg.OAuthModelMappings) == 0 { return models } - mappings := cfg.ModelNameMappings[channel] + mappings := cfg.OAuthModelMappings[channel] if len(mappings) == 0 { return models }