refactor(config): rename model-name-mappings to oauth-model-mappings
Unified · Split
| @@ -156,9 +156,9 @@ ws-auth: false · 155 unmodified lines | |||
| 156 | # headers: | 156 | # headers: |
| 157 | # X-Custom-Header: "custom-value" | 157 | # X-Custom-Header: "custom-value" |
| 158 | # models: # optional: map aliases to upstream model names | 158 | # models: # optional: map aliases to upstream model names |
| 159 | # - name: "gemini-2.0-flash" # upstream model name | 159 | # - name: "gemini-2.5-flash" # upstream model name |
| 160 | # alias: "vertex-flash" # client-visible alias | 160 | # alias: "vertex-flash" # client-visible alias |
| 161 | # - name: "gemini-1.5-pro" | 161 | # - name: "gemini-2.5-pro" |
| 162 | # alias: "vertex-pro" | 162 | # alias: "vertex-pro" |
| 163 | 163 | ||
| 164 | # Amp Integration | 164 | # Amp Integration |
| @@ -188,38 +188,42 @@ ws-auth: false · 23 unmodified lines | |||
| 188 | # # Useful when Amp CLI requests models you don't have access to (e.g., Claude Opus 4.5) | 188 | # # Useful when Amp CLI requests models you don't have access to (e.g., Claude Opus 4.5) |
| 189 | # # but you have a similar model available (e.g., Claude Sonnet 4). | 189 | # # but you have a similar model available (e.g., Claude Sonnet 4). |
| 190 | # model-mappings: | 190 | # model-mappings: |
| 191 | # - from: "claude-opus-4.5" # Model requested by Amp CLI | 191 | # - from: "claude-opus-4-5-20251101" # Model requested by Amp CLI |
| 192 | # to: "claude-sonnet-4" # Route to this available model instead | 192 | # to: "gemini-claude-opus-4-5-thinking" # Route to this available model instead |
| 193 | # - from: "gpt-5" | 193 | # - from: "claude-sonnet-4-5-20250929" |
| 194 | # to: "gemini-2.5-pro" | 194 | # to: "gemini-claude-sonnet-4-5-thinking" |
| 195 | # - from: "claude-3-opus-20240229" | 195 | # - from: "claude-haiku-4-5-20251001" |
| 196 | # to: "claude-3-5-sonnet-20241022" | 196 | # to: "gemini-2.5-flash" |
| 197 | 197 | ||
| 198 | # Global model name mappings (per channel) | 198 | # Global OAuth model name mappings (per channel) |
| 199 | # These mappings rename model IDs for both model listing and request routing. | 199 | # These mappings rename model IDs for both model listing and request routing. |
| 200 | # NOTE: Mappings do not apply to codex-api-key, claude-api-key, openai-compatibility, vertex-api-key, or ampcode. | 200 | # Supported channels: gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow. |
| 201 | # model-name-mappings: | 201 | # NOTE: Mappings do not apply to gemini-api-key, codex-api-key, claude-api-key, openai-compatibility, vertex-api-key, or ampcode. |
| 202 | # gemini: | 202 | # oauth-model-mappings: |
| 203 | # - from: "gemini-2.5-pro" # original model name under this channel | 203 | # gemini-cli: |
| 204 | # to: "gpt-5" # client-visible alias | 204 | # - name: "gemini-2.5-pro" # original model name under this channel |
| 205 | # apikey-gemini: | 205 | # alias: "g2.5p" # client-visible alias |
| 206 | # - from: "gemini-2.5-pro" | ||
| 207 | # to: "gpt-5" | ||
| 208 | # claude: | ||
| 209 | # - from: "claude-sonnet-4" | ||
| 210 | # to: "gpt-4o" | ||
| 211 | # vertex: | 206 | # vertex: |
| 212 | # - from: "gemini-2.5-pro" | 207 | # - name: "gemini-2.5-pro" |
| 213 | # to: "gpt-5" | 208 | # alias: "g2.5p" |
| 209 | # aistudio: | ||
| 210 | # - name: "gemini-2.5-pro" | ||
| 211 | # alias: "g2.5p" | ||
| 212 | # antigravity: | ||
| 213 | # - name: "gemini-3-pro-preview" | ||
| 214 | # alias: "g3p" | ||
| 215 | # claude: | ||
| 216 | # - name: "claude-sonnet-4-5-20250929" | ||
| 217 | # alias: "cs4.5" | ||
| 218 | # codex: | ||
| 219 | # - name: "gpt-5" | ||
| 220 | # alias: "g5" | ||
| 214 | # qwen: | 221 | # qwen: |
| 215 | # - from: "qwen3-coder-plus" | 222 | # - name: "qwen3-coder-plus" |
| 216 | # to: "gpt-4o-mini" | 223 | # alias: "qwen-plus" |
| 217 | # iflow: | 224 | # iflow: |
| 218 | # - from: "glm-4.7" | 225 | # - name: "glm-4.7" |
| 219 | # to: "gpt-5.1-mini" | 226 | # alias: "glm-god" |
| 220 | # antigravity: | ||
| 221 | # - from: "gemini-3-pro-preview" | ||
| 222 | # to: "gpt-5" | ||
| 223 | 227 | ||
| 224 | # OAuth provider excluded models | 228 | # OAuth provider excluded models |
| 225 | # oauth-excluded-models: | 229 | # oauth-excluded-models: |
| @@ -91,12 +91,13 @@ type Config struct { · 90 unmodified lines | |||
| 91 | // OAuthExcludedModels defines per-provider global model exclusions applied to OAuth/file-backed auth entries. | 91 | // OAuthExcludedModels defines per-provider global model exclusions applied to OAuth/file-backed auth entries. |
| 92 | OAuthExcludedModels map[string][]string `yaml:"oauth-excluded-models,omitempty" json:"oauth-excluded-models,omitempty"` | 92 | OAuthExcludedModels map[string][]string `yaml:"oauth-excluded-models,omitempty" json:"oauth-excluded-models,omitempty"` |
| 93 | 93 | ||
| 94 | // ModelNameMappings defines global per-channel model name mappings. | 94 | // OAuthModelMappings defines global model name mappings for OAuth/file-backed auth channels. |
| 95 | // These mappings affect both model listing and model routing for supported channels. | 95 | // These mappings affect both model listing and model routing for supported channels: |
| 96 | // gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow. | ||
| 96 | // | 97 | // |
| 97 | // NOTE: This does not apply to existing per-credential model alias features under: | 98 | // NOTE: This does not apply to existing per-credential model alias features under: |
| 98 | // codex-api-key, claude-api-key, openai-compatibility, vertex-api-key, and ampcode. | 99 | // gemini-api-key, codex-api-key, claude-api-key, openai-compatibility, vertex-api-key, and ampcode. |
| 99 | ModelNameMappings map[string][]ModelNameMapping `yaml:"model-name-mappings,omitempty" json:"model-name-mappings,omitempty"` | 100 | OAuthModelMappings map[string][]ModelNameMapping `yaml:"oauth-model-mappings,omitempty" json:"oauth-model-mappings,omitempty"` |
| 100 | 101 | ||
| 101 | // Payload defines default and override rules for provider payload parameters. | 102 | // Payload defines default and override rules for provider payload parameters. |
| 102 | Payload PayloadConfig `yaml:"payload" json:"payload"` | 103 | Payload PayloadConfig `yaml:"payload" json:"payload"` |
| @@ -145,10 +146,10 @@ type RoutingConfig struct { · 42 unmodified lines | |||
| 145 | } | 146 | } |
| 146 | 147 | ||
| 147 | // ModelNameMapping defines a model ID rename mapping for a specific channel. | 148 | // ModelNameMapping defines a model ID rename mapping for a specific channel. |
| 148 | // It maps the original model name (From) to the client-visible alias (To). | 149 | // It maps the original model name (Name) to the client-visible alias (Alias). |
| 149 | type ModelNameMapping struct { | 150 | type ModelNameMapping struct { |
| 150 | From string `yaml:"from" json:"from"` | 151 | Name string `yaml:"name" json:"name"` |
| 151 | To string `yaml:"to" json:"to"` | 152 | Alias string `yaml:"alias" json:"alias"` |
| 152 | } | 153 | } |
| 153 | 154 | ||
| 154 | // AmpModelMapping defines a model name mapping for Amp CLI requests. | 155 | // AmpModelMapping defines a model name mapping for Amp CLI requests. |
| @@ -475,8 +476,8 @@ func LoadConfigOptional(configFile string, optional bool) (*Config, error) { · 320 unmodified lines | |||
| 475 | // Normalize OAuth provider model exclusion map. | 476 | // Normalize OAuth provider model exclusion map. |
| 476 | cfg.OAuthExcludedModels = NormalizeOAuthExcludedModels(cfg.OAuthExcludedModels) | 477 | cfg.OAuthExcludedModels = NormalizeOAuthExcludedModels(cfg.OAuthExcludedModels) |
| 477 | 478 | ||
| 478 | // Normalize global model name mappings. | 479 | // Normalize global OAuth model name mappings. |
| 479 | cfg.SanitizeModelNameMappings() | 480 | cfg.SanitizeOAuthModelMappings() |
| 480 | 481 | ||
| 481 | if cfg.legacyMigrationPending { | 482 | if cfg.legacyMigrationPending { |
| 482 | fmt.Println("Detected legacy configuration keys, attempting to persist the normalized config...") | 483 | fmt.Println("Detected legacy configuration keys, attempting to persist the normalized config...") |
| @@ -494,48 +495,48 @@ func LoadConfigOptional(configFile string, optional bool) (*Config, error) { · 11 unmodified lines | |||
| 494 | return &cfg, nil | 495 | return &cfg, nil |
| 495 | } | 496 | } |
| 496 | 497 | ||
| 497 | // SanitizeModelNameMappings normalizes and deduplicates global model name mappings. | 498 | // SanitizeOAuthModelMappings normalizes and deduplicates global OAuth model name mappings. |
| 498 | // It trims whitespace, normalizes channel keys to lower-case, drops empty entries, | 499 | // It trims whitespace, normalizes channel keys to lower-case, drops empty entries, |
| 499 | // and ensures (From, To) pairs are unique within each channel. | 500 | // and ensures (From, To) pairs are unique within each channel. |
| 500 | func (cfg *Config) SanitizeModelNameMappings() { | 501 | func (cfg *Config) SanitizeOAuthModelMappings() { |
| 501 | if cfg == nil || len(cfg.ModelNameMappings) == 0 { | 502 | if cfg == nil || len(cfg.OAuthModelMappings) == 0 { |
| 502 | return | 503 | return |
| 503 | } | 504 | } |
| 504 | out := make(map[string][]ModelNameMapping, len(cfg.ModelNameMappings)) | 505 | out := make(map[string][]ModelNameMapping, len(cfg.OAuthModelMappings)) |
| 505 | for rawChannel, mappings := range cfg.ModelNameMappings { | 506 | for rawChannel, mappings := range cfg.OAuthModelMappings { |
| 506 | channel := strings.ToLower(strings.TrimSpace(rawChannel)) | 507 | channel := strings.ToLower(strings.TrimSpace(rawChannel)) |
| 507 | if channel == "" || len(mappings) == 0 { | 508 | if channel == "" || len(mappings) == 0 { |
| 508 | continue | 509 | continue |
| 509 | } | 510 | } |
| 510 | seenFrom := make(map[string]struct{}, len(mappings)) | 511 | seenName := make(map[string]struct{}, len(mappings)) |
| 511 | seenTo := make(map[string]struct{}, len(mappings)) | 512 | seenAlias := make(map[string]struct{}, len(mappings)) |
| 512 | clean := make([]ModelNameMapping, 0, len(mappings)) | 513 | clean := make([]ModelNameMapping, 0, len(mappings)) |
| 513 | for _, mapping := range mappings { | 514 | for _, mapping := range mappings { |
| 514 | from := strings.TrimSpace(mapping.From) | 515 | name := strings.TrimSpace(mapping.Name) |
| 515 | to := strings.TrimSpace(mapping.To) | 516 | alias := strings.TrimSpace(mapping.Alias) |
| 516 | if from == "" || to == "" { | 517 | if name == "" || alias == "" { |
| 517 | continue | 518 | continue |
| 518 | } | 519 | } |
| 519 | if strings.EqualFold(from, to) { | 520 | if strings.EqualFold(name, alias) { |
| 520 | continue | 521 | continue |
| 521 | } | 522 | } |
| 522 | fromKey := strings.ToLower(from) | 523 | nameKey := strings.ToLower(name) |
| 523 | toKey := strings.ToLower(to) | 524 | aliasKey := strings.ToLower(alias) |
| 524 | if _, ok := seenFrom[fromKey]; ok { | 525 | if _, ok := seenName[nameKey]; ok { |
| 525 | continue | 526 | continue |
| 526 | } | 527 | } |
| 527 | if _, ok := seenTo[toKey]; ok { | 528 | if _, ok := seenAlias[aliasKey]; ok { |
| 528 | continue | 529 | continue |
| 529 | } | 530 | } |
| 530 | seenFrom[fromKey] = struct{}{} | 531 | seenName[nameKey] = struct{}{} |
| 531 | seenTo[toKey] = struct{}{} | 532 | seenAlias[aliasKey] = struct{}{} |
| 532 | clean = append(clean, ModelNameMapping{From: from, To: to}) | 533 | clean = append(clean, ModelNameMapping{Name: name, Alias: alias}) |
| 533 | } | 534 | } |
| 534 | if len(clean) > 0 { | 535 | if len(clean) > 0 { |
| 535 | out[channel] = clean | 536 | out[channel] = clean |
| 536 | } | 537 | } |
| 537 | } | 538 | } |
| 538 | cfg.ModelNameMappings = out | 539 | cfg.OAuthModelMappings = out |
| 539 | } | 540 | } |
| 540 | 541 | ||
| 541 | // SanitizeOpenAICompatibility removes OpenAI-compatibility provider entries that are | 542 | // SanitizeOpenAICompatibility removes OpenAI-compatibility provider entries that are |
| @@ -127,7 +127,7 @@ func (w *Watcher) reloadConfig() bool { · 126 unmodified lines | |||
| 127 | } | 127 | } |
| 128 | 128 | ||
| 129 | authDirChanged := oldConfig == nil || oldConfig.AuthDir != newConfig.AuthDir | 129 | authDirChanged := oldConfig == nil || oldConfig.AuthDir != newConfig.AuthDir |
| 130 | forceAuthRefresh := oldConfig != nil && (oldConfig.ForceModelPrefix != newConfig.ForceModelPrefix || !reflect.DeepEqual(oldConfig.ModelNameMappings, newConfig.ModelNameMappings)) | 130 | forceAuthRefresh := oldConfig != nil && (oldConfig.ForceModelPrefix != newConfig.ForceModelPrefix || !reflect.DeepEqual(oldConfig.OAuthModelMappings, newConfig.OAuthModelMappings)) |
| 131 | 131 | ||
| 132 | log.Infof("config successfully reloaded, triggering client reload") | 132 | log.Infof("config successfully reloaded, triggering client reload") |
| 133 | w.reloadClients(authDirChanged, affectedOAuthProviders, forceAuthRefresh) | 133 | w.reloadClients(authDirChanged, affectedOAuthProviders, forceAuthRefresh) |
| @@ -413,7 +413,7 @@ func (m *Manager) executeWithProvider(ctx context.Context, provider string, req · 412 unmodified lines | |||
| 413 | } | 413 | } |
| 414 | execReq := req | 414 | execReq := req |
| 415 | execReq.Model, execReq.Metadata = rewriteModelForAuth(routeModel, req.Metadata, auth) | 415 | execReq.Model, execReq.Metadata = rewriteModelForAuth(routeModel, req.Metadata, auth) |
| 416 | execReq.Metadata = m.applyGlobalModelNameMappingMetadata(auth, execReq.Model, execReq.Metadata) | 416 | execReq.Metadata = m.applyOAuthModelMappingMetadata(auth, execReq.Model, execReq.Metadata) |
| 417 | resp, errExec := executor.Execute(execCtx, auth, execReq, opts) | 417 | resp, errExec := executor.Execute(execCtx, auth, execReq, opts) |
| 418 | result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil} | 418 | result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil} |
| 419 | if errExec != nil { | 419 | if errExec != nil { |
| @@ -475,7 +475,7 @@ func (m *Manager) executeCountWithProvider(ctx context.Context, provider string, · 55 unmodified lines | |||
| 475 | } | 475 | } |
| 476 | execReq := req | 476 | execReq := req |
| 477 | execReq.Model, execReq.Metadata = rewriteModelForAuth(routeModel, req.Metadata, auth) | 477 | execReq.Model, execReq.Metadata = rewriteModelForAuth(routeModel, req.Metadata, auth) |
| 478 | execReq.Metadata = m.applyGlobalModelNameMappingMetadata(auth, execReq.Model, execReq.Metadata) | 478 | execReq.Metadata = m.applyOAuthModelMappingMetadata(auth, execReq.Model, execReq.Metadata) |
| 479 | resp, errExec := executor.CountTokens(execCtx, auth, execReq, opts) | 479 | resp, errExec := executor.CountTokens(execCtx, auth, execReq, opts) |
| 480 | result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil} | 480 | result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil} |
| 481 | if errExec != nil { | 481 | if errExec != nil { |
| @@ -537,7 +537,7 @@ func (m *Manager) executeStreamWithProvider(ctx context.Context, provider string · 55 unmodified lines | |||
| 537 | } | 537 | } |
| 538 | execReq := req | 538 | execReq := req |
| 539 | execReq.Model, execReq.Metadata = rewriteModelForAuth(routeModel, req.Metadata, auth) | 539 | execReq.Model, execReq.Metadata = rewriteModelForAuth(routeModel, req.Metadata, auth) |
| 540 | execReq.Metadata = m.applyGlobalModelNameMappingMetadata(auth, execReq.Model, execReq.Metadata) | 540 | execReq.Metadata = m.applyOAuthModelMappingMetadata(auth, execReq.Model, execReq.Metadata) |
| 541 | chunks, errStream := executor.ExecuteStream(execCtx, auth, execReq, opts) | 541 | chunks, errStream := executor.ExecuteStream(execCtx, auth, execReq, opts) |
| 542 | if errStream != nil { | 542 | if errStream != nil { |
| 543 | rerr := &Error{Message: errStream.Error()} | 543 | rerr := &Error{Message: errStream.Error()} |
| @@ -26,19 +26,19 @@ func compileModelNameMappingTable(mappings map[string][]internalconfig.ModelName · 25 unmodified lines | |||
| 26 | } | 26 | } |
| 27 | rev := make(map[string]string, len(entries)) | 27 | rev := make(map[string]string, len(entries)) |
| 28 | for _, entry := range entries { | 28 | for _, entry := range entries { |
| 29 | from := strings.TrimSpace(entry.From) | 29 | name := strings.TrimSpace(entry.Name) |
| 30 | to := strings.TrimSpace(entry.To) | 30 | alias := strings.TrimSpace(entry.Alias) |
| 31 | if from == "" || to == "" { | 31 | if name == "" || alias == "" { |
| 32 | continue | 32 | continue |
| 33 | } | 33 | } |
| 34 | if strings.EqualFold(from, to) { | 34 | if strings.EqualFold(name, alias) { |
| 35 | continue | 35 | continue |
| 36 | } | 36 | } |
| 37 | aliasKey := strings.ToLower(to) | 37 | aliasKey := strings.ToLower(alias) |
| 38 | if _, exists := rev[aliasKey]; exists { | 38 | if _, exists := rev[aliasKey]; exists { |
| 39 | continue | 39 | continue |
| 40 | } | 40 | } |
| 41 | rev[aliasKey] = from | 41 | rev[aliasKey] = name |
| 42 | } | 42 | } |
| 43 | if len(rev) > 0 { | 43 | if len(rev) > 0 { |
| 44 | out.reverse[channel] = rev | 44 | out.reverse[channel] = rev |
| @@ -50,10 +50,10 @@ func compileModelNameMappingTable(mappings map[string][]internalconfig.ModelName · 5 unmodified lines | |||
| 50 | return out | 50 | return out |
| 51 | } | 51 | } |
| 52 | 52 | ||
| 53 | // SetGlobalModelNameMappings updates the global model name mapping table used during execution. | 53 | // SetOAuthModelMappings updates the OAuth model name mapping table used during execution. |
| 54 | // The mapping is applied per-auth channel to resolve the upstream model name while keeping the | 54 | // The mapping is applied per-auth channel to resolve the upstream model name while keeping the |
| 55 | // client-visible model name unchanged for translation/response formatting. | 55 | // client-visible model name unchanged for translation/response formatting. |
| 56 | func (m *Manager) SetGlobalModelNameMappings(mappings map[string][]internalconfig.ModelNameMapping) { | 56 | func (m *Manager) SetOAuthModelMappings(mappings map[string][]internalconfig.ModelNameMapping) { |
| 57 | if m == nil { | 57 | if m == nil { |
| 58 | return | 58 | return |
| 59 | } | 59 | } |
| @@ -65,8 +65,8 @@ func (m *Manager) SetGlobalModelNameMappings(mappings map[string][]internalconfi · 5 unmodified lines | |||
| 65 | m.modelNameMappings.Store(table) | 65 | m.modelNameMappings.Store(table) |
| 66 | } | 66 | } |
| 67 | 67 | ||
| 68 | func (m *Manager) applyGlobalModelNameMappingMetadata(auth *Auth, requestedModel string, metadata map[string]any) map[string]any { | 68 | func (m *Manager) applyOAuthModelMappingMetadata(auth *Auth, requestedModel string, metadata map[string]any) map[string]any { |
| 69 | original := m.resolveGlobalUpstreamModelForAuth(auth, requestedModel) | 69 | original := m.resolveOAuthUpstreamModel(auth, requestedModel) |
| 70 | if original == "" { | 70 | if original == "" { |
| 71 | return metadata | 71 | return metadata |
| 72 | } | 72 | } |
| @@ -88,11 +88,11 @@ func (m *Manager) applyGlobalModelNameMappingMetadata(auth *Auth, requestedModel · 15 unmodified lines | |||
| 88 | return out | 88 | return out |
| 89 | } | 89 | } |
| 90 | 90 | ||
| 91 | func (m *Manager) resolveGlobalUpstreamModelForAuth(auth *Auth, requestedModel string) string { | 91 | func (m *Manager) resolveOAuthUpstreamModel(auth *Auth, requestedModel string) string { |
| 92 | if m == nil || auth == nil { | 92 | if m == nil || auth == nil { |
| 93 | return "" | 93 | return "" |
| 94 | } | 94 | } |
| 95 | channel := globalModelMappingChannelForAuth(auth) | 95 | channel := modelMappingChannel(auth) |
| 96 | if channel == "" { | 96 | if channel == "" { |
| 97 | return "" | 97 | return "" |
| 98 | } | 98 | } |
| @@ -116,7 +116,10 @@ func (m *Manager) resolveGlobalUpstreamModelForAuth(auth *Auth, requestedModel s · 17 unmodified lines | |||
| 116 | return original | 116 | return original |
| 117 | } | 117 | } |
| 118 | 118 | ||
| 119 | func globalModelMappingChannelForAuth(auth *Auth) string { | 119 | // modelMappingChannel extracts the OAuth model mapping channel from an Auth object. |
| 120 | // It determines the provider and auth kind from the Auth's attributes and delegates | ||
| 121 | // to OAuthModelMappingChannel for the actual channel resolution. | ||
| 122 | func modelMappingChannel(auth *Auth) string { | ||
| 120 | if auth == nil { | 123 | if auth == nil { |
| 121 | return "" | 124 | return "" |
| 122 | } | 125 | } |
| @@ -130,32 +133,38 @@ func globalModelMappingChannelForAuth(auth *Auth) string { · 7 unmodified lines | |||
| 130 | authKind = "apikey" | 133 | authKind = "apikey" |
| 131 | } | 134 | } |
| 132 | } | 135 | } |
| 133 | return globalModelMappingChannel(provider, authKind) | 136 | return OAuthModelMappingChannel(provider, authKind) |
| 134 | } | 137 | } |
| 135 | 138 | ||
| 136 | func globalModelMappingChannel(provider, authKind string) string { | 139 | // OAuthModelMappingChannel returns the OAuth model mapping channel name for a given provider |
| 140 | // and auth kind. Returns empty string if the provider/authKind combination doesn't support | ||
| 141 | // OAuth model mappings (e.g., API key authentication). | ||
| 142 | // | ||
| 143 | // Supported channels: gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow. | ||
| 144 | func OAuthModelMappingChannel(provider, authKind string) string { | ||
| 145 | provider = strings.ToLower(strings.TrimSpace(provider)) | ||
| 146 | authKind = strings.ToLower(strings.TrimSpace(authKind)) | ||
| 137 | switch provider { | 147 | switch provider { |
| 138 | case "gemini": | 148 | case "gemini": |
| 139 | if authKind == "apikey" { | 149 | // gemini provider uses gemini-api-key config, not oauth-model-mappings. |
| 140 | return "apikey-gemini" | 150 | // OAuth-based gemini auth is converted to "gemini-cli" by the synthesizer. |
| 141 | } | 151 | return "" |
| 142 | return "gemini" | 152 | case "vertex": |
| 143 | case "codex": | ||
| 144 | if authKind == "apikey" { | 153 | if authKind == "apikey" { |
| 145 | return "" | 154 | return "" |
| 146 | } | 155 | } |
| 147 | return "codex" | 156 | return "vertex" |
| 148 | case "claude": | 157 | case "claude": |
| 149 | if authKind == "apikey" { | 158 | if authKind == "apikey" { |
| 150 | return "" | 159 | return "" |
| 151 | } | 160 | } |
| 152 | return "claude" | 161 | return "claude" |
| 153 | case "vertex": | 162 | case "codex": |
| 154 | if authKind == "apikey" { | 163 | if authKind == "apikey" { |
| 155 | return "" | 164 | return "" |
| 156 | } | 165 | } |
| 157 | return "vertex" | 166 | return "codex" |
| 158 | case "antigravity", "qwen", "iflow": | 167 | case "gemini-cli", "aistudio", "antigravity", "qwen", "iflow": |
| 159 | return provider | 168 | return provider |
| 160 | default: | 169 | default: |
| 161 | return "" | 170 | return "" |
| @@ -215,7 +215,7 @@ func (b *Builder) Build() (*Service, error) { · 214 unmodified lines | |||
| 215 | } | 215 | } |
| 216 | // Attach a default RoundTripper provider so providers can opt-in per-auth transports. | 216 | // Attach a default RoundTripper provider so providers can opt-in per-auth transports. |
| 217 | coreManager.SetRoundTripperProvider(newDefaultRoundTripperProvider()) | 217 | coreManager.SetRoundTripperProvider(newDefaultRoundTripperProvider()) |
| 218 | coreManager.SetGlobalModelNameMappings(b.cfg.ModelNameMappings) | 218 | coreManager.SetOAuthModelMappings(b.cfg.OAuthModelMappings) |
| 219 | 219 | ||
| 220 | service := &Service{ | 220 | service := &Service{ |
| 221 | cfg: b.cfg, | 221 | cfg: b.cfg, |
| @@ -553,7 +553,7 @@ func (s *Service) Run(ctx context.Context) error { · 552 unmodified lines | |||
| 553 | s.cfg = newCfg | 553 | s.cfg = newCfg |
| 554 | s.cfgMu.Unlock() | 554 | s.cfgMu.Unlock() |
| 555 | if s.coreManager != nil { | 555 | if s.coreManager != nil { |
| 556 | s.coreManager.SetGlobalModelNameMappings(newCfg.ModelNameMappings) | 556 | s.coreManager.SetOAuthModelMappings(newCfg.OAuthModelMappings) |
| 557 | } | 557 | } |
| 558 | s.rebindExecutors() | 558 | s.rebindExecutors() |
| 559 | } | 559 | } |
| @@ -844,7 +844,7 @@ func (s *Service) registerModelsForAuth(a *coreauth.Auth) { · 284 unmodified lines | |||
| 844 | } | 844 | } |
| 845 | } | 845 | } |
| 846 | } | 846 | } |
| 847 | models = applyGlobalModelNameMappings(s.cfg, provider, authKind, models) | 847 | models = applyOAuthModelMappings(s.cfg, provider, authKind, models) |
| 848 | if len(models) > 0 { | 848 | if len(models) > 0 { |
| 849 | key := provider | 849 | key := provider |
| 850 | if key == "" { | 850 | if key == "" { |
| @@ -1154,37 +1154,6 @@ func buildVertexCompatConfigModels(entry *config.VertexCompatKey) []*ModelInfo { · 303 unmodified lines | |||
| 1154 | return out | 1154 | return out |
| 1155 | } | 1155 | } |
| 1156 | 1156 | ||
| 1157 | func globalModelMappingChannel(provider, authKind string) string { | ||
| 1158 | provider = strings.ToLower(strings.TrimSpace(provider)) | ||
| 1159 | authKind = strings.ToLower(strings.TrimSpace(authKind)) | ||
| 1160 | switch provider { | ||
| 1161 | case "gemini": | ||
| 1162 | if authKind == "apikey" { | ||
| 1163 | return "apikey-gemini" | ||
| 1164 | } | ||
| 1165 | return "gemini" | ||
| 1166 | case "codex": | ||
| 1167 | if authKind == "apikey" { | ||
| 1168 | return "" | ||
| 1169 | } | ||
| 1170 | return "codex" | ||
| 1171 | case "claude": | ||
| 1172 | if authKind == "apikey" { | ||
| 1173 | return "" | ||
| 1174 | } | ||
| 1175 | return "claude" | ||
| 1176 | case "vertex": | ||
| 1177 | if authKind == "apikey" { | ||
| 1178 | return "" | ||
| 1179 | } | ||
| 1180 | return "vertex" | ||
| 1181 | case "antigravity", "qwen", "iflow": | ||
| 1182 | return provider | ||
| 1183 | default: | ||
| 1184 | return "" | ||
| 1185 | } | ||
| 1186 | } | ||
| 1187 | |||
| 1188 | func rewriteModelInfoName(name, oldID, newID string) string { | 1157 | func rewriteModelInfoName(name, oldID, newID string) string { |
| 1189 | trimmed := strings.TrimSpace(name) | 1158 | trimmed := strings.TrimSpace(name) |
| 1190 | if trimmed == "" { | 1159 | if trimmed == "" { |
| @@ -1208,33 +1177,33 @@ func rewriteModelInfoName(name, oldID, newID string) string { · 17 unmodified lines | |||
| 1208 | return name | 1177 | return name |
| 1209 | } | 1178 | } |
| 1210 | 1179 | ||
| 1211 | func applyGlobalModelNameMappings(cfg *config.Config, provider, authKind string, models []*ModelInfo) []*ModelInfo { | 1180 | func applyOAuthModelMappings(cfg *config.Config, provider, authKind string, models []*ModelInfo) []*ModelInfo { |
| 1212 | if cfg == nil || len(models) == 0 { | 1181 | if cfg == nil || len(models) == 0 { |
| 1213 | return models | 1182 | return models |
| 1214 | } | 1183 | } |
| 1215 | channel := globalModelMappingChannel(provider, authKind) | 1184 | channel := coreauth.OAuthModelMappingChannel(provider, authKind) |
| 1216 | if channel == "" || len(cfg.ModelNameMappings) == 0 { | 1185 | if channel == "" || len(cfg.OAuthModelMappings) == 0 { |
| 1217 | return models | 1186 | return models |
| 1218 | } | 1187 | } |
| 1219 | mappings := cfg.ModelNameMappings[channel] | 1188 | mappings := cfg.OAuthModelMappings[channel] |
| 1220 | if len(mappings) == 0 { | 1189 | if len(mappings) == 0 { |
| 1221 | return models | 1190 | return models |
| 1222 | } | 1191 | } |
| 1223 | forward := make(map[string]string, len(mappings)) | 1192 | forward := make(map[string]string, len(mappings)) |
| 1224 | for i := range mappings { | 1193 | for i := range mappings { |
| 1225 | from := strings.TrimSpace(mappings[i].From) | 1194 | name := strings.TrimSpace(mappings[i].Name) |
| 1226 | to := strings.TrimSpace(mappings[i].To) | 1195 | alias := strings.TrimSpace(mappings[i].Alias) |
| 1227 | if from == "" || to == "" { | 1196 | if name == "" || alias == "" { |
| 1228 | continue | 1197 | continue |
| 1229 | } | 1198 | } |
| 1230 | if strings.EqualFold(from, to) { | 1199 | if strings.EqualFold(name, alias) { |
| 1231 | continue | 1200 | continue |
| 1232 | } | 1201 | } |
| 1233 | key := strings.ToLower(from) | 1202 | key := strings.ToLower(name) |
| 1234 | if _, exists := forward[key]; exists { | 1203 | if _, exists := forward[key]; exists { |
| 1235 | continue | 1204 | continue |
| 1236 | } | 1205 | } |
| 1237 | forward[key] = to | 1206 | forward[key] = alias |
| 1238 | } | 1207 | } |
| 1239 | if len(forward) == 0 { | 1208 | if len(forward) == 0 { |
| 1240 | return models | 1209 | return models |