History

Merge pull request #782 from router-for-me/modelmappings

refactor(config): rename model-name-mappings to oauth-model-mappings

Luis Pater committed 10 months agov6.6.69

Commit 9fa2a7e2 parents f6ab6d9 d443c867 files · +112 −129Browse files

Unified · Split

7 files
  1. config.example.yaml +33 −29
  2. internal/config/config.go +29 −28
  3. internal/watcher/config_reload.go +1 −1
  4. sdk/cliproxy/auth/conductor.go +3 −3
  5. sdk/cliproxy/auth/model_name_mappings.go +33 −24
  6. sdk/cliproxy/builder.go +1 −1
  7. sdk/cliproxy/service.go +12 −43
config.example.yaml+33−29
@@ -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 names158# models: # optional: map aliases to upstream model names
159# - name: "gemini-2.0-flash" # upstream model name159# - name: "gemini-2.5-flash" # upstream model name
160# alias: "vertex-flash" # client-visible alias160# 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 Integration164# 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 CLI191# - from: "claude-opus-4-5-20251101" # Model requested by Amp CLI
192# to: "claude-sonnet-4" # Route to this available model instead192# 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 channel203# gemini-cli:
204# to: "gpt-5" # client-visible alias204# - 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 models228# OAuth provider excluded models
225# oauth-excluded-models:229# oauth-excluded-models:
internal/config/config.go+29−28
@@ -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).
149type ModelNameMapping struct {150type 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, nil495 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.
500func (cfg *Config) SanitizeModelNameMappings() {501func (cfg *Config) SanitizeOAuthModelMappings() {
501 if cfg == nil || len(cfg.ModelNameMappings) == 0 {502 if cfg == nil || len(cfg.OAuthModelMappings) == 0 {
502 return503 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 continue509 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 continue518 continue
518 }519 }
519 if strings.EqualFold(from, to) {520 if strings.EqualFold(name, alias) {
520 continue521 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 continue526 continue
526 }527 }
527 if _, ok := seenTo[toKey]; ok {528 if _, ok := seenAlias[aliasKey]; ok {
528 continue529 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] = clean536 out[channel] = clean
536 }537 }
537 }538 }
538 cfg.ModelNameMappings = out539 cfg.OAuthModelMappings = out
539}540}
540 541
541// SanitizeOpenAICompatibility removes OpenAI-compatibility provider entries that are542// SanitizeOpenAICompatibility removes OpenAI-compatibility provider entries that are
internal/watcher/config_reload.go+1−1
@@ -127,7 +127,7 @@ func (w *Watcher) reloadConfig() bool { · 126 unmodified lines
127 }127 }
128 128
129 authDirChanged := oldConfig == nil || oldConfig.AuthDir != newConfig.AuthDir129 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)
sdk/cliproxy/auth/conductor.go+3−3
@@ -413,7 +413,7 @@ func (m *Manager) executeWithProvider(ctx context.Context, provider string, req · 412 unmodified lines
413 }413 }
414 execReq := req414 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 := req476 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 := req538 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()}
sdk/cliproxy/auth/model_name_mappings.go+33−24
@@ -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 continue32 continue
33 }33 }
34 if strings.EqualFold(from, to) {34 if strings.EqualFold(name, alias) {
35 continue35 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 continue39 continue
40 }40 }
41 rev[aliasKey] = from41 rev[aliasKey] = name
42 }42 }
43 if len(rev) > 0 {43 if len(rev) > 0 {
44 out.reverse[channel] = rev44 out.reverse[channel] = rev
@@ -50,10 +50,10 @@ func compileModelNameMappingTable(mappings map[string][]internalconfig.ModelName · 5 unmodified lines
50 return out50 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 the54// 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.
56func (m *Manager) SetGlobalModelNameMappings(mappings map[string][]internalconfig.ModelNameMapping) {56func (m *Manager) SetOAuthModelMappings(mappings map[string][]internalconfig.ModelNameMapping) {
57 if m == nil {57 if m == nil {
58 return58 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
68func (m *Manager) applyGlobalModelNameMappingMetadata(auth *Auth, requestedModel string, metadata map[string]any) map[string]any {68func (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 metadata71 return metadata
72 }72 }
@@ -88,11 +88,11 @@ func (m *Manager) applyGlobalModelNameMappingMetadata(auth *Auth, requestedModel · 15 unmodified lines
88 return out88 return out
89}89}
90 90
91func (m *Manager) resolveGlobalUpstreamModelForAuth(auth *Auth, requestedModel string) string {91func (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 original116 return original
117}117}
118 118
119func 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.
122func 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
136func 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.
144func 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 provider168 return provider
160 default:169 default:
161 return ""170 return ""
sdk/cliproxy/builder.go+1−1
@@ -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,
sdk/cliproxy/service.go+12−43
@@ -553,7 +553,7 @@ func (s *Service) Run(ctx context.Context) error { · 552 unmodified lines
553 s.cfg = newCfg553 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 := provider849 key := provider
850 if key == "" {850 if key == "" {
@@ -1154,37 +1154,6 @@ func buildVertexCompatConfigModels(entry *config.VertexCompatKey) []*ModelInfo { · 303 unmodified lines
1154 return out1154 return out
1155}1155}
1156 1156
1157func 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
1188func rewriteModelInfoName(name, oldID, newID string) string {1157func 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 name1177 return name
1209}1178}
1210 1179
1211func applyGlobalModelNameMappings(cfg *config.Config, provider, authKind string, models []*ModelInfo) []*ModelInfo {1180func 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 models1182 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 models1186 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 models1190 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 continue1197 continue
1229 }1198 }
1230 if strings.EqualFold(from, to) {1199 if strings.EqualFold(name, alias) {
1231 continue1200 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 continue1204 continue
1236 }1205 }
1237 forward[key] = to1206 forward[key] = alias
1238 }1207 }
1239 if len(forward) == 0 {1208 if len(forward) == 0 {
1240 return models1209 return models