mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-19 12:50:51 +08:00
refactor(config): rename oauth-model-mappings to oauth-model-alias
This commit is contained in:
@@ -201,12 +201,12 @@ nonstream-keepalive-interval: 0
|
|||||||
# - from: "claude-haiku-4-5-20251001"
|
# - from: "claude-haiku-4-5-20251001"
|
||||||
# to: "gemini-2.5-flash"
|
# to: "gemini-2.5-flash"
|
||||||
|
|
||||||
# Global OAuth model name mappings (per channel)
|
# Global OAuth model name aliases (per channel)
|
||||||
# These mappings rename model IDs for both model listing and request routing.
|
# These aliases rename model IDs for both model listing and request routing.
|
||||||
# Supported channels: gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow.
|
# 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.
|
# NOTE: Aliases do not apply to gemini-api-key, codex-api-key, claude-api-key, openai-compatibility, vertex-api-key, or ampcode.
|
||||||
# You can repeat the same name with different aliases to expose multiple client model names.
|
# You can repeat the same name with different aliases to expose multiple client model names.
|
||||||
oauth-model-mappings:
|
oauth-model-alias:
|
||||||
antigravity:
|
antigravity:
|
||||||
- name: "rev19-uic3-1p"
|
- name: "rev19-uic3-1p"
|
||||||
alias: "gemini-2.5-computer-use-preview-10-2025"
|
alias: "gemini-2.5-computer-use-preview-10-2025"
|
||||||
|
|||||||
@@ -703,21 +703,21 @@ func (h *Handler) DeleteOAuthExcludedModels(c *gin.Context) {
|
|||||||
h.persist(c)
|
h.persist(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// oauth-model-mappings: map[string][]ModelNameMapping
|
// oauth-model-alias: map[string][]OAuthModelAlias
|
||||||
func (h *Handler) GetOAuthModelMappings(c *gin.Context) {
|
func (h *Handler) GetOAuthModelAlias(c *gin.Context) {
|
||||||
c.JSON(200, gin.H{"oauth-model-mappings": sanitizedOAuthModelMappings(h.cfg.OAuthModelMappings)})
|
c.JSON(200, gin.H{"oauth-model-alias": sanitizedOAuthModelAlias(h.cfg.OAuthModelAlias)})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) PutOAuthModelMappings(c *gin.Context) {
|
func (h *Handler) PutOAuthModelAlias(c *gin.Context) {
|
||||||
data, err := c.GetRawData()
|
data, err := c.GetRawData()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(400, gin.H{"error": "failed to read body"})
|
c.JSON(400, gin.H{"error": "failed to read body"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var entries map[string][]config.ModelNameMapping
|
var entries map[string][]config.OAuthModelAlias
|
||||||
if err = json.Unmarshal(data, &entries); err != nil {
|
if err = json.Unmarshal(data, &entries); err != nil {
|
||||||
var wrapper struct {
|
var wrapper struct {
|
||||||
Items map[string][]config.ModelNameMapping `json:"items"`
|
Items map[string][]config.OAuthModelAlias `json:"items"`
|
||||||
}
|
}
|
||||||
if err2 := json.Unmarshal(data, &wrapper); err2 != nil {
|
if err2 := json.Unmarshal(data, &wrapper); err2 != nil {
|
||||||
c.JSON(400, gin.H{"error": "invalid body"})
|
c.JSON(400, gin.H{"error": "invalid body"})
|
||||||
@@ -725,15 +725,15 @@ func (h *Handler) PutOAuthModelMappings(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
entries = wrapper.Items
|
entries = wrapper.Items
|
||||||
}
|
}
|
||||||
h.cfg.OAuthModelMappings = sanitizedOAuthModelMappings(entries)
|
h.cfg.OAuthModelAlias = sanitizedOAuthModelAlias(entries)
|
||||||
h.persist(c)
|
h.persist(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) PatchOAuthModelMappings(c *gin.Context) {
|
func (h *Handler) PatchOAuthModelAlias(c *gin.Context) {
|
||||||
var body struct {
|
var body struct {
|
||||||
Provider *string `json:"provider"`
|
Provider *string `json:"provider"`
|
||||||
Channel *string `json:"channel"`
|
Channel *string `json:"channel"`
|
||||||
Mappings []config.ModelNameMapping `json:"mappings"`
|
Aliases []config.OAuthModelAlias `json:"aliases"`
|
||||||
}
|
}
|
||||||
if errBindJSON := c.ShouldBindJSON(&body); errBindJSON != nil {
|
if errBindJSON := c.ShouldBindJSON(&body); errBindJSON != nil {
|
||||||
c.JSON(400, gin.H{"error": "invalid body"})
|
c.JSON(400, gin.H{"error": "invalid body"})
|
||||||
@@ -751,32 +751,32 @@ func (h *Handler) PatchOAuthModelMappings(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
normalizedMap := sanitizedOAuthModelMappings(map[string][]config.ModelNameMapping{channel: body.Mappings})
|
normalizedMap := sanitizedOAuthModelAlias(map[string][]config.OAuthModelAlias{channel: body.Aliases})
|
||||||
normalized := normalizedMap[channel]
|
normalized := normalizedMap[channel]
|
||||||
if len(normalized) == 0 {
|
if len(normalized) == 0 {
|
||||||
if h.cfg.OAuthModelMappings == nil {
|
if h.cfg.OAuthModelAlias == nil {
|
||||||
c.JSON(404, gin.H{"error": "channel not found"})
|
c.JSON(404, gin.H{"error": "channel not found"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, ok := h.cfg.OAuthModelMappings[channel]; !ok {
|
if _, ok := h.cfg.OAuthModelAlias[channel]; !ok {
|
||||||
c.JSON(404, gin.H{"error": "channel not found"})
|
c.JSON(404, gin.H{"error": "channel not found"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
delete(h.cfg.OAuthModelMappings, channel)
|
delete(h.cfg.OAuthModelAlias, channel)
|
||||||
if len(h.cfg.OAuthModelMappings) == 0 {
|
if len(h.cfg.OAuthModelAlias) == 0 {
|
||||||
h.cfg.OAuthModelMappings = nil
|
h.cfg.OAuthModelAlias = nil
|
||||||
}
|
}
|
||||||
h.persist(c)
|
h.persist(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if h.cfg.OAuthModelMappings == nil {
|
if h.cfg.OAuthModelAlias == nil {
|
||||||
h.cfg.OAuthModelMappings = make(map[string][]config.ModelNameMapping)
|
h.cfg.OAuthModelAlias = make(map[string][]config.OAuthModelAlias)
|
||||||
}
|
}
|
||||||
h.cfg.OAuthModelMappings[channel] = normalized
|
h.cfg.OAuthModelAlias[channel] = normalized
|
||||||
h.persist(c)
|
h.persist(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) DeleteOAuthModelMappings(c *gin.Context) {
|
func (h *Handler) DeleteOAuthModelAlias(c *gin.Context) {
|
||||||
channel := strings.ToLower(strings.TrimSpace(c.Query("channel")))
|
channel := strings.ToLower(strings.TrimSpace(c.Query("channel")))
|
||||||
if channel == "" {
|
if channel == "" {
|
||||||
channel = strings.ToLower(strings.TrimSpace(c.Query("provider")))
|
channel = strings.ToLower(strings.TrimSpace(c.Query("provider")))
|
||||||
@@ -785,17 +785,17 @@ func (h *Handler) DeleteOAuthModelMappings(c *gin.Context) {
|
|||||||
c.JSON(400, gin.H{"error": "missing channel"})
|
c.JSON(400, gin.H{"error": "missing channel"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if h.cfg.OAuthModelMappings == nil {
|
if h.cfg.OAuthModelAlias == nil {
|
||||||
c.JSON(404, gin.H{"error": "channel not found"})
|
c.JSON(404, gin.H{"error": "channel not found"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, ok := h.cfg.OAuthModelMappings[channel]; !ok {
|
if _, ok := h.cfg.OAuthModelAlias[channel]; !ok {
|
||||||
c.JSON(404, gin.H{"error": "channel not found"})
|
c.JSON(404, gin.H{"error": "channel not found"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
delete(h.cfg.OAuthModelMappings, channel)
|
delete(h.cfg.OAuthModelAlias, channel)
|
||||||
if len(h.cfg.OAuthModelMappings) == 0 {
|
if len(h.cfg.OAuthModelAlias) == 0 {
|
||||||
h.cfg.OAuthModelMappings = nil
|
h.cfg.OAuthModelAlias = nil
|
||||||
}
|
}
|
||||||
h.persist(c)
|
h.persist(c)
|
||||||
}
|
}
|
||||||
@@ -1042,26 +1042,26 @@ func normalizeVertexCompatKey(entry *config.VertexCompatKey) {
|
|||||||
entry.Models = normalized
|
entry.Models = normalized
|
||||||
}
|
}
|
||||||
|
|
||||||
func sanitizedOAuthModelMappings(entries map[string][]config.ModelNameMapping) map[string][]config.ModelNameMapping {
|
func sanitizedOAuthModelAlias(entries map[string][]config.OAuthModelAlias) map[string][]config.OAuthModelAlias {
|
||||||
if len(entries) == 0 {
|
if len(entries) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
copied := make(map[string][]config.ModelNameMapping, len(entries))
|
copied := make(map[string][]config.OAuthModelAlias, len(entries))
|
||||||
for channel, mappings := range entries {
|
for channel, aliases := range entries {
|
||||||
if len(mappings) == 0 {
|
if len(aliases) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
copied[channel] = append([]config.ModelNameMapping(nil), mappings...)
|
copied[channel] = append([]config.OAuthModelAlias(nil), aliases...)
|
||||||
}
|
}
|
||||||
if len(copied) == 0 {
|
if len(copied) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
cfg := config.Config{OAuthModelMappings: copied}
|
cfg := config.Config{OAuthModelAlias: copied}
|
||||||
cfg.SanitizeOAuthModelMappings()
|
cfg.SanitizeOAuthModelAlias()
|
||||||
if len(cfg.OAuthModelMappings) == 0 {
|
if len(cfg.OAuthModelAlias) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return cfg.OAuthModelMappings
|
return cfg.OAuthModelAlias
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAmpCode returns the complete ampcode configuration.
|
// GetAmpCode returns the complete ampcode configuration.
|
||||||
|
|||||||
@@ -601,10 +601,10 @@ func (s *Server) registerManagementRoutes() {
|
|||||||
mgmt.PATCH("/oauth-excluded-models", s.mgmt.PatchOAuthExcludedModels)
|
mgmt.PATCH("/oauth-excluded-models", s.mgmt.PatchOAuthExcludedModels)
|
||||||
mgmt.DELETE("/oauth-excluded-models", s.mgmt.DeleteOAuthExcludedModels)
|
mgmt.DELETE("/oauth-excluded-models", s.mgmt.DeleteOAuthExcludedModels)
|
||||||
|
|
||||||
mgmt.GET("/oauth-model-mappings", s.mgmt.GetOAuthModelMappings)
|
mgmt.GET("/oauth-model-alias", s.mgmt.GetOAuthModelAlias)
|
||||||
mgmt.PUT("/oauth-model-mappings", s.mgmt.PutOAuthModelMappings)
|
mgmt.PUT("/oauth-model-alias", s.mgmt.PutOAuthModelAlias)
|
||||||
mgmt.PATCH("/oauth-model-mappings", s.mgmt.PatchOAuthModelMappings)
|
mgmt.PATCH("/oauth-model-alias", s.mgmt.PatchOAuthModelAlias)
|
||||||
mgmt.DELETE("/oauth-model-mappings", s.mgmt.DeleteOAuthModelMappings)
|
mgmt.DELETE("/oauth-model-alias", s.mgmt.DeleteOAuthModelAlias)
|
||||||
|
|
||||||
mgmt.GET("/auth-files", s.mgmt.ListAuthFiles)
|
mgmt.GET("/auth-files", s.mgmt.ListAuthFiles)
|
||||||
mgmt.GET("/auth-files/models", s.mgmt.GetAuthFileModels)
|
mgmt.GET("/auth-files/models", s.mgmt.GetAuthFileModels)
|
||||||
|
|||||||
@@ -91,13 +91,13 @@ type Config struct {
|
|||||||
// OAuthExcludedModels defines per-provider global model exclusions applied to OAuth/file-backed auth entries.
|
// 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"`
|
OAuthExcludedModels map[string][]string `yaml:"oauth-excluded-models,omitempty" json:"oauth-excluded-models,omitempty"`
|
||||||
|
|
||||||
// OAuthModelMappings defines global model name mappings for OAuth/file-backed auth channels.
|
// OAuthModelAlias defines global model name aliases for OAuth/file-backed auth channels.
|
||||||
// These mappings affect both model listing and model routing for supported channels:
|
// These aliases affect both model listing and model routing for supported channels:
|
||||||
// gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow.
|
// gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow.
|
||||||
//
|
//
|
||||||
// NOTE: This does not apply to existing per-credential model alias features under:
|
// NOTE: This does not apply to existing per-credential model alias features under:
|
||||||
// gemini-api-key, codex-api-key, claude-api-key, openai-compatibility, vertex-api-key, and ampcode.
|
// 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"`
|
OAuthModelAlias map[string][]OAuthModelAlias `yaml:"oauth-model-alias,omitempty" json:"oauth-model-alias,omitempty"`
|
||||||
|
|
||||||
// Payload defines default and override rules for provider payload parameters.
|
// Payload defines default and override rules for provider payload parameters.
|
||||||
Payload PayloadConfig `yaml:"payload" json:"payload"`
|
Payload PayloadConfig `yaml:"payload" json:"payload"`
|
||||||
@@ -145,11 +145,11 @@ type RoutingConfig struct {
|
|||||||
Strategy string `yaml:"strategy,omitempty" json:"strategy,omitempty"`
|
Strategy string `yaml:"strategy,omitempty" json:"strategy,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ModelNameMapping defines a model ID mapping for a specific channel.
|
// OAuthModelAlias defines a model ID alias for a specific channel.
|
||||||
// It maps the upstream model name (Name) to the client-visible alias (Alias).
|
// It maps the upstream model name (Name) to the client-visible alias (Alias).
|
||||||
// When Fork is true, the alias is added as an additional model in listings while
|
// When Fork is true, the alias is added as an additional model in listings while
|
||||||
// keeping the original model ID available.
|
// keeping the original model ID available.
|
||||||
type ModelNameMapping struct {
|
type OAuthModelAlias struct {
|
||||||
Name string `yaml:"name" json:"name"`
|
Name string `yaml:"name" json:"name"`
|
||||||
Alias string `yaml:"alias" json:"alias"`
|
Alias string `yaml:"alias" json:"alias"`
|
||||||
Fork bool `yaml:"fork,omitempty" json:"fork,omitempty"`
|
Fork bool `yaml:"fork,omitempty" json:"fork,omitempty"`
|
||||||
@@ -436,6 +436,15 @@ func LoadConfig(configFile string) (*Config, error) {
|
|||||||
// If optional is true and the file is missing, it returns an empty Config.
|
// If optional is true and the file is missing, it returns an empty Config.
|
||||||
// If optional is true and the file is empty or invalid, it returns an empty Config.
|
// If optional is true and the file is empty or invalid, it returns an empty Config.
|
||||||
func LoadConfigOptional(configFile string, optional bool) (*Config, error) {
|
func LoadConfigOptional(configFile string, optional bool) (*Config, error) {
|
||||||
|
// Perform oauth-model-alias migration before loading config.
|
||||||
|
// This migrates oauth-model-mappings to oauth-model-alias if needed.
|
||||||
|
if migrated, err := MigrateOAuthModelAlias(configFile); err != nil {
|
||||||
|
// Log warning but don't fail - config loading should still work
|
||||||
|
fmt.Printf("Warning: oauth-model-alias migration failed: %v\n", err)
|
||||||
|
} else if migrated {
|
||||||
|
fmt.Println("Migrated oauth-model-mappings to oauth-model-alias")
|
||||||
|
}
|
||||||
|
|
||||||
// Read the entire configuration file into memory.
|
// Read the entire configuration file into memory.
|
||||||
data, err := os.ReadFile(configFile)
|
data, err := os.ReadFile(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -528,8 +537,8 @@ func LoadConfigOptional(configFile string, optional bool) (*Config, error) {
|
|||||||
// Normalize OAuth provider model exclusion map.
|
// Normalize OAuth provider model exclusion map.
|
||||||
cfg.OAuthExcludedModels = NormalizeOAuthExcludedModels(cfg.OAuthExcludedModels)
|
cfg.OAuthExcludedModels = NormalizeOAuthExcludedModels(cfg.OAuthExcludedModels)
|
||||||
|
|
||||||
// Normalize global OAuth model name mappings.
|
// Normalize global OAuth model name aliases.
|
||||||
cfg.SanitizeOAuthModelMappings()
|
cfg.SanitizeOAuthModelAlias()
|
||||||
|
|
||||||
if cfg.legacyMigrationPending {
|
if cfg.legacyMigrationPending {
|
||||||
fmt.Println("Detected legacy configuration keys, attempting to persist the normalized config...")
|
fmt.Println("Detected legacy configuration keys, attempting to persist the normalized config...")
|
||||||
@@ -547,24 +556,24 @@ func LoadConfigOptional(configFile string, optional bool) (*Config, error) {
|
|||||||
return &cfg, nil
|
return &cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SanitizeOAuthModelMappings normalizes and deduplicates global OAuth model name mappings.
|
// SanitizeOAuthModelAlias normalizes and deduplicates global OAuth model name aliases.
|
||||||
// It trims whitespace, normalizes channel keys to lower-case, drops empty entries,
|
// It trims whitespace, normalizes channel keys to lower-case, drops empty entries,
|
||||||
// allows multiple aliases per upstream name, and ensures aliases are unique within each channel.
|
// allows multiple aliases per upstream name, and ensures aliases are unique within each channel.
|
||||||
func (cfg *Config) SanitizeOAuthModelMappings() {
|
func (cfg *Config) SanitizeOAuthModelAlias() {
|
||||||
if cfg == nil || len(cfg.OAuthModelMappings) == 0 {
|
if cfg == nil || len(cfg.OAuthModelAlias) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
out := make(map[string][]ModelNameMapping, len(cfg.OAuthModelMappings))
|
out := make(map[string][]OAuthModelAlias, len(cfg.OAuthModelAlias))
|
||||||
for rawChannel, mappings := range cfg.OAuthModelMappings {
|
for rawChannel, aliases := range cfg.OAuthModelAlias {
|
||||||
channel := strings.ToLower(strings.TrimSpace(rawChannel))
|
channel := strings.ToLower(strings.TrimSpace(rawChannel))
|
||||||
if channel == "" || len(mappings) == 0 {
|
if channel == "" || len(aliases) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seenAlias := make(map[string]struct{}, len(mappings))
|
seenAlias := make(map[string]struct{}, len(aliases))
|
||||||
clean := make([]ModelNameMapping, 0, len(mappings))
|
clean := make([]OAuthModelAlias, 0, len(aliases))
|
||||||
for _, mapping := range mappings {
|
for _, entry := range aliases {
|
||||||
name := strings.TrimSpace(mapping.Name)
|
name := strings.TrimSpace(entry.Name)
|
||||||
alias := strings.TrimSpace(mapping.Alias)
|
alias := strings.TrimSpace(entry.Alias)
|
||||||
if name == "" || alias == "" {
|
if name == "" || alias == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -576,13 +585,13 @@ func (cfg *Config) SanitizeOAuthModelMappings() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seenAlias[aliasKey] = struct{}{}
|
seenAlias[aliasKey] = struct{}{}
|
||||||
clean = append(clean, ModelNameMapping{Name: name, Alias: alias, Fork: mapping.Fork})
|
clean = append(clean, OAuthModelAlias{Name: name, Alias: alias, Fork: entry.Fork})
|
||||||
}
|
}
|
||||||
if len(clean) > 0 {
|
if len(clean) > 0 {
|
||||||
out[channel] = clean
|
out[channel] = clean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cfg.OAuthModelMappings = out
|
cfg.OAuthModelAlias = out
|
||||||
}
|
}
|
||||||
|
|
||||||
// SanitizeOpenAICompatibility removes OpenAI-compatibility provider entries that are
|
// SanitizeOpenAICompatibility removes OpenAI-compatibility provider entries that are
|
||||||
|
|||||||
258
internal/config/oauth_model_alias_migration.go
Normal file
258
internal/config/oauth_model_alias_migration.go
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// antigravityModelConversionTable maps old built-in aliases to actual model names
|
||||||
|
// for the antigravity channel during migration.
|
||||||
|
var antigravityModelConversionTable = map[string]string{
|
||||||
|
"gemini-2.5-computer-use-preview-10-2025": "rev19-uic3-1p",
|
||||||
|
"gemini-3-pro-image-preview": "gemini-3-pro-image",
|
||||||
|
"gemini-3-pro-preview": "gemini-3-pro-high",
|
||||||
|
"gemini-3-flash-preview": "gemini-3-flash",
|
||||||
|
"gemini-claude-sonnet-4-5": "claude-sonnet-4-5",
|
||||||
|
"gemini-claude-sonnet-4-5-thinking": "claude-sonnet-4-5-thinking",
|
||||||
|
"gemini-claude-opus-4-5-thinking": "claude-opus-4-5-thinking",
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultAntigravityAliases returns the default oauth-model-alias configuration
|
||||||
|
// for the antigravity channel when neither field exists.
|
||||||
|
func defaultAntigravityAliases() []OAuthModelAlias {
|
||||||
|
return []OAuthModelAlias{
|
||||||
|
{Name: "rev19-uic3-1p", Alias: "gemini-2.5-computer-use-preview-10-2025"},
|
||||||
|
{Name: "gemini-3-pro-image", Alias: "gemini-3-pro-image-preview"},
|
||||||
|
{Name: "gemini-3-pro-high", Alias: "gemini-3-pro-preview"},
|
||||||
|
{Name: "gemini-3-flash", Alias: "gemini-3-flash-preview"},
|
||||||
|
{Name: "claude-sonnet-4-5", Alias: "gemini-claude-sonnet-4-5"},
|
||||||
|
{Name: "claude-sonnet-4-5-thinking", Alias: "gemini-claude-sonnet-4-5-thinking"},
|
||||||
|
{Name: "claude-opus-4-5-thinking", Alias: "gemini-claude-opus-4-5-thinking"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MigrateOAuthModelAlias checks for and performs migration from oauth-model-mappings
|
||||||
|
// to oauth-model-alias at startup. Returns true if migration was performed.
|
||||||
|
//
|
||||||
|
// Migration flow:
|
||||||
|
// 1. Check if oauth-model-alias exists -> skip migration
|
||||||
|
// 2. Check if oauth-model-mappings exists -> convert and migrate
|
||||||
|
// - For antigravity channel, convert old built-in aliases to actual model names
|
||||||
|
//
|
||||||
|
// 3. Neither exists -> add default antigravity config
|
||||||
|
func MigrateOAuthModelAlias(configFile string) (bool, error) {
|
||||||
|
data, err := os.ReadFile(configFile)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if len(data) == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse YAML into node tree to preserve structure
|
||||||
|
var root yaml.Node
|
||||||
|
if err := yaml.Unmarshal(data, &root); err != nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if root.Kind != yaml.DocumentNode || len(root.Content) == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
rootMap := root.Content[0]
|
||||||
|
if rootMap == nil || rootMap.Kind != yaml.MappingNode {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if oauth-model-alias already exists
|
||||||
|
if findMapKeyIndex(rootMap, "oauth-model-alias") >= 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if oauth-model-mappings exists
|
||||||
|
oldIdx := findMapKeyIndex(rootMap, "oauth-model-mappings")
|
||||||
|
if oldIdx >= 0 {
|
||||||
|
// Migrate from old field
|
||||||
|
return migrateFromOldField(configFile, &root, rootMap, oldIdx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neither field exists - add default antigravity config
|
||||||
|
return addDefaultAntigravityConfig(configFile, &root, rootMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// migrateFromOldField converts oauth-model-mappings to oauth-model-alias
|
||||||
|
func migrateFromOldField(configFile string, root *yaml.Node, rootMap *yaml.Node, oldIdx int) (bool, error) {
|
||||||
|
if oldIdx+1 >= len(rootMap.Content) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
oldValue := rootMap.Content[oldIdx+1]
|
||||||
|
if oldValue == nil || oldValue.Kind != yaml.MappingNode {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the old aliases
|
||||||
|
oldAliases := parseOldAliasNode(oldValue)
|
||||||
|
if len(oldAliases) == 0 {
|
||||||
|
// Remove the old field and write
|
||||||
|
removeMapKeyByIndex(rootMap, oldIdx)
|
||||||
|
return writeYAMLNode(configFile, root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert model names for antigravity channel
|
||||||
|
newAliases := make(map[string][]OAuthModelAlias, len(oldAliases))
|
||||||
|
for channel, entries := range oldAliases {
|
||||||
|
converted := make([]OAuthModelAlias, 0, len(entries))
|
||||||
|
for _, entry := range entries {
|
||||||
|
newEntry := OAuthModelAlias{
|
||||||
|
Name: entry.Name,
|
||||||
|
Alias: entry.Alias,
|
||||||
|
Fork: entry.Fork,
|
||||||
|
}
|
||||||
|
// Convert model names for antigravity channel
|
||||||
|
if strings.EqualFold(channel, "antigravity") {
|
||||||
|
if actual, ok := antigravityModelConversionTable[entry.Name]; ok {
|
||||||
|
newEntry.Name = actual
|
||||||
|
}
|
||||||
|
}
|
||||||
|
converted = append(converted, newEntry)
|
||||||
|
}
|
||||||
|
newAliases[channel] = converted
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build new node
|
||||||
|
newNode := buildOAuthModelAliasNode(newAliases)
|
||||||
|
|
||||||
|
// Replace old key with new key and value
|
||||||
|
rootMap.Content[oldIdx].Value = "oauth-model-alias"
|
||||||
|
rootMap.Content[oldIdx+1] = newNode
|
||||||
|
|
||||||
|
return writeYAMLNode(configFile, root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addDefaultAntigravityConfig adds the default antigravity configuration
|
||||||
|
func addDefaultAntigravityConfig(configFile string, root *yaml.Node, rootMap *yaml.Node) (bool, error) {
|
||||||
|
defaults := map[string][]OAuthModelAlias{
|
||||||
|
"antigravity": defaultAntigravityAliases(),
|
||||||
|
}
|
||||||
|
newNode := buildOAuthModelAliasNode(defaults)
|
||||||
|
|
||||||
|
// Add new key-value pair
|
||||||
|
keyNode := &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: "oauth-model-alias"}
|
||||||
|
rootMap.Content = append(rootMap.Content, keyNode, newNode)
|
||||||
|
|
||||||
|
return writeYAMLNode(configFile, root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseOldAliasNode parses the old oauth-model-mappings node structure
|
||||||
|
func parseOldAliasNode(node *yaml.Node) map[string][]OAuthModelAlias {
|
||||||
|
if node == nil || node.Kind != yaml.MappingNode {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
result := make(map[string][]OAuthModelAlias)
|
||||||
|
for i := 0; i+1 < len(node.Content); i += 2 {
|
||||||
|
channelNode := node.Content[i]
|
||||||
|
entriesNode := node.Content[i+1]
|
||||||
|
if channelNode == nil || entriesNode == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
channel := strings.ToLower(strings.TrimSpace(channelNode.Value))
|
||||||
|
if channel == "" || entriesNode.Kind != yaml.SequenceNode {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
entries := make([]OAuthModelAlias, 0, len(entriesNode.Content))
|
||||||
|
for _, entryNode := range entriesNode.Content {
|
||||||
|
if entryNode == nil || entryNode.Kind != yaml.MappingNode {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
entry := parseAliasEntry(entryNode)
|
||||||
|
if entry.Name != "" && entry.Alias != "" {
|
||||||
|
entries = append(entries, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(entries) > 0 {
|
||||||
|
result[channel] = entries
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseAliasEntry parses a single alias entry node
|
||||||
|
func parseAliasEntry(node *yaml.Node) OAuthModelAlias {
|
||||||
|
var entry OAuthModelAlias
|
||||||
|
for i := 0; i+1 < len(node.Content); i += 2 {
|
||||||
|
keyNode := node.Content[i]
|
||||||
|
valNode := node.Content[i+1]
|
||||||
|
if keyNode == nil || valNode == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch strings.ToLower(strings.TrimSpace(keyNode.Value)) {
|
||||||
|
case "name":
|
||||||
|
entry.Name = strings.TrimSpace(valNode.Value)
|
||||||
|
case "alias":
|
||||||
|
entry.Alias = strings.TrimSpace(valNode.Value)
|
||||||
|
case "fork":
|
||||||
|
entry.Fork = strings.ToLower(strings.TrimSpace(valNode.Value)) == "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildOAuthModelAliasNode creates a YAML node for oauth-model-alias
|
||||||
|
func buildOAuthModelAliasNode(aliases map[string][]OAuthModelAlias) *yaml.Node {
|
||||||
|
node := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
|
||||||
|
for channel, entries := range aliases {
|
||||||
|
channelNode := &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: channel}
|
||||||
|
entriesNode := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
|
||||||
|
for _, entry := range entries {
|
||||||
|
entryNode := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
|
||||||
|
entryNode.Content = append(entryNode.Content,
|
||||||
|
&yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: "name"},
|
||||||
|
&yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: entry.Name},
|
||||||
|
&yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: "alias"},
|
||||||
|
&yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: entry.Alias},
|
||||||
|
)
|
||||||
|
if entry.Fork {
|
||||||
|
entryNode.Content = append(entryNode.Content,
|
||||||
|
&yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: "fork"},
|
||||||
|
&yaml.Node{Kind: yaml.ScalarNode, Tag: "!!bool", Value: "true"},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
entriesNode.Content = append(entriesNode.Content, entryNode)
|
||||||
|
}
|
||||||
|
node.Content = append(node.Content, channelNode, entriesNode)
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeMapKeyByIndex removes a key-value pair from a mapping node by index
|
||||||
|
func removeMapKeyByIndex(mapNode *yaml.Node, keyIdx int) {
|
||||||
|
if mapNode == nil || mapNode.Kind != yaml.MappingNode {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if keyIdx < 0 || keyIdx+1 >= len(mapNode.Content) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mapNode.Content = append(mapNode.Content[:keyIdx], mapNode.Content[keyIdx+2:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeYAMLNode writes the YAML node tree back to file
|
||||||
|
func writeYAMLNode(configFile string, root *yaml.Node) (bool, error) {
|
||||||
|
f, err := os.Create(configFile)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
enc := yaml.NewEncoder(f)
|
||||||
|
enc.SetIndent(2)
|
||||||
|
if err := enc.Encode(root); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if err := enc.Close(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
225
internal/config/oauth_model_alias_migration_test.go
Normal file
225
internal/config/oauth_model_alias_migration_test.go
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMigrateOAuthModelAlias_SkipsIfNewFieldExists(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
configFile := filepath.Join(dir, "config.yaml")
|
||||||
|
|
||||||
|
content := `oauth-model-alias:
|
||||||
|
gemini-cli:
|
||||||
|
- name: "gemini-2.5-pro"
|
||||||
|
alias: "g2.5p"
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
migrated, err := MigrateOAuthModelAlias(configFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if migrated {
|
||||||
|
t.Fatal("expected no migration when oauth-model-alias already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify file unchanged
|
||||||
|
data, _ := os.ReadFile(configFile)
|
||||||
|
if !strings.Contains(string(data), "oauth-model-alias:") {
|
||||||
|
t.Fatal("file should still contain oauth-model-alias")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMigrateOAuthModelAlias_MigratesOldField(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
configFile := filepath.Join(dir, "config.yaml")
|
||||||
|
|
||||||
|
content := `oauth-model-mappings:
|
||||||
|
gemini-cli:
|
||||||
|
- name: "gemini-2.5-pro"
|
||||||
|
alias: "g2.5p"
|
||||||
|
fork: true
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
migrated, err := MigrateOAuthModelAlias(configFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !migrated {
|
||||||
|
t.Fatal("expected migration to occur")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify new field exists and old field removed
|
||||||
|
data, _ := os.ReadFile(configFile)
|
||||||
|
if strings.Contains(string(data), "oauth-model-mappings:") {
|
||||||
|
t.Fatal("old field should be removed")
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(data), "oauth-model-alias:") {
|
||||||
|
t.Fatal("new field should exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse and verify structure
|
||||||
|
var root yaml.Node
|
||||||
|
if err := yaml.Unmarshal(data, &root); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMigrateOAuthModelAlias_ConvertsAntigravityModels(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
configFile := filepath.Join(dir, "config.yaml")
|
||||||
|
|
||||||
|
// Use old model names that should be converted
|
||||||
|
content := `oauth-model-mappings:
|
||||||
|
antigravity:
|
||||||
|
- name: "gemini-2.5-computer-use-preview-10-2025"
|
||||||
|
alias: "computer-use"
|
||||||
|
- name: "gemini-3-pro-preview"
|
||||||
|
alias: "g3p"
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
migrated, err := MigrateOAuthModelAlias(configFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !migrated {
|
||||||
|
t.Fatal("expected migration to occur")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify model names were converted
|
||||||
|
data, _ := os.ReadFile(configFile)
|
||||||
|
content = string(data)
|
||||||
|
if !strings.Contains(content, "rev19-uic3-1p") {
|
||||||
|
t.Fatal("expected gemini-2.5-computer-use-preview-10-2025 to be converted to rev19-uic3-1p")
|
||||||
|
}
|
||||||
|
if !strings.Contains(content, "gemini-3-pro-high") {
|
||||||
|
t.Fatal("expected gemini-3-pro-preview to be converted to gemini-3-pro-high")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMigrateOAuthModelAlias_AddsDefaultIfNeitherExists(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
configFile := filepath.Join(dir, "config.yaml")
|
||||||
|
|
||||||
|
content := `debug: true
|
||||||
|
port: 8080
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
migrated, err := MigrateOAuthModelAlias(configFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !migrated {
|
||||||
|
t.Fatal("expected migration to add default config")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify default antigravity config was added
|
||||||
|
data, _ := os.ReadFile(configFile)
|
||||||
|
content = string(data)
|
||||||
|
if !strings.Contains(content, "oauth-model-alias:") {
|
||||||
|
t.Fatal("expected oauth-model-alias to be added")
|
||||||
|
}
|
||||||
|
if !strings.Contains(content, "antigravity:") {
|
||||||
|
t.Fatal("expected antigravity channel to be added")
|
||||||
|
}
|
||||||
|
if !strings.Contains(content, "rev19-uic3-1p") {
|
||||||
|
t.Fatal("expected default antigravity aliases to include rev19-uic3-1p")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMigrateOAuthModelAlias_PreservesOtherConfig(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
configFile := filepath.Join(dir, "config.yaml")
|
||||||
|
|
||||||
|
content := `debug: true
|
||||||
|
port: 8080
|
||||||
|
oauth-model-mappings:
|
||||||
|
gemini-cli:
|
||||||
|
- name: "test"
|
||||||
|
alias: "t"
|
||||||
|
api-keys:
|
||||||
|
- "key1"
|
||||||
|
- "key2"
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(configFile, []byte(content), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
migrated, err := MigrateOAuthModelAlias(configFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !migrated {
|
||||||
|
t.Fatal("expected migration to occur")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify other config preserved
|
||||||
|
data, _ := os.ReadFile(configFile)
|
||||||
|
content = string(data)
|
||||||
|
if !strings.Contains(content, "debug: true") {
|
||||||
|
t.Fatal("expected debug field to be preserved")
|
||||||
|
}
|
||||||
|
if !strings.Contains(content, "port: 8080") {
|
||||||
|
t.Fatal("expected port field to be preserved")
|
||||||
|
}
|
||||||
|
if !strings.Contains(content, "api-keys:") {
|
||||||
|
t.Fatal("expected api-keys field to be preserved")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMigrateOAuthModelAlias_NonexistentFile(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
migrated, err := MigrateOAuthModelAlias("/nonexistent/path/config.yaml")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error for nonexistent file: %v", err)
|
||||||
|
}
|
||||||
|
if migrated {
|
||||||
|
t.Fatal("expected no migration for nonexistent file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMigrateOAuthModelAlias_EmptyFile(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
configFile := filepath.Join(dir, "config.yaml")
|
||||||
|
|
||||||
|
if err := os.WriteFile(configFile, []byte(""), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
migrated, err := MigrateOAuthModelAlias(configFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if migrated {
|
||||||
|
t.Fatal("expected no migration for empty file")
|
||||||
|
}
|
||||||
|
}
|
||||||
56
internal/config/oauth_model_alias_test.go
Normal file
56
internal/config/oauth_model_alias_test.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestSanitizeOAuthModelAlias_PreservesForkFlag(t *testing.T) {
|
||||||
|
cfg := &Config{
|
||||||
|
OAuthModelAlias: map[string][]OAuthModelAlias{
|
||||||
|
" CoDeX ": {
|
||||||
|
{Name: " gpt-5 ", Alias: " g5 ", Fork: true},
|
||||||
|
{Name: "gpt-6", Alias: "g6"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.SanitizeOAuthModelAlias()
|
||||||
|
|
||||||
|
aliases := cfg.OAuthModelAlias["codex"]
|
||||||
|
if len(aliases) != 2 {
|
||||||
|
t.Fatalf("expected 2 sanitized aliases, got %d", len(aliases))
|
||||||
|
}
|
||||||
|
if aliases[0].Name != "gpt-5" || aliases[0].Alias != "g5" || !aliases[0].Fork {
|
||||||
|
t.Fatalf("expected first alias to be gpt-5->g5 fork=true, got name=%q alias=%q fork=%v", aliases[0].Name, aliases[0].Alias, aliases[0].Fork)
|
||||||
|
}
|
||||||
|
if aliases[1].Name != "gpt-6" || aliases[1].Alias != "g6" || aliases[1].Fork {
|
||||||
|
t.Fatalf("expected second alias to be gpt-6->g6 fork=false, got name=%q alias=%q fork=%v", aliases[1].Name, aliases[1].Alias, aliases[1].Fork)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSanitizeOAuthModelAlias_AllowsMultipleAliasesForSameName(t *testing.T) {
|
||||||
|
cfg := &Config{
|
||||||
|
OAuthModelAlias: map[string][]OAuthModelAlias{
|
||||||
|
"antigravity": {
|
||||||
|
{Name: "gemini-claude-opus-4-5-thinking", Alias: "claude-opus-4-5-20251101", Fork: true},
|
||||||
|
{Name: "gemini-claude-opus-4-5-thinking", Alias: "claude-opus-4-5-20251101-thinking", Fork: true},
|
||||||
|
{Name: "gemini-claude-opus-4-5-thinking", Alias: "claude-opus-4-5", Fork: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.SanitizeOAuthModelAlias()
|
||||||
|
|
||||||
|
aliases := cfg.OAuthModelAlias["antigravity"]
|
||||||
|
expected := []OAuthModelAlias{
|
||||||
|
{Name: "gemini-claude-opus-4-5-thinking", Alias: "claude-opus-4-5-20251101", Fork: true},
|
||||||
|
{Name: "gemini-claude-opus-4-5-thinking", Alias: "claude-opus-4-5-20251101-thinking", Fork: true},
|
||||||
|
{Name: "gemini-claude-opus-4-5-thinking", Alias: "claude-opus-4-5", Fork: true},
|
||||||
|
}
|
||||||
|
if len(aliases) != len(expected) {
|
||||||
|
t.Fatalf("expected %d sanitized aliases, got %d", len(expected), len(aliases))
|
||||||
|
}
|
||||||
|
for i, exp := range expected {
|
||||||
|
if aliases[i].Name != exp.Name || aliases[i].Alias != exp.Alias || aliases[i].Fork != exp.Fork {
|
||||||
|
t.Fatalf("expected alias %d to be name=%q alias=%q fork=%v, got name=%q alias=%q fork=%v", i, exp.Name, exp.Alias, exp.Fork, aliases[i].Name, aliases[i].Alias, aliases[i].Fork)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestSanitizeOAuthModelMappings_PreservesForkFlag(t *testing.T) {
|
|
||||||
cfg := &Config{
|
|
||||||
OAuthModelMappings: map[string][]ModelNameMapping{
|
|
||||||
" CoDeX ": {
|
|
||||||
{Name: " gpt-5 ", Alias: " g5 ", Fork: true},
|
|
||||||
{Name: "gpt-6", Alias: "g6"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.SanitizeOAuthModelMappings()
|
|
||||||
|
|
||||||
mappings := cfg.OAuthModelMappings["codex"]
|
|
||||||
if len(mappings) != 2 {
|
|
||||||
t.Fatalf("expected 2 sanitized mappings, got %d", len(mappings))
|
|
||||||
}
|
|
||||||
if mappings[0].Name != "gpt-5" || mappings[0].Alias != "g5" || !mappings[0].Fork {
|
|
||||||
t.Fatalf("expected first mapping to be gpt-5->g5 fork=true, got name=%q alias=%q fork=%v", mappings[0].Name, mappings[0].Alias, mappings[0].Fork)
|
|
||||||
}
|
|
||||||
if mappings[1].Name != "gpt-6" || mappings[1].Alias != "g6" || mappings[1].Fork {
|
|
||||||
t.Fatalf("expected second mapping to be gpt-6->g6 fork=false, got name=%q alias=%q fork=%v", mappings[1].Name, mappings[1].Alias, mappings[1].Fork)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSanitizeOAuthModelMappings_AllowsMultipleAliasesForSameName(t *testing.T) {
|
|
||||||
cfg := &Config{
|
|
||||||
OAuthModelMappings: map[string][]ModelNameMapping{
|
|
||||||
"antigravity": {
|
|
||||||
{Name: "gemini-claude-opus-4-5-thinking", Alias: "claude-opus-4-5-20251101", Fork: true},
|
|
||||||
{Name: "gemini-claude-opus-4-5-thinking", Alias: "claude-opus-4-5-20251101-thinking", Fork: true},
|
|
||||||
{Name: "gemini-claude-opus-4-5-thinking", Alias: "claude-opus-4-5", Fork: true},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.SanitizeOAuthModelMappings()
|
|
||||||
|
|
||||||
mappings := cfg.OAuthModelMappings["antigravity"]
|
|
||||||
expected := []ModelNameMapping{
|
|
||||||
{Name: "gemini-claude-opus-4-5-thinking", Alias: "claude-opus-4-5-20251101", Fork: true},
|
|
||||||
{Name: "gemini-claude-opus-4-5-thinking", Alias: "claude-opus-4-5-20251101-thinking", Fork: true},
|
|
||||||
{Name: "gemini-claude-opus-4-5-thinking", Alias: "claude-opus-4-5", Fork: true},
|
|
||||||
}
|
|
||||||
if len(mappings) != len(expected) {
|
|
||||||
t.Fatalf("expected %d sanitized mappings, got %d", len(expected), len(mappings))
|
|
||||||
}
|
|
||||||
for i, exp := range expected {
|
|
||||||
if mappings[i].Name != exp.Name || mappings[i].Alias != exp.Alias || mappings[i].Fork != exp.Fork {
|
|
||||||
t.Fatalf("expected mapping %d to be name=%q alias=%q fork=%v, got name=%q alias=%q fork=%v", i, exp.Name, exp.Alias, exp.Fork, mappings[i].Name, mappings[i].Alias, mappings[i].Fork)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -127,7 +127,7 @@ func (w *Watcher) reloadConfig() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
authDirChanged := oldConfig == nil || oldConfig.AuthDir != newConfig.AuthDir
|
authDirChanged := oldConfig == nil || oldConfig.AuthDir != newConfig.AuthDir
|
||||||
forceAuthRefresh := oldConfig != nil && (oldConfig.ForceModelPrefix != newConfig.ForceModelPrefix || !reflect.DeepEqual(oldConfig.OAuthModelMappings, newConfig.OAuthModelMappings))
|
forceAuthRefresh := oldConfig != nil && (oldConfig.ForceModelPrefix != newConfig.ForceModelPrefix || !reflect.DeepEqual(oldConfig.OAuthModelAlias, newConfig.OAuthModelAlias))
|
||||||
|
|
||||||
log.Infof("config successfully reloaded, triggering client reload")
|
log.Infof("config successfully reloaded, triggering client reload")
|
||||||
w.reloadClients(authDirChanged, affectedOAuthProviders, forceAuthRefresh)
|
w.reloadClients(authDirChanged, affectedOAuthProviders, forceAuthRefresh)
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ func BuildConfigChangeDetails(oldCfg, newCfg *config.Config) []string {
|
|||||||
if entries, _ := DiffOAuthExcludedModelChanges(oldCfg.OAuthExcludedModels, newCfg.OAuthExcludedModels); len(entries) > 0 {
|
if entries, _ := DiffOAuthExcludedModelChanges(oldCfg.OAuthExcludedModels, newCfg.OAuthExcludedModels); len(entries) > 0 {
|
||||||
changes = append(changes, entries...)
|
changes = append(changes, entries...)
|
||||||
}
|
}
|
||||||
if entries, _ := DiffOAuthModelMappingChanges(oldCfg.OAuthModelMappings, newCfg.OAuthModelMappings); len(entries) > 0 {
|
if entries, _ := DiffOAuthModelAliasChanges(oldCfg.OAuthModelAlias, newCfg.OAuthModelAlias); len(entries) > 0 {
|
||||||
changes = append(changes, entries...)
|
changes = append(changes, entries...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,23 +10,23 @@ import (
|
|||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OAuthModelMappingsSummary struct {
|
type OAuthModelAliasSummary struct {
|
||||||
hash string
|
hash string
|
||||||
count int
|
count int
|
||||||
}
|
}
|
||||||
|
|
||||||
// SummarizeOAuthModelMappings summarizes OAuth model mappings per channel.
|
// SummarizeOAuthModelAlias summarizes OAuth model alias per channel.
|
||||||
func SummarizeOAuthModelMappings(entries map[string][]config.ModelNameMapping) map[string]OAuthModelMappingsSummary {
|
func SummarizeOAuthModelAlias(entries map[string][]config.OAuthModelAlias) map[string]OAuthModelAliasSummary {
|
||||||
if len(entries) == 0 {
|
if len(entries) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
out := make(map[string]OAuthModelMappingsSummary, len(entries))
|
out := make(map[string]OAuthModelAliasSummary, len(entries))
|
||||||
for k, v := range entries {
|
for k, v := range entries {
|
||||||
key := strings.ToLower(strings.TrimSpace(k))
|
key := strings.ToLower(strings.TrimSpace(k))
|
||||||
if key == "" {
|
if key == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
out[key] = summarizeOAuthModelMappingList(v)
|
out[key] = summarizeOAuthModelAliasList(v)
|
||||||
}
|
}
|
||||||
if len(out) == 0 {
|
if len(out) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -34,10 +34,10 @@ func SummarizeOAuthModelMappings(entries map[string][]config.ModelNameMapping) m
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// DiffOAuthModelMappingChanges compares OAuth model mappings maps.
|
// DiffOAuthModelAliasChanges compares OAuth model alias maps.
|
||||||
func DiffOAuthModelMappingChanges(oldMap, newMap map[string][]config.ModelNameMapping) ([]string, []string) {
|
func DiffOAuthModelAliasChanges(oldMap, newMap map[string][]config.OAuthModelAlias) ([]string, []string) {
|
||||||
oldSummary := SummarizeOAuthModelMappings(oldMap)
|
oldSummary := SummarizeOAuthModelAlias(oldMap)
|
||||||
newSummary := SummarizeOAuthModelMappings(newMap)
|
newSummary := SummarizeOAuthModelAlias(newMap)
|
||||||
keys := make(map[string]struct{}, len(oldSummary)+len(newSummary))
|
keys := make(map[string]struct{}, len(oldSummary)+len(newSummary))
|
||||||
for k := range oldSummary {
|
for k := range oldSummary {
|
||||||
keys[k] = struct{}{}
|
keys[k] = struct{}{}
|
||||||
@@ -52,13 +52,13 @@ func DiffOAuthModelMappingChanges(oldMap, newMap map[string][]config.ModelNameMa
|
|||||||
newInfo, okNew := newSummary[key]
|
newInfo, okNew := newSummary[key]
|
||||||
switch {
|
switch {
|
||||||
case okOld && !okNew:
|
case okOld && !okNew:
|
||||||
changes = append(changes, fmt.Sprintf("oauth-model-mappings[%s]: removed", key))
|
changes = append(changes, fmt.Sprintf("oauth-model-alias[%s]: removed", key))
|
||||||
affected = append(affected, key)
|
affected = append(affected, key)
|
||||||
case !okOld && okNew:
|
case !okOld && okNew:
|
||||||
changes = append(changes, fmt.Sprintf("oauth-model-mappings[%s]: added (%d entries)", key, newInfo.count))
|
changes = append(changes, fmt.Sprintf("oauth-model-alias[%s]: added (%d entries)", key, newInfo.count))
|
||||||
affected = append(affected, key)
|
affected = append(affected, key)
|
||||||
case okOld && okNew && oldInfo.hash != newInfo.hash:
|
case okOld && okNew && oldInfo.hash != newInfo.hash:
|
||||||
changes = append(changes, fmt.Sprintf("oauth-model-mappings[%s]: updated (%d -> %d entries)", key, oldInfo.count, newInfo.count))
|
changes = append(changes, fmt.Sprintf("oauth-model-alias[%s]: updated (%d -> %d entries)", key, oldInfo.count, newInfo.count))
|
||||||
affected = append(affected, key)
|
affected = append(affected, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,20 +67,20 @@ func DiffOAuthModelMappingChanges(oldMap, newMap map[string][]config.ModelNameMa
|
|||||||
return changes, affected
|
return changes, affected
|
||||||
}
|
}
|
||||||
|
|
||||||
func summarizeOAuthModelMappingList(list []config.ModelNameMapping) OAuthModelMappingsSummary {
|
func summarizeOAuthModelAliasList(list []config.OAuthModelAlias) OAuthModelAliasSummary {
|
||||||
if len(list) == 0 {
|
if len(list) == 0 {
|
||||||
return OAuthModelMappingsSummary{}
|
return OAuthModelAliasSummary{}
|
||||||
}
|
}
|
||||||
seen := make(map[string]struct{}, len(list))
|
seen := make(map[string]struct{}, len(list))
|
||||||
normalized := make([]string, 0, len(list))
|
normalized := make([]string, 0, len(list))
|
||||||
for _, mapping := range list {
|
for _, alias := range list {
|
||||||
name := strings.ToLower(strings.TrimSpace(mapping.Name))
|
name := strings.ToLower(strings.TrimSpace(alias.Name))
|
||||||
alias := strings.ToLower(strings.TrimSpace(mapping.Alias))
|
aliasVal := strings.ToLower(strings.TrimSpace(alias.Alias))
|
||||||
if name == "" || alias == "" {
|
if name == "" || aliasVal == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
key := name + "->" + alias
|
key := name + "->" + aliasVal
|
||||||
if mapping.Fork {
|
if alias.Fork {
|
||||||
key += "|fork"
|
key += "|fork"
|
||||||
}
|
}
|
||||||
if _, exists := seen[key]; exists {
|
if _, exists := seen[key]; exists {
|
||||||
@@ -90,11 +90,11 @@ func summarizeOAuthModelMappingList(list []config.ModelNameMapping) OAuthModelMa
|
|||||||
normalized = append(normalized, key)
|
normalized = append(normalized, key)
|
||||||
}
|
}
|
||||||
if len(normalized) == 0 {
|
if len(normalized) == 0 {
|
||||||
return OAuthModelMappingsSummary{}
|
return OAuthModelAliasSummary{}
|
||||||
}
|
}
|
||||||
sort.Strings(normalized)
|
sort.Strings(normalized)
|
||||||
sum := sha256.Sum256([]byte(strings.Join(normalized, "|")))
|
sum := sha256.Sum256([]byte(strings.Join(normalized, "|")))
|
||||||
return OAuthModelMappingsSummary{
|
return OAuthModelAliasSummary{
|
||||||
hash: hex.EncodeToString(sum[:]),
|
hash: hex.EncodeToString(sum[:]),
|
||||||
count: len(normalized),
|
count: len(normalized),
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ func TestLookupAPIKeyUpstreamModel(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAPIKeyModelMappings_ConfigHotReload(t *testing.T) {
|
func TestAPIKeyModelAlias_ConfigHotReload(t *testing.T) {
|
||||||
cfg := &internalconfig.Config{
|
cfg := &internalconfig.Config{
|
||||||
GeminiKey: []internalconfig.GeminiKey{
|
GeminiKey: []internalconfig.GeminiKey{
|
||||||
{
|
{
|
||||||
@@ -82,12 +82,12 @@ func TestAPIKeyModelMappings_ConfigHotReload(t *testing.T) {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
_, _ = mgr.Register(ctx, &Auth{ID: "a1", Provider: "gemini", Attributes: map[string]string{"api_key": "k"}})
|
_, _ = mgr.Register(ctx, &Auth{ID: "a1", Provider: "gemini", Attributes: map[string]string{"api_key": "k"}})
|
||||||
|
|
||||||
// Initial mapping
|
// Initial alias
|
||||||
if resolved := mgr.lookupAPIKeyUpstreamModel("a1", "g25p"); resolved != "gemini-2.5-pro-exp-03-25" {
|
if resolved := mgr.lookupAPIKeyUpstreamModel("a1", "g25p"); resolved != "gemini-2.5-pro-exp-03-25" {
|
||||||
t.Fatalf("before reload: got %q, want %q", resolved, "gemini-2.5-pro-exp-03-25")
|
t.Fatalf("before reload: got %q, want %q", resolved, "gemini-2.5-pro-exp-03-25")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hot reload with new mapping
|
// Hot reload with new alias
|
||||||
mgr.SetConfig(&internalconfig.Config{
|
mgr.SetConfig(&internalconfig.Config{
|
||||||
GeminiKey: []internalconfig.GeminiKey{
|
GeminiKey: []internalconfig.GeminiKey{
|
||||||
{
|
{
|
||||||
@@ -97,13 +97,13 @@ func TestAPIKeyModelMappings_ConfigHotReload(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// New mapping should take effect
|
// New alias should take effect
|
||||||
if resolved := mgr.lookupAPIKeyUpstreamModel("a1", "g25p"); resolved != "gemini-2.5-flash" {
|
if resolved := mgr.lookupAPIKeyUpstreamModel("a1", "g25p"); resolved != "gemini-2.5-flash" {
|
||||||
t.Fatalf("after reload: got %q, want %q", resolved, "gemini-2.5-flash")
|
t.Fatalf("after reload: got %q, want %q", resolved, "gemini-2.5-flash")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAPIKeyModelMappings_MultipleProviders(t *testing.T) {
|
func TestAPIKeyModelAlias_MultipleProviders(t *testing.T) {
|
||||||
cfg := &internalconfig.Config{
|
cfg := &internalconfig.Config{
|
||||||
GeminiKey: []internalconfig.GeminiKey{{APIKey: "gemini-key", Models: []internalconfig.GeminiModel{{Name: "gemini-2.5-pro", Alias: "gp"}}}},
|
GeminiKey: []internalconfig.GeminiKey{{APIKey: "gemini-key", Models: []internalconfig.GeminiModel{{Name: "gemini-2.5-pro", Alias: "gp"}}}},
|
||||||
ClaudeKey: []internalconfig.ClaudeKey{{APIKey: "claude-key", Models: []internalconfig.ClaudeModel{{Name: "claude-sonnet-4", Alias: "cs4"}}}},
|
ClaudeKey: []internalconfig.ClaudeKey{{APIKey: "claude-key", Models: []internalconfig.ClaudeModel{{Name: "claude-sonnet-4", Alias: "cs4"}}}},
|
||||||
@@ -133,7 +133,7 @@ func TestAPIKeyModelMappings_MultipleProviders(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApplyAPIKeyModelMapping(t *testing.T) {
|
func TestApplyAPIKeyModelAlias(t *testing.T) {
|
||||||
cfg := &internalconfig.Config{
|
cfg := &internalconfig.Config{
|
||||||
GeminiKey: []internalconfig.GeminiKey{
|
GeminiKey: []internalconfig.GeminiKey{
|
||||||
{APIKey: "k", Models: []internalconfig.GeminiModel{{Name: "gemini-2.5-pro-exp-03-25", Alias: "g25p"}}},
|
{APIKey: "k", Models: []internalconfig.GeminiModel{{Name: "gemini-2.5-pro-exp-03-25", Alias: "g25p"}}},
|
||||||
@@ -170,7 +170,7 @@ func TestApplyAPIKeyModelMapping(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
resolvedModel := mgr.applyAPIKeyModelMapping(tt.auth, tt.inputModel)
|
resolvedModel := mgr.applyAPIKeyModelAlias(tt.auth, tt.inputModel)
|
||||||
|
|
||||||
if resolvedModel != tt.wantModel {
|
if resolvedModel != tt.wantModel {
|
||||||
t.Errorf("model = %q, want %q", resolvedModel, tt.wantModel)
|
t.Errorf("model = %q, want %q", resolvedModel, tt.wantModel)
|
||||||
@@ -119,17 +119,17 @@ type Manager struct {
|
|||||||
requestRetry atomic.Int32
|
requestRetry atomic.Int32
|
||||||
maxRetryInterval atomic.Int64
|
maxRetryInterval atomic.Int64
|
||||||
|
|
||||||
// modelNameMappings stores global model name alias mappings (alias -> upstream name) keyed by channel.
|
// oauthModelAlias stores global OAuth model alias mappings (alias -> upstream name) keyed by channel.
|
||||||
modelNameMappings atomic.Value
|
oauthModelAlias atomic.Value
|
||||||
|
|
||||||
|
// apiKeyModelAlias caches resolved model alias mappings for API-key auths.
|
||||||
|
// Keyed by auth.ID, value is alias(lower) -> upstream model (including suffix).
|
||||||
|
apiKeyModelAlias atomic.Value
|
||||||
|
|
||||||
// runtimeConfig stores the latest application config for request-time decisions.
|
// runtimeConfig stores the latest application config for request-time decisions.
|
||||||
// It is initialized in NewManager; never Load() before first Store().
|
// It is initialized in NewManager; never Load() before first Store().
|
||||||
runtimeConfig atomic.Value
|
runtimeConfig atomic.Value
|
||||||
|
|
||||||
// apiKeyModelMappings caches resolved model alias mappings for API-key auths.
|
|
||||||
// Keyed by auth.ID, value is alias(lower) -> upstream model (including suffix).
|
|
||||||
apiKeyModelMappings atomic.Value
|
|
||||||
|
|
||||||
// Optional HTTP RoundTripper provider injected by host.
|
// Optional HTTP RoundTripper provider injected by host.
|
||||||
rtProvider RoundTripperProvider
|
rtProvider RoundTripperProvider
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ func NewManager(store Store, selector Selector, hook Hook) *Manager {
|
|||||||
}
|
}
|
||||||
// atomic.Value requires non-nil initial value.
|
// atomic.Value requires non-nil initial value.
|
||||||
manager.runtimeConfig.Store(&internalconfig.Config{})
|
manager.runtimeConfig.Store(&internalconfig.Config{})
|
||||||
manager.apiKeyModelMappings.Store(apiKeyModelMappingTable(nil))
|
manager.apiKeyModelAlias.Store(apiKeyModelAliasTable(nil))
|
||||||
return manager
|
return manager
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ func (m *Manager) SetConfig(cfg *internalconfig.Config) {
|
|||||||
cfg = &internalconfig.Config{}
|
cfg = &internalconfig.Config{}
|
||||||
}
|
}
|
||||||
m.runtimeConfig.Store(cfg)
|
m.runtimeConfig.Store(cfg)
|
||||||
m.rebuildAPIKeyModelMappingsFromRuntimeConfig()
|
m.rebuildAPIKeyModelAliasFromRuntimeConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) lookupAPIKeyUpstreamModel(authID, requestedModel string) string {
|
func (m *Manager) lookupAPIKeyUpstreamModel(authID, requestedModel string) string {
|
||||||
@@ -210,7 +210,7 @@ func (m *Manager) lookupAPIKeyUpstreamModel(authID, requestedModel string) strin
|
|||||||
if requestedModel == "" {
|
if requestedModel == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
table, _ := m.apiKeyModelMappings.Load().(apiKeyModelMappingTable)
|
table, _ := m.apiKeyModelAlias.Load().(apiKeyModelAliasTable)
|
||||||
if table == nil {
|
if table == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -238,7 +238,7 @@ func (m *Manager) lookupAPIKeyUpstreamModel(authID, requestedModel string) strin
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) rebuildAPIKeyModelMappingsFromRuntimeConfig() {
|
func (m *Manager) rebuildAPIKeyModelAliasFromRuntimeConfig() {
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -248,10 +248,10 @@ func (m *Manager) rebuildAPIKeyModelMappingsFromRuntimeConfig() {
|
|||||||
}
|
}
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
m.rebuildAPIKeyModelMappingsLocked(cfg)
|
m.rebuildAPIKeyModelAliasLocked(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) rebuildAPIKeyModelMappingsLocked(cfg *internalconfig.Config) {
|
func (m *Manager) rebuildAPIKeyModelAliasLocked(cfg *internalconfig.Config) {
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -259,7 +259,7 @@ func (m *Manager) rebuildAPIKeyModelMappingsLocked(cfg *internalconfig.Config) {
|
|||||||
cfg = &internalconfig.Config{}
|
cfg = &internalconfig.Config{}
|
||||||
}
|
}
|
||||||
|
|
||||||
out := make(apiKeyModelMappingTable)
|
out := make(apiKeyModelAliasTable)
|
||||||
for _, auth := range m.auths {
|
for _, auth := range m.auths {
|
||||||
if auth == nil {
|
if auth == nil {
|
||||||
continue
|
continue
|
||||||
@@ -277,19 +277,19 @@ func (m *Manager) rebuildAPIKeyModelMappingsLocked(cfg *internalconfig.Config) {
|
|||||||
switch provider {
|
switch provider {
|
||||||
case "gemini":
|
case "gemini":
|
||||||
if entry := resolveGeminiAPIKeyConfig(cfg, auth); entry != nil {
|
if entry := resolveGeminiAPIKeyConfig(cfg, auth); entry != nil {
|
||||||
compileAPIKeyModelMappingsForModels(byAlias, entry.Models)
|
compileAPIKeyModelAliasForModels(byAlias, entry.Models)
|
||||||
}
|
}
|
||||||
case "claude":
|
case "claude":
|
||||||
if entry := resolveClaudeAPIKeyConfig(cfg, auth); entry != nil {
|
if entry := resolveClaudeAPIKeyConfig(cfg, auth); entry != nil {
|
||||||
compileAPIKeyModelMappingsForModels(byAlias, entry.Models)
|
compileAPIKeyModelAliasForModels(byAlias, entry.Models)
|
||||||
}
|
}
|
||||||
case "codex":
|
case "codex":
|
||||||
if entry := resolveCodexAPIKeyConfig(cfg, auth); entry != nil {
|
if entry := resolveCodexAPIKeyConfig(cfg, auth); entry != nil {
|
||||||
compileAPIKeyModelMappingsForModels(byAlias, entry.Models)
|
compileAPIKeyModelAliasForModels(byAlias, entry.Models)
|
||||||
}
|
}
|
||||||
case "vertex":
|
case "vertex":
|
||||||
if entry := resolveVertexAPIKeyConfig(cfg, auth); entry != nil {
|
if entry := resolveVertexAPIKeyConfig(cfg, auth); entry != nil {
|
||||||
compileAPIKeyModelMappingsForModels(byAlias, entry.Models)
|
compileAPIKeyModelAliasForModels(byAlias, entry.Models)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// OpenAI-compat uses config selection from auth.Attributes.
|
// OpenAI-compat uses config selection from auth.Attributes.
|
||||||
@@ -301,7 +301,7 @@ func (m *Manager) rebuildAPIKeyModelMappingsLocked(cfg *internalconfig.Config) {
|
|||||||
}
|
}
|
||||||
if compatName != "" || strings.EqualFold(strings.TrimSpace(auth.Provider), "openai-compatibility") {
|
if compatName != "" || strings.EqualFold(strings.TrimSpace(auth.Provider), "openai-compatibility") {
|
||||||
if entry := resolveOpenAICompatConfig(cfg, providerKey, compatName, auth.Provider); entry != nil {
|
if entry := resolveOpenAICompatConfig(cfg, providerKey, compatName, auth.Provider); entry != nil {
|
||||||
compileAPIKeyModelMappingsForModels(byAlias, entry.Models)
|
compileAPIKeyModelAliasForModels(byAlias, entry.Models)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -311,10 +311,10 @@ func (m *Manager) rebuildAPIKeyModelMappingsLocked(cfg *internalconfig.Config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m.apiKeyModelMappings.Store(out)
|
m.apiKeyModelAlias.Store(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
func compileAPIKeyModelMappingsForModels[T interface {
|
func compileAPIKeyModelAliasForModels[T interface {
|
||||||
GetName() string
|
GetName() string
|
||||||
GetAlias() string
|
GetAlias() string
|
||||||
}](out map[string]string, models []T) {
|
}](out map[string]string, models []T) {
|
||||||
@@ -408,7 +408,7 @@ func (m *Manager) Register(ctx context.Context, auth *Auth) (*Auth, error) {
|
|||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
m.auths[auth.ID] = auth.Clone()
|
m.auths[auth.ID] = auth.Clone()
|
||||||
m.mu.Unlock()
|
m.mu.Unlock()
|
||||||
m.rebuildAPIKeyModelMappingsFromRuntimeConfig()
|
m.rebuildAPIKeyModelAliasFromRuntimeConfig()
|
||||||
_ = m.persist(ctx, auth)
|
_ = m.persist(ctx, auth)
|
||||||
m.hook.OnAuthRegistered(ctx, auth.Clone())
|
m.hook.OnAuthRegistered(ctx, auth.Clone())
|
||||||
return auth.Clone(), nil
|
return auth.Clone(), nil
|
||||||
@@ -427,7 +427,7 @@ func (m *Manager) Update(ctx context.Context, auth *Auth) (*Auth, error) {
|
|||||||
auth.EnsureIndex()
|
auth.EnsureIndex()
|
||||||
m.auths[auth.ID] = auth.Clone()
|
m.auths[auth.ID] = auth.Clone()
|
||||||
m.mu.Unlock()
|
m.mu.Unlock()
|
||||||
m.rebuildAPIKeyModelMappingsFromRuntimeConfig()
|
m.rebuildAPIKeyModelAliasFromRuntimeConfig()
|
||||||
_ = m.persist(ctx, auth)
|
_ = m.persist(ctx, auth)
|
||||||
m.hook.OnAuthUpdated(ctx, auth.Clone())
|
m.hook.OnAuthUpdated(ctx, auth.Clone())
|
||||||
return auth.Clone(), nil
|
return auth.Clone(), nil
|
||||||
@@ -456,7 +456,7 @@ func (m *Manager) Load(ctx context.Context) error {
|
|||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
cfg = &internalconfig.Config{}
|
cfg = &internalconfig.Config{}
|
||||||
}
|
}
|
||||||
m.rebuildAPIKeyModelMappingsLocked(cfg)
|
m.rebuildAPIKeyModelAliasLocked(cfg)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,8 +592,8 @@ func (m *Manager) executeMixedOnce(ctx context.Context, providers []string, req
|
|||||||
}
|
}
|
||||||
execReq := req
|
execReq := req
|
||||||
execReq.Model = rewriteModelForAuth(routeModel, auth)
|
execReq.Model = rewriteModelForAuth(routeModel, auth)
|
||||||
execReq.Model = m.applyOAuthModelMapping(auth, execReq.Model)
|
execReq.Model = m.applyOAuthModelAlias(auth, execReq.Model)
|
||||||
execReq.Model = m.applyAPIKeyModelMapping(auth, execReq.Model)
|
execReq.Model = m.applyAPIKeyModelAlias(auth, execReq.Model)
|
||||||
resp, errExec := executor.Execute(execCtx, auth, execReq, opts)
|
resp, errExec := executor.Execute(execCtx, auth, execReq, opts)
|
||||||
result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil}
|
result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil}
|
||||||
if errExec != nil {
|
if errExec != nil {
|
||||||
@@ -641,8 +641,8 @@ func (m *Manager) executeCountMixedOnce(ctx context.Context, providers []string,
|
|||||||
}
|
}
|
||||||
execReq := req
|
execReq := req
|
||||||
execReq.Model = rewriteModelForAuth(routeModel, auth)
|
execReq.Model = rewriteModelForAuth(routeModel, auth)
|
||||||
execReq.Model = m.applyOAuthModelMapping(auth, execReq.Model)
|
execReq.Model = m.applyOAuthModelAlias(auth, execReq.Model)
|
||||||
execReq.Model = m.applyAPIKeyModelMapping(auth, execReq.Model)
|
execReq.Model = m.applyAPIKeyModelAlias(auth, execReq.Model)
|
||||||
resp, errExec := executor.CountTokens(execCtx, auth, execReq, opts)
|
resp, errExec := executor.CountTokens(execCtx, auth, execReq, opts)
|
||||||
result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil}
|
result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil}
|
||||||
if errExec != nil {
|
if errExec != nil {
|
||||||
@@ -690,8 +690,8 @@ func (m *Manager) executeStreamMixedOnce(ctx context.Context, providers []string
|
|||||||
}
|
}
|
||||||
execReq := req
|
execReq := req
|
||||||
execReq.Model = rewriteModelForAuth(routeModel, auth)
|
execReq.Model = rewriteModelForAuth(routeModel, auth)
|
||||||
execReq.Model = m.applyOAuthModelMapping(auth, execReq.Model)
|
execReq.Model = m.applyOAuthModelAlias(auth, execReq.Model)
|
||||||
execReq.Model = m.applyAPIKeyModelMapping(auth, execReq.Model)
|
execReq.Model = m.applyAPIKeyModelAlias(auth, execReq.Model)
|
||||||
chunks, errStream := executor.ExecuteStream(execCtx, auth, execReq, opts)
|
chunks, errStream := executor.ExecuteStream(execCtx, auth, execReq, opts)
|
||||||
if errStream != nil {
|
if errStream != nil {
|
||||||
rerr := &Error{Message: errStream.Error()}
|
rerr := &Error{Message: errStream.Error()}
|
||||||
@@ -756,8 +756,8 @@ func (m *Manager) executeWithProvider(ctx context.Context, provider string, req
|
|||||||
}
|
}
|
||||||
execReq := req
|
execReq := req
|
||||||
execReq.Model = rewriteModelForAuth(routeModel, auth)
|
execReq.Model = rewriteModelForAuth(routeModel, auth)
|
||||||
execReq.Model = m.applyOAuthModelMapping(auth, execReq.Model)
|
execReq.Model = m.applyOAuthModelAlias(auth, execReq.Model)
|
||||||
execReq.Model = m.applyAPIKeyModelMapping(auth, execReq.Model)
|
execReq.Model = m.applyAPIKeyModelAlias(auth, execReq.Model)
|
||||||
resp, errExec := executor.Execute(execCtx, auth, execReq, opts)
|
resp, errExec := executor.Execute(execCtx, auth, execReq, opts)
|
||||||
result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil}
|
result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil}
|
||||||
if errExec != nil {
|
if errExec != nil {
|
||||||
@@ -805,8 +805,8 @@ func (m *Manager) executeCountWithProvider(ctx context.Context, provider string,
|
|||||||
}
|
}
|
||||||
execReq := req
|
execReq := req
|
||||||
execReq.Model = rewriteModelForAuth(routeModel, auth)
|
execReq.Model = rewriteModelForAuth(routeModel, auth)
|
||||||
execReq.Model = m.applyOAuthModelMapping(auth, execReq.Model)
|
execReq.Model = m.applyOAuthModelAlias(auth, execReq.Model)
|
||||||
execReq.Model = m.applyAPIKeyModelMapping(auth, execReq.Model)
|
execReq.Model = m.applyAPIKeyModelAlias(auth, execReq.Model)
|
||||||
resp, errExec := executor.CountTokens(execCtx, auth, execReq, opts)
|
resp, errExec := executor.CountTokens(execCtx, auth, execReq, opts)
|
||||||
result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil}
|
result := Result{AuthID: auth.ID, Provider: provider, Model: routeModel, Success: errExec == nil}
|
||||||
if errExec != nil {
|
if errExec != nil {
|
||||||
@@ -854,8 +854,8 @@ func (m *Manager) executeStreamWithProvider(ctx context.Context, provider string
|
|||||||
}
|
}
|
||||||
execReq := req
|
execReq := req
|
||||||
execReq.Model = rewriteModelForAuth(routeModel, auth)
|
execReq.Model = rewriteModelForAuth(routeModel, auth)
|
||||||
execReq.Model = m.applyOAuthModelMapping(auth, execReq.Model)
|
execReq.Model = m.applyOAuthModelAlias(auth, execReq.Model)
|
||||||
execReq.Model = m.applyAPIKeyModelMapping(auth, execReq.Model)
|
execReq.Model = m.applyAPIKeyModelAlias(auth, execReq.Model)
|
||||||
chunks, errStream := executor.ExecuteStream(execCtx, auth, execReq, opts)
|
chunks, errStream := executor.ExecuteStream(execCtx, auth, execReq, opts)
|
||||||
if errStream != nil {
|
if errStream != nil {
|
||||||
rerr := &Error{Message: errStream.Error()}
|
rerr := &Error{Message: errStream.Error()}
|
||||||
@@ -908,7 +908,7 @@ func rewriteModelForAuth(model string, auth *Auth) string {
|
|||||||
return strings.TrimPrefix(model, needle)
|
return strings.TrimPrefix(model, needle)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) applyAPIKeyModelMapping(auth *Auth, requestedModel string) string {
|
func (m *Manager) applyAPIKeyModelAlias(auth *Auth, requestedModel string) string {
|
||||||
if m == nil || auth == nil {
|
if m == nil || auth == nil {
|
||||||
return requestedModel
|
return requestedModel
|
||||||
}
|
}
|
||||||
@@ -1079,7 +1079,7 @@ func resolveUpstreamModelForOpenAICompatAPIKey(cfg *internalconfig.Config, auth
|
|||||||
return resolveModelAliasFromConfigModels(requestedModel, asModelAliasEntries(entry.Models))
|
return resolveModelAliasFromConfigModels(requestedModel, asModelAliasEntries(entry.Models))
|
||||||
}
|
}
|
||||||
|
|
||||||
type apiKeyModelMappingTable map[string]map[string]string
|
type apiKeyModelAliasTable map[string]map[string]string
|
||||||
|
|
||||||
func resolveOpenAICompatConfig(cfg *internalconfig.Config, providerKey, compatName, authProvider string) *internalconfig.OpenAICompatibility {
|
func resolveOpenAICompatConfig(cfg *internalconfig.Config, providerKey, compatName, authProvider string) *internalconfig.OpenAICompatibility {
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
@@ -1109,11 +1109,11 @@ func resolveOpenAICompatConfig(cfg *internalconfig.Config, providerKey, compatNa
|
|||||||
func asModelAliasEntries[T interface {
|
func asModelAliasEntries[T interface {
|
||||||
GetName() string
|
GetName() string
|
||||||
GetAlias() string
|
GetAlias() string
|
||||||
}](models []T) []modelMappingEntry {
|
}](models []T) []modelAliasEntry {
|
||||||
if len(models) == 0 {
|
if len(models) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
out := make([]modelMappingEntry, 0, len(models))
|
out := make([]modelAliasEntry, 0, len(models))
|
||||||
for i := range models {
|
for i := range models {
|
||||||
out = append(out, models[i])
|
out = append(out, models[i])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,24 +7,24 @@ import (
|
|||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/thinking"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/thinking"
|
||||||
)
|
)
|
||||||
|
|
||||||
type modelMappingEntry interface {
|
type modelAliasEntry interface {
|
||||||
GetName() string
|
GetName() string
|
||||||
GetAlias() string
|
GetAlias() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type modelNameMappingTable struct {
|
type oauthModelAliasTable struct {
|
||||||
// reverse maps channel -> alias (lower) -> original upstream model name.
|
// reverse maps channel -> alias (lower) -> original upstream model name.
|
||||||
reverse map[string]map[string]string
|
reverse map[string]map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func compileModelNameMappingTable(mappings map[string][]internalconfig.ModelNameMapping) *modelNameMappingTable {
|
func compileOAuthModelAliasTable(aliases map[string][]internalconfig.OAuthModelAlias) *oauthModelAliasTable {
|
||||||
if len(mappings) == 0 {
|
if len(aliases) == 0 {
|
||||||
return &modelNameMappingTable{}
|
return &oauthModelAliasTable{}
|
||||||
}
|
}
|
||||||
out := &modelNameMappingTable{
|
out := &oauthModelAliasTable{
|
||||||
reverse: make(map[string]map[string]string, len(mappings)),
|
reverse: make(map[string]map[string]string, len(aliases)),
|
||||||
}
|
}
|
||||||
for rawChannel, entries := range mappings {
|
for rawChannel, entries := range aliases {
|
||||||
channel := strings.ToLower(strings.TrimSpace(rawChannel))
|
channel := strings.ToLower(strings.TrimSpace(rawChannel))
|
||||||
if channel == "" || len(entries) == 0 {
|
if channel == "" || len(entries) == 0 {
|
||||||
continue
|
continue
|
||||||
@@ -55,24 +55,24 @@ func compileModelNameMappingTable(mappings map[string][]internalconfig.ModelName
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetOAuthModelMappings updates the OAuth model name mapping table used during execution.
|
// SetOAuthModelAlias updates the OAuth model name alias table used during execution.
|
||||||
// The mapping is applied per-auth channel to resolve the upstream model name while keeping the
|
// The alias is applied per-auth channel to resolve the upstream model name while keeping the
|
||||||
// client-visible model name unchanged for translation/response formatting.
|
// client-visible model name unchanged for translation/response formatting.
|
||||||
func (m *Manager) SetOAuthModelMappings(mappings map[string][]internalconfig.ModelNameMapping) {
|
func (m *Manager) SetOAuthModelAlias(aliases map[string][]internalconfig.OAuthModelAlias) {
|
||||||
if m == nil {
|
if m == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
table := compileModelNameMappingTable(mappings)
|
table := compileOAuthModelAliasTable(aliases)
|
||||||
// atomic.Value requires non-nil store values.
|
// atomic.Value requires non-nil store values.
|
||||||
if table == nil {
|
if table == nil {
|
||||||
table = &modelNameMappingTable{}
|
table = &oauthModelAliasTable{}
|
||||||
}
|
}
|
||||||
m.modelNameMappings.Store(table)
|
m.oauthModelAlias.Store(table)
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyOAuthModelMapping resolves the upstream model from OAuth model mappings.
|
// applyOAuthModelAlias resolves the upstream model from OAuth model alias.
|
||||||
// If a mapping exists, the returned model is the upstream model.
|
// If an alias exists, the returned model is the upstream model.
|
||||||
func (m *Manager) applyOAuthModelMapping(auth *Auth, requestedModel string) string {
|
func (m *Manager) applyOAuthModelAlias(auth *Auth, requestedModel string) string {
|
||||||
upstreamModel := m.resolveOAuthUpstreamModel(auth, requestedModel)
|
upstreamModel := m.resolveOAuthUpstreamModel(auth, requestedModel)
|
||||||
if upstreamModel == "" {
|
if upstreamModel == "" {
|
||||||
return requestedModel
|
return requestedModel
|
||||||
@@ -80,7 +80,7 @@ func (m *Manager) applyOAuthModelMapping(auth *Auth, requestedModel string) stri
|
|||||||
return upstreamModel
|
return upstreamModel
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveModelAliasFromConfigModels(requestedModel string, models []modelMappingEntry) string {
|
func resolveModelAliasFromConfigModels(requestedModel string, models []modelAliasEntry) string {
|
||||||
requestedModel = strings.TrimSpace(requestedModel)
|
requestedModel = strings.TrimSpace(requestedModel)
|
||||||
if requestedModel == "" {
|
if requestedModel == "" {
|
||||||
return ""
|
return ""
|
||||||
@@ -131,18 +131,18 @@ func resolveModelAliasFromConfigModels(requestedModel string, models []modelMapp
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveOAuthUpstreamModel resolves the upstream model name from OAuth model mappings.
|
// resolveOAuthUpstreamModel resolves the upstream model name from OAuth model alias.
|
||||||
// If a mapping exists, returns the original (upstream) model name that corresponds
|
// If an alias exists, returns the original (upstream) model name that corresponds
|
||||||
// to the requested alias.
|
// to the requested alias.
|
||||||
//
|
//
|
||||||
// If the requested model contains a thinking suffix (e.g., "gemini-2.5-pro(8192)"),
|
// If the requested model contains a thinking suffix (e.g., "gemini-2.5-pro(8192)"),
|
||||||
// the suffix is preserved in the returned model name. However, if the mapping's
|
// the suffix is preserved in the returned model name. However, if the alias's
|
||||||
// original name already contains a suffix, the config suffix takes priority.
|
// original name already contains a suffix, the config suffix takes priority.
|
||||||
func (m *Manager) resolveOAuthUpstreamModel(auth *Auth, requestedModel string) string {
|
func (m *Manager) resolveOAuthUpstreamModel(auth *Auth, requestedModel string) string {
|
||||||
return resolveUpstreamModelFromMappingTable(m, auth, requestedModel, modelMappingChannel(auth))
|
return resolveUpstreamModelFromAliasTable(m, auth, requestedModel, modelAliasChannel(auth))
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveUpstreamModelFromMappingTable(m *Manager, auth *Auth, requestedModel, channel string) string {
|
func resolveUpstreamModelFromAliasTable(m *Manager, auth *Auth, requestedModel, channel string) string {
|
||||||
if m == nil || auth == nil {
|
if m == nil || auth == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -160,8 +160,8 @@ func resolveUpstreamModelFromMappingTable(m *Manager, auth *Auth, requestedModel
|
|||||||
candidates = append(candidates, requestedModel)
|
candidates = append(candidates, requestedModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
raw := m.modelNameMappings.Load()
|
raw := m.oauthModelAlias.Load()
|
||||||
table, _ := raw.(*modelNameMappingTable)
|
table, _ := raw.(*oauthModelAliasTable)
|
||||||
if table == nil || table.reverse == nil {
|
if table == nil || table.reverse == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -197,10 +197,10 @@ func resolveUpstreamModelFromMappingTable(m *Manager, auth *Auth, requestedModel
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// modelMappingChannel extracts the OAuth model mapping channel from an Auth object.
|
// modelAliasChannel extracts the OAuth model alias channel from an Auth object.
|
||||||
// It determines the provider and auth kind from the Auth's attributes and delegates
|
// It determines the provider and auth kind from the Auth's attributes and delegates
|
||||||
// to OAuthModelMappingChannel for the actual channel resolution.
|
// to OAuthModelAliasChannel for the actual channel resolution.
|
||||||
func modelMappingChannel(auth *Auth) string {
|
func modelAliasChannel(auth *Auth) string {
|
||||||
if auth == nil {
|
if auth == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -214,20 +214,20 @@ func modelMappingChannel(auth *Auth) string {
|
|||||||
authKind = "apikey"
|
authKind = "apikey"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return OAuthModelMappingChannel(provider, authKind)
|
return OAuthModelAliasChannel(provider, authKind)
|
||||||
}
|
}
|
||||||
|
|
||||||
// OAuthModelMappingChannel returns the OAuth model mapping channel name for a given provider
|
// OAuthModelAliasChannel returns the OAuth model alias channel name for a given provider
|
||||||
// and auth kind. Returns empty string if the provider/authKind combination doesn't support
|
// and auth kind. Returns empty string if the provider/authKind combination doesn't support
|
||||||
// OAuth model mappings (e.g., API key authentication).
|
// OAuth model alias (e.g., API key authentication).
|
||||||
//
|
//
|
||||||
// Supported channels: gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow.
|
// Supported channels: gemini-cli, vertex, aistudio, antigravity, claude, codex, qwen, iflow.
|
||||||
func OAuthModelMappingChannel(provider, authKind string) string {
|
func OAuthModelAliasChannel(provider, authKind string) string {
|
||||||
provider = strings.ToLower(strings.TrimSpace(provider))
|
provider = strings.ToLower(strings.TrimSpace(provider))
|
||||||
authKind = strings.ToLower(strings.TrimSpace(authKind))
|
authKind = strings.ToLower(strings.TrimSpace(authKind))
|
||||||
switch provider {
|
switch provider {
|
||||||
case "gemini":
|
case "gemini":
|
||||||
// gemini provider uses gemini-api-key config, not oauth-model-mappings.
|
// gemini provider uses gemini-api-key config, not oauth-model-alias.
|
||||||
// OAuth-based gemini auth is converted to "gemini-cli" by the synthesizer.
|
// OAuth-based gemini auth is converted to "gemini-cli" by the synthesizer.
|
||||||
return ""
|
return ""
|
||||||
case "vertex":
|
case "vertex":
|
||||||
@@ -10,15 +10,15 @@ func TestResolveOAuthUpstreamModel_SuffixPreservation(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
mappings map[string][]internalconfig.ModelNameMapping
|
aliases map[string][]internalconfig.OAuthModelAlias
|
||||||
channel string
|
channel string
|
||||||
input string
|
input string
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "numeric suffix preserved",
|
name: "numeric suffix preserved",
|
||||||
mappings: map[string][]internalconfig.ModelNameMapping{
|
aliases: map[string][]internalconfig.OAuthModelAlias{
|
||||||
"gemini-cli": {{Name: "gemini-2.5-pro-exp-03-25", Alias: "gemini-2.5-pro"}},
|
"gemini-cli": {{Name: "gemini-2.5-pro-exp-03-25", Alias: "gemini-2.5-pro"}},
|
||||||
},
|
},
|
||||||
channel: "gemini-cli",
|
channel: "gemini-cli",
|
||||||
@@ -27,7 +27,7 @@ func TestResolveOAuthUpstreamModel_SuffixPreservation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "level suffix preserved",
|
name: "level suffix preserved",
|
||||||
mappings: map[string][]internalconfig.ModelNameMapping{
|
aliases: map[string][]internalconfig.OAuthModelAlias{
|
||||||
"claude": {{Name: "claude-sonnet-4-5-20250514", Alias: "claude-sonnet-4-5"}},
|
"claude": {{Name: "claude-sonnet-4-5-20250514", Alias: "claude-sonnet-4-5"}},
|
||||||
},
|
},
|
||||||
channel: "claude",
|
channel: "claude",
|
||||||
@@ -36,7 +36,7 @@ func TestResolveOAuthUpstreamModel_SuffixPreservation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no suffix unchanged",
|
name: "no suffix unchanged",
|
||||||
mappings: map[string][]internalconfig.ModelNameMapping{
|
aliases: map[string][]internalconfig.OAuthModelAlias{
|
||||||
"gemini-cli": {{Name: "gemini-2.5-pro-exp-03-25", Alias: "gemini-2.5-pro"}},
|
"gemini-cli": {{Name: "gemini-2.5-pro-exp-03-25", Alias: "gemini-2.5-pro"}},
|
||||||
},
|
},
|
||||||
channel: "gemini-cli",
|
channel: "gemini-cli",
|
||||||
@@ -45,7 +45,7 @@ func TestResolveOAuthUpstreamModel_SuffixPreservation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "config suffix takes priority",
|
name: "config suffix takes priority",
|
||||||
mappings: map[string][]internalconfig.ModelNameMapping{
|
aliases: map[string][]internalconfig.OAuthModelAlias{
|
||||||
"claude": {{Name: "claude-sonnet-4-5-20250514(low)", Alias: "claude-sonnet-4-5"}},
|
"claude": {{Name: "claude-sonnet-4-5-20250514(low)", Alias: "claude-sonnet-4-5"}},
|
||||||
},
|
},
|
||||||
channel: "claude",
|
channel: "claude",
|
||||||
@@ -54,7 +54,7 @@ func TestResolveOAuthUpstreamModel_SuffixPreservation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "auto suffix preserved",
|
name: "auto suffix preserved",
|
||||||
mappings: map[string][]internalconfig.ModelNameMapping{
|
aliases: map[string][]internalconfig.OAuthModelAlias{
|
||||||
"gemini-cli": {{Name: "gemini-2.5-pro-exp-03-25", Alias: "gemini-2.5-pro"}},
|
"gemini-cli": {{Name: "gemini-2.5-pro-exp-03-25", Alias: "gemini-2.5-pro"}},
|
||||||
},
|
},
|
||||||
channel: "gemini-cli",
|
channel: "gemini-cli",
|
||||||
@@ -63,7 +63,7 @@ func TestResolveOAuthUpstreamModel_SuffixPreservation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "none suffix preserved",
|
name: "none suffix preserved",
|
||||||
mappings: map[string][]internalconfig.ModelNameMapping{
|
aliases: map[string][]internalconfig.OAuthModelAlias{
|
||||||
"gemini-cli": {{Name: "gemini-2.5-pro-exp-03-25", Alias: "gemini-2.5-pro"}},
|
"gemini-cli": {{Name: "gemini-2.5-pro-exp-03-25", Alias: "gemini-2.5-pro"}},
|
||||||
},
|
},
|
||||||
channel: "gemini-cli",
|
channel: "gemini-cli",
|
||||||
@@ -72,7 +72,7 @@ func TestResolveOAuthUpstreamModel_SuffixPreservation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "case insensitive alias lookup with suffix",
|
name: "case insensitive alias lookup with suffix",
|
||||||
mappings: map[string][]internalconfig.ModelNameMapping{
|
aliases: map[string][]internalconfig.OAuthModelAlias{
|
||||||
"gemini-cli": {{Name: "gemini-2.5-pro-exp-03-25", Alias: "Gemini-2.5-Pro"}},
|
"gemini-cli": {{Name: "gemini-2.5-pro-exp-03-25", Alias: "Gemini-2.5-Pro"}},
|
||||||
},
|
},
|
||||||
channel: "gemini-cli",
|
channel: "gemini-cli",
|
||||||
@@ -80,8 +80,8 @@ func TestResolveOAuthUpstreamModel_SuffixPreservation(t *testing.T) {
|
|||||||
want: "gemini-2.5-pro-exp-03-25(high)",
|
want: "gemini-2.5-pro-exp-03-25(high)",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no mapping returns empty",
|
name: "no alias returns empty",
|
||||||
mappings: map[string][]internalconfig.ModelNameMapping{
|
aliases: map[string][]internalconfig.OAuthModelAlias{
|
||||||
"gemini-cli": {{Name: "gemini-2.5-pro-exp-03-25", Alias: "gemini-2.5-pro"}},
|
"gemini-cli": {{Name: "gemini-2.5-pro-exp-03-25", Alias: "gemini-2.5-pro"}},
|
||||||
},
|
},
|
||||||
channel: "gemini-cli",
|
channel: "gemini-cli",
|
||||||
@@ -90,7 +90,7 @@ func TestResolveOAuthUpstreamModel_SuffixPreservation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "wrong channel returns empty",
|
name: "wrong channel returns empty",
|
||||||
mappings: map[string][]internalconfig.ModelNameMapping{
|
aliases: map[string][]internalconfig.OAuthModelAlias{
|
||||||
"gemini-cli": {{Name: "gemini-2.5-pro-exp-03-25", Alias: "gemini-2.5-pro"}},
|
"gemini-cli": {{Name: "gemini-2.5-pro-exp-03-25", Alias: "gemini-2.5-pro"}},
|
||||||
},
|
},
|
||||||
channel: "claude",
|
channel: "claude",
|
||||||
@@ -99,7 +99,7 @@ func TestResolveOAuthUpstreamModel_SuffixPreservation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty suffix filtered out",
|
name: "empty suffix filtered out",
|
||||||
mappings: map[string][]internalconfig.ModelNameMapping{
|
aliases: map[string][]internalconfig.OAuthModelAlias{
|
||||||
"gemini-cli": {{Name: "gemini-2.5-pro-exp-03-25", Alias: "gemini-2.5-pro"}},
|
"gemini-cli": {{Name: "gemini-2.5-pro-exp-03-25", Alias: "gemini-2.5-pro"}},
|
||||||
},
|
},
|
||||||
channel: "gemini-cli",
|
channel: "gemini-cli",
|
||||||
@@ -108,7 +108,7 @@ func TestResolveOAuthUpstreamModel_SuffixPreservation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "incomplete suffix treated as no suffix",
|
name: "incomplete suffix treated as no suffix",
|
||||||
mappings: map[string][]internalconfig.ModelNameMapping{
|
aliases: map[string][]internalconfig.OAuthModelAlias{
|
||||||
"gemini-cli": {{Name: "gemini-2.5-pro-exp-03-25", Alias: "gemini-2.5-pro(high"}},
|
"gemini-cli": {{Name: "gemini-2.5-pro-exp-03-25", Alias: "gemini-2.5-pro(high"}},
|
||||||
},
|
},
|
||||||
channel: "gemini-cli",
|
channel: "gemini-cli",
|
||||||
@@ -123,7 +123,7 @@ func TestResolveOAuthUpstreamModel_SuffixPreservation(t *testing.T) {
|
|||||||
|
|
||||||
mgr := NewManager(nil, nil, nil)
|
mgr := NewManager(nil, nil, nil)
|
||||||
mgr.SetConfig(&internalconfig.Config{})
|
mgr.SetConfig(&internalconfig.Config{})
|
||||||
mgr.SetOAuthModelMappings(tt.mappings)
|
mgr.SetOAuthModelAlias(tt.aliases)
|
||||||
|
|
||||||
auth := createAuthForChannel(tt.channel)
|
auth := createAuthForChannel(tt.channel)
|
||||||
got := mgr.resolveOAuthUpstreamModel(auth, tt.input)
|
got := mgr.resolveOAuthUpstreamModel(auth, tt.input)
|
||||||
@@ -157,21 +157,21 @@ func createAuthForChannel(channel string) *Auth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApplyOAuthModelMapping_SuffixPreservation(t *testing.T) {
|
func TestApplyOAuthModelAlias_SuffixPreservation(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
mappings := map[string][]internalconfig.ModelNameMapping{
|
aliases := map[string][]internalconfig.OAuthModelAlias{
|
||||||
"gemini-cli": {{Name: "gemini-2.5-pro-exp-03-25", Alias: "gemini-2.5-pro"}},
|
"gemini-cli": {{Name: "gemini-2.5-pro-exp-03-25", Alias: "gemini-2.5-pro"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
mgr := NewManager(nil, nil, nil)
|
mgr := NewManager(nil, nil, nil)
|
||||||
mgr.SetConfig(&internalconfig.Config{})
|
mgr.SetConfig(&internalconfig.Config{})
|
||||||
mgr.SetOAuthModelMappings(mappings)
|
mgr.SetOAuthModelAlias(aliases)
|
||||||
|
|
||||||
auth := &Auth{ID: "test-auth-id", Provider: "gemini-cli"}
|
auth := &Auth{ID: "test-auth-id", Provider: "gemini-cli"}
|
||||||
|
|
||||||
resolvedModel := mgr.applyOAuthModelMapping(auth, "gemini-2.5-pro(8192)")
|
resolvedModel := mgr.applyOAuthModelAlias(auth, "gemini-2.5-pro(8192)")
|
||||||
if resolvedModel != "gemini-2.5-pro-exp-03-25(8192)" {
|
if resolvedModel != "gemini-2.5-pro-exp-03-25(8192)" {
|
||||||
t.Errorf("applyOAuthModelMapping() model = %q, want %q", resolvedModel, "gemini-2.5-pro-exp-03-25(8192)")
|
t.Errorf("applyOAuthModelAlias() model = %q, want %q", resolvedModel, "gemini-2.5-pro-exp-03-25(8192)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -216,7 +216,7 @@ func (b *Builder) Build() (*Service, error) {
|
|||||||
// Attach a default RoundTripper provider so providers can opt-in per-auth transports.
|
// Attach a default RoundTripper provider so providers can opt-in per-auth transports.
|
||||||
coreManager.SetRoundTripperProvider(newDefaultRoundTripperProvider())
|
coreManager.SetRoundTripperProvider(newDefaultRoundTripperProvider())
|
||||||
coreManager.SetConfig(b.cfg)
|
coreManager.SetConfig(b.cfg)
|
||||||
coreManager.SetOAuthModelMappings(b.cfg.OAuthModelMappings)
|
coreManager.SetOAuthModelAlias(b.cfg.OAuthModelAlias)
|
||||||
|
|
||||||
service := &Service{
|
service := &Service{
|
||||||
cfg: b.cfg,
|
cfg: b.cfg,
|
||||||
|
|||||||
@@ -554,7 +554,7 @@ func (s *Service) Run(ctx context.Context) error {
|
|||||||
s.cfgMu.Unlock()
|
s.cfgMu.Unlock()
|
||||||
if s.coreManager != nil {
|
if s.coreManager != nil {
|
||||||
s.coreManager.SetConfig(newCfg)
|
s.coreManager.SetConfig(newCfg)
|
||||||
s.coreManager.SetOAuthModelMappings(newCfg.OAuthModelMappings)
|
s.coreManager.SetOAuthModelAlias(newCfg.OAuthModelAlias)
|
||||||
}
|
}
|
||||||
s.rebindExecutors()
|
s.rebindExecutors()
|
||||||
}
|
}
|
||||||
@@ -849,7 +849,7 @@ func (s *Service) registerModelsForAuth(a *coreauth.Auth) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
models = applyOAuthModelMappings(s.cfg, provider, authKind, models)
|
models = applyOAuthModelAlias(s.cfg, provider, authKind, models)
|
||||||
if len(models) > 0 {
|
if len(models) > 0 {
|
||||||
key := provider
|
key := provider
|
||||||
if key == "" {
|
if key == "" {
|
||||||
@@ -1222,28 +1222,28 @@ func rewriteModelInfoName(name, oldID, newID string) string {
|
|||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyOAuthModelMappings(cfg *config.Config, provider, authKind string, models []*ModelInfo) []*ModelInfo {
|
func applyOAuthModelAlias(cfg *config.Config, provider, authKind string, models []*ModelInfo) []*ModelInfo {
|
||||||
if cfg == nil || len(models) == 0 {
|
if cfg == nil || len(models) == 0 {
|
||||||
return models
|
return models
|
||||||
}
|
}
|
||||||
channel := coreauth.OAuthModelMappingChannel(provider, authKind)
|
channel := coreauth.OAuthModelAliasChannel(provider, authKind)
|
||||||
if channel == "" || len(cfg.OAuthModelMappings) == 0 {
|
if channel == "" || len(cfg.OAuthModelAlias) == 0 {
|
||||||
return models
|
return models
|
||||||
}
|
}
|
||||||
mappings := cfg.OAuthModelMappings[channel]
|
aliases := cfg.OAuthModelAlias[channel]
|
||||||
if len(mappings) == 0 {
|
if len(aliases) == 0 {
|
||||||
return models
|
return models
|
||||||
}
|
}
|
||||||
|
|
||||||
type mappingEntry struct {
|
type aliasEntry struct {
|
||||||
alias string
|
alias string
|
||||||
fork bool
|
fork bool
|
||||||
}
|
}
|
||||||
|
|
||||||
forward := make(map[string][]mappingEntry, len(mappings))
|
forward := make(map[string][]aliasEntry, len(aliases))
|
||||||
for i := range mappings {
|
for i := range aliases {
|
||||||
name := strings.TrimSpace(mappings[i].Name)
|
name := strings.TrimSpace(aliases[i].Name)
|
||||||
alias := strings.TrimSpace(mappings[i].Alias)
|
alias := strings.TrimSpace(aliases[i].Alias)
|
||||||
if name == "" || alias == "" {
|
if name == "" || alias == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1251,7 +1251,7 @@ func applyOAuthModelMappings(cfg *config.Config, provider, authKind string, mode
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
key := strings.ToLower(name)
|
key := strings.ToLower(name)
|
||||||
forward[key] = append(forward[key], mappingEntry{alias: alias, fork: mappings[i].Fork})
|
forward[key] = append(forward[key], aliasEntry{alias: alias, fork: aliases[i].Fork})
|
||||||
}
|
}
|
||||||
if len(forward) == 0 {
|
if len(forward) == 0 {
|
||||||
return models
|
return models
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
"github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestApplyOAuthModelMappings_Rename(t *testing.T) {
|
func TestApplyOAuthModelAlias_Rename(t *testing.T) {
|
||||||
cfg := &config.Config{
|
cfg := &config.Config{
|
||||||
OAuthModelMappings: map[string][]config.ModelNameMapping{
|
OAuthModelAlias: map[string][]config.OAuthModelAlias{
|
||||||
"codex": {
|
"codex": {
|
||||||
{Name: "gpt-5", Alias: "g5"},
|
{Name: "gpt-5", Alias: "g5"},
|
||||||
},
|
},
|
||||||
@@ -18,7 +18,7 @@ func TestApplyOAuthModelMappings_Rename(t *testing.T) {
|
|||||||
{ID: "gpt-5", Name: "models/gpt-5"},
|
{ID: "gpt-5", Name: "models/gpt-5"},
|
||||||
}
|
}
|
||||||
|
|
||||||
out := applyOAuthModelMappings(cfg, "codex", "oauth", models)
|
out := applyOAuthModelAlias(cfg, "codex", "oauth", models)
|
||||||
if len(out) != 1 {
|
if len(out) != 1 {
|
||||||
t.Fatalf("expected 1 model, got %d", len(out))
|
t.Fatalf("expected 1 model, got %d", len(out))
|
||||||
}
|
}
|
||||||
@@ -30,9 +30,9 @@ func TestApplyOAuthModelMappings_Rename(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApplyOAuthModelMappings_ForkAddsAlias(t *testing.T) {
|
func TestApplyOAuthModelAlias_ForkAddsAlias(t *testing.T) {
|
||||||
cfg := &config.Config{
|
cfg := &config.Config{
|
||||||
OAuthModelMappings: map[string][]config.ModelNameMapping{
|
OAuthModelAlias: map[string][]config.OAuthModelAlias{
|
||||||
"codex": {
|
"codex": {
|
||||||
{Name: "gpt-5", Alias: "g5", Fork: true},
|
{Name: "gpt-5", Alias: "g5", Fork: true},
|
||||||
},
|
},
|
||||||
@@ -42,7 +42,7 @@ func TestApplyOAuthModelMappings_ForkAddsAlias(t *testing.T) {
|
|||||||
{ID: "gpt-5", Name: "models/gpt-5"},
|
{ID: "gpt-5", Name: "models/gpt-5"},
|
||||||
}
|
}
|
||||||
|
|
||||||
out := applyOAuthModelMappings(cfg, "codex", "oauth", models)
|
out := applyOAuthModelAlias(cfg, "codex", "oauth", models)
|
||||||
if len(out) != 2 {
|
if len(out) != 2 {
|
||||||
t.Fatalf("expected 2 models, got %d", len(out))
|
t.Fatalf("expected 2 models, got %d", len(out))
|
||||||
}
|
}
|
||||||
@@ -57,9 +57,9 @@ func TestApplyOAuthModelMappings_ForkAddsAlias(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApplyOAuthModelMappings_ForkAddsMultipleAliases(t *testing.T) {
|
func TestApplyOAuthModelAlias_ForkAddsMultipleAliases(t *testing.T) {
|
||||||
cfg := &config.Config{
|
cfg := &config.Config{
|
||||||
OAuthModelMappings: map[string][]config.ModelNameMapping{
|
OAuthModelAlias: map[string][]config.OAuthModelAlias{
|
||||||
"codex": {
|
"codex": {
|
||||||
{Name: "gpt-5", Alias: "g5", Fork: true},
|
{Name: "gpt-5", Alias: "g5", Fork: true},
|
||||||
{Name: "gpt-5", Alias: "g5-2", Fork: true},
|
{Name: "gpt-5", Alias: "g5-2", Fork: true},
|
||||||
@@ -70,7 +70,7 @@ func TestApplyOAuthModelMappings_ForkAddsMultipleAliases(t *testing.T) {
|
|||||||
{ID: "gpt-5", Name: "models/gpt-5"},
|
{ID: "gpt-5", Name: "models/gpt-5"},
|
||||||
}
|
}
|
||||||
|
|
||||||
out := applyOAuthModelMappings(cfg, "codex", "oauth", models)
|
out := applyOAuthModelAlias(cfg, "codex", "oauth", models)
|
||||||
if len(out) != 3 {
|
if len(out) != 3 {
|
||||||
t.Fatalf("expected 3 models, got %d", len(out))
|
t.Fatalf("expected 3 models, got %d", len(out))
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,7 @@ type StreamingConfig = internalconfig.StreamingConfig
|
|||||||
type TLSConfig = internalconfig.TLSConfig
|
type TLSConfig = internalconfig.TLSConfig
|
||||||
type RemoteManagement = internalconfig.RemoteManagement
|
type RemoteManagement = internalconfig.RemoteManagement
|
||||||
type AmpCode = internalconfig.AmpCode
|
type AmpCode = internalconfig.AmpCode
|
||||||
type ModelNameMapping = internalconfig.ModelNameMapping
|
type OAuthModelAlias = internalconfig.OAuthModelAlias
|
||||||
type PayloadConfig = internalconfig.PayloadConfig
|
type PayloadConfig = internalconfig.PayloadConfig
|
||||||
type PayloadRule = internalconfig.PayloadRule
|
type PayloadRule = internalconfig.PayloadRule
|
||||||
type PayloadModelRule = internalconfig.PayloadModelRule
|
type PayloadModelRule = internalconfig.PayloadModelRule
|
||||||
|
|||||||
Reference in New Issue
Block a user