mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-02 20:40:52 +08:00
refactor(sdk/auth): rename manager.go to conductor.go
This commit is contained in:
@@ -279,26 +279,26 @@ func (m *AmpModule) hasModelMappingsChanged(old *config.AmpCode, new *config.Amp
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build map for efficient and robust comparison
|
// Build map for efficient and robust comparison
|
||||||
type mappingInfo struct {
|
type mappingInfo struct {
|
||||||
to string
|
to string
|
||||||
regex bool
|
regex bool
|
||||||
}
|
}
|
||||||
oldMap := make(map[string]mappingInfo, len(old.ModelMappings))
|
oldMap := make(map[string]mappingInfo, len(old.ModelMappings))
|
||||||
for _, mapping := range old.ModelMappings {
|
for _, mapping := range old.ModelMappings {
|
||||||
oldMap[strings.TrimSpace(mapping.From)] = mappingInfo{
|
oldMap[strings.TrimSpace(mapping.From)] = mappingInfo{
|
||||||
to: strings.TrimSpace(mapping.To),
|
to: strings.TrimSpace(mapping.To),
|
||||||
regex: mapping.Regex,
|
regex: mapping.Regex,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, mapping := range new.ModelMappings {
|
for _, mapping := range new.ModelMappings {
|
||||||
from := strings.TrimSpace(mapping.From)
|
from := strings.TrimSpace(mapping.From)
|
||||||
to := strings.TrimSpace(mapping.To)
|
to := strings.TrimSpace(mapping.To)
|
||||||
if oldVal, exists := oldMap[from]; !exists || oldVal.to != to || oldVal.regex != mapping.Regex {
|
if oldVal, exists := oldMap[from]; !exists || oldVal.to != to || oldVal.regex != mapping.Regex {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
package amp
|
package amp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -27,15 +27,15 @@ type ModelMapper interface {
|
|||||||
// DefaultModelMapper implements ModelMapper with thread-safe mapping storage.
|
// DefaultModelMapper implements ModelMapper with thread-safe mapping storage.
|
||||||
type DefaultModelMapper struct {
|
type DefaultModelMapper struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
mappings map[string]string // exact: from -> to (normalized lowercase keys)
|
mappings map[string]string // exact: from -> to (normalized lowercase keys)
|
||||||
regexps []regexMapping // regex rules evaluated in order
|
regexps []regexMapping // regex rules evaluated in order
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewModelMapper creates a new model mapper with the given initial mappings.
|
// NewModelMapper creates a new model mapper with the given initial mappings.
|
||||||
func NewModelMapper(mappings []config.AmpModelMapping) *DefaultModelMapper {
|
func NewModelMapper(mappings []config.AmpModelMapping) *DefaultModelMapper {
|
||||||
m := &DefaultModelMapper{
|
m := &DefaultModelMapper{
|
||||||
mappings: make(map[string]string),
|
mappings: make(map[string]string),
|
||||||
regexps: nil,
|
regexps: nil,
|
||||||
}
|
}
|
||||||
m.UpdateMappings(mappings)
|
m.UpdateMappings(mappings)
|
||||||
return m
|
return m
|
||||||
@@ -58,18 +58,18 @@ func (m *DefaultModelMapper) MapModel(requestedModel string) string {
|
|||||||
// Check for direct mapping
|
// Check for direct mapping
|
||||||
targetModel, exists := m.mappings[normalizedRequest]
|
targetModel, exists := m.mappings[normalizedRequest]
|
||||||
if !exists {
|
if !exists {
|
||||||
// Try regex mappings in order
|
// Try regex mappings in order
|
||||||
base, _ := util.NormalizeThinkingModel(requestedModel)
|
base, _ := util.NormalizeThinkingModel(requestedModel)
|
||||||
for _, rm := range m.regexps {
|
for _, rm := range m.regexps {
|
||||||
if rm.re.MatchString(requestedModel) || (base != "" && rm.re.MatchString(base)) {
|
if rm.re.MatchString(requestedModel) || (base != "" && rm.re.MatchString(base)) {
|
||||||
targetModel = rm.to
|
targetModel = rm.to
|
||||||
exists = true
|
exists = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !exists {
|
if !exists {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify target model has available providers
|
// Verify target model has available providers
|
||||||
@@ -91,8 +91,8 @@ func (m *DefaultModelMapper) UpdateMappings(mappings []config.AmpModelMapping) {
|
|||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
// Clear and rebuild mappings
|
// Clear and rebuild mappings
|
||||||
m.mappings = make(map[string]string, len(mappings))
|
m.mappings = make(map[string]string, len(mappings))
|
||||||
m.regexps = make([]regexMapping, 0, len(mappings))
|
m.regexps = make([]regexMapping, 0, len(mappings))
|
||||||
|
|
||||||
for _, mapping := range mappings {
|
for _, mapping := range mappings {
|
||||||
from := strings.TrimSpace(mapping.From)
|
from := strings.TrimSpace(mapping.From)
|
||||||
@@ -103,30 +103,30 @@ func (m *DefaultModelMapper) UpdateMappings(mappings []config.AmpModelMapping) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if mapping.Regex {
|
if mapping.Regex {
|
||||||
// Compile case-insensitive regex; wrap with (?i) to match behavior of exact lookups
|
// Compile case-insensitive regex; wrap with (?i) to match behavior of exact lookups
|
||||||
pattern := "(?i)" + from
|
pattern := "(?i)" + from
|
||||||
re, err := regexp.Compile(pattern)
|
re, err := regexp.Compile(pattern)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("amp model mapping: invalid regex %q: %v", from, err)
|
log.Warnf("amp model mapping: invalid regex %q: %v", from, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m.regexps = append(m.regexps, regexMapping{re: re, to: to})
|
m.regexps = append(m.regexps, regexMapping{re: re, to: to})
|
||||||
log.Debugf("amp model regex mapping registered: /%s/ -> %s", from, to)
|
log.Debugf("amp model regex mapping registered: /%s/ -> %s", from, to)
|
||||||
} else {
|
} else {
|
||||||
// Store with normalized lowercase key for case-insensitive lookup
|
// Store with normalized lowercase key for case-insensitive lookup
|
||||||
normalizedFrom := strings.ToLower(from)
|
normalizedFrom := strings.ToLower(from)
|
||||||
m.mappings[normalizedFrom] = to
|
m.mappings[normalizedFrom] = to
|
||||||
log.Debugf("amp model mapping registered: %s -> %s", from, to)
|
log.Debugf("amp model mapping registered: %s -> %s", from, to)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(m.mappings) > 0 {
|
if len(m.mappings) > 0 {
|
||||||
log.Infof("amp model mapping: loaded %d mapping(s)", len(m.mappings))
|
log.Infof("amp model mapping: loaded %d mapping(s)", len(m.mappings))
|
||||||
}
|
}
|
||||||
if n := len(m.regexps); n > 0 {
|
if n := len(m.regexps); n > 0 {
|
||||||
log.Infof("amp model mapping: loaded %d regex mapping(s)", n)
|
log.Infof("amp model mapping: loaded %d regex mapping(s)", n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMappings returns a copy of current mappings (for debugging/status).
|
// GetMappings returns a copy of current mappings (for debugging/status).
|
||||||
@@ -142,6 +142,6 @@ func (m *DefaultModelMapper) GetMappings() map[string]string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type regexMapping struct {
|
type regexMapping struct {
|
||||||
re *regexp.Regexp
|
re *regexp.Regexp
|
||||||
to string
|
to string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,79 +205,79 @@ func TestModelMapper_GetMappings_ReturnsCopy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestModelMapper_Regex_MatchBaseWithoutParens(t *testing.T) {
|
func TestModelMapper_Regex_MatchBaseWithoutParens(t *testing.T) {
|
||||||
reg := registry.GetGlobalRegistry()
|
reg := registry.GetGlobalRegistry()
|
||||||
reg.RegisterClient("test-client-regex-1", "gemini", []*registry.ModelInfo{
|
reg.RegisterClient("test-client-regex-1", "gemini", []*registry.ModelInfo{
|
||||||
{ID: "gemini-2.5-pro", OwnedBy: "google", Type: "gemini"},
|
{ID: "gemini-2.5-pro", OwnedBy: "google", Type: "gemini"},
|
||||||
})
|
})
|
||||||
defer reg.UnregisterClient("test-client-regex-1")
|
defer reg.UnregisterClient("test-client-regex-1")
|
||||||
|
|
||||||
mappings := []config.AmpModelMapping{
|
mappings := []config.AmpModelMapping{
|
||||||
{From: "^gpt-5$", To: "gemini-2.5-pro", Regex: true},
|
{From: "^gpt-5$", To: "gemini-2.5-pro", Regex: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
mapper := NewModelMapper(mappings)
|
mapper := NewModelMapper(mappings)
|
||||||
|
|
||||||
// Incoming model has reasoning suffix but should match base via regex
|
// Incoming model has reasoning suffix but should match base via regex
|
||||||
result := mapper.MapModel("gpt-5(high)")
|
result := mapper.MapModel("gpt-5(high)")
|
||||||
if result != "gemini-2.5-pro" {
|
if result != "gemini-2.5-pro" {
|
||||||
t.Errorf("Expected gemini-2.5-pro, got %s", result)
|
t.Errorf("Expected gemini-2.5-pro, got %s", result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestModelMapper_Regex_ExactPrecedence(t *testing.T) {
|
func TestModelMapper_Regex_ExactPrecedence(t *testing.T) {
|
||||||
reg := registry.GetGlobalRegistry()
|
reg := registry.GetGlobalRegistry()
|
||||||
reg.RegisterClient("test-client-regex-2", "claude", []*registry.ModelInfo{
|
reg.RegisterClient("test-client-regex-2", "claude", []*registry.ModelInfo{
|
||||||
{ID: "claude-sonnet-4", OwnedBy: "anthropic", Type: "claude"},
|
{ID: "claude-sonnet-4", OwnedBy: "anthropic", Type: "claude"},
|
||||||
})
|
})
|
||||||
reg.RegisterClient("test-client-regex-3", "gemini", []*registry.ModelInfo{
|
reg.RegisterClient("test-client-regex-3", "gemini", []*registry.ModelInfo{
|
||||||
{ID: "gemini-2.5-pro", OwnedBy: "google", Type: "gemini"},
|
{ID: "gemini-2.5-pro", OwnedBy: "google", Type: "gemini"},
|
||||||
})
|
})
|
||||||
defer reg.UnregisterClient("test-client-regex-2")
|
defer reg.UnregisterClient("test-client-regex-2")
|
||||||
defer reg.UnregisterClient("test-client-regex-3")
|
defer reg.UnregisterClient("test-client-regex-3")
|
||||||
|
|
||||||
mappings := []config.AmpModelMapping{
|
mappings := []config.AmpModelMapping{
|
||||||
{From: "gpt-5", To: "claude-sonnet-4"}, // exact
|
{From: "gpt-5", To: "claude-sonnet-4"}, // exact
|
||||||
{From: "^gpt-5.*$", To: "gemini-2.5-pro", Regex: true}, // regex
|
{From: "^gpt-5.*$", To: "gemini-2.5-pro", Regex: true}, // regex
|
||||||
}
|
}
|
||||||
|
|
||||||
mapper := NewModelMapper(mappings)
|
mapper := NewModelMapper(mappings)
|
||||||
|
|
||||||
// Exact match should win over regex
|
// Exact match should win over regex
|
||||||
result := mapper.MapModel("gpt-5")
|
result := mapper.MapModel("gpt-5")
|
||||||
if result != "claude-sonnet-4" {
|
if result != "claude-sonnet-4" {
|
||||||
t.Errorf("Expected claude-sonnet-4, got %s", result)
|
t.Errorf("Expected claude-sonnet-4, got %s", result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestModelMapper_Regex_InvalidPattern_Skipped(t *testing.T) {
|
func TestModelMapper_Regex_InvalidPattern_Skipped(t *testing.T) {
|
||||||
// Invalid regex should be skipped and not cause panic
|
// Invalid regex should be skipped and not cause panic
|
||||||
mappings := []config.AmpModelMapping{
|
mappings := []config.AmpModelMapping{
|
||||||
{From: "(", To: "target", Regex: true},
|
{From: "(", To: "target", Regex: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
mapper := NewModelMapper(mappings)
|
mapper := NewModelMapper(mappings)
|
||||||
|
|
||||||
result := mapper.MapModel("anything")
|
result := mapper.MapModel("anything")
|
||||||
if result != "" {
|
if result != "" {
|
||||||
t.Errorf("Expected empty result due to invalid regex, got %s", result)
|
t.Errorf("Expected empty result due to invalid regex, got %s", result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestModelMapper_Regex_CaseInsensitive(t *testing.T) {
|
func TestModelMapper_Regex_CaseInsensitive(t *testing.T) {
|
||||||
reg := registry.GetGlobalRegistry()
|
reg := registry.GetGlobalRegistry()
|
||||||
reg.RegisterClient("test-client-regex-4", "claude", []*registry.ModelInfo{
|
reg.RegisterClient("test-client-regex-4", "claude", []*registry.ModelInfo{
|
||||||
{ID: "claude-sonnet-4", OwnedBy: "anthropic", Type: "claude"},
|
{ID: "claude-sonnet-4", OwnedBy: "anthropic", Type: "claude"},
|
||||||
})
|
})
|
||||||
defer reg.UnregisterClient("test-client-regex-4")
|
defer reg.UnregisterClient("test-client-regex-4")
|
||||||
|
|
||||||
mappings := []config.AmpModelMapping{
|
mappings := []config.AmpModelMapping{
|
||||||
{From: "^CLAUDE-OPUS-.*$", To: "claude-sonnet-4", Regex: true},
|
{From: "^CLAUDE-OPUS-.*$", To: "claude-sonnet-4", Regex: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
mapper := NewModelMapper(mappings)
|
mapper := NewModelMapper(mappings)
|
||||||
|
|
||||||
result := mapper.MapModel("claude-opus-4.5")
|
result := mapper.MapModel("claude-opus-4.5")
|
||||||
if result != "claude-sonnet-4" {
|
if result != "claude-sonnet-4" {
|
||||||
t.Errorf("Expected claude-sonnet-4, got %s", result)
|
t.Errorf("Expected claude-sonnet-4, got %s", result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,10 +145,10 @@ type AmpModelMapping struct {
|
|||||||
// The target model must have available providers in the registry.
|
// The target model must have available providers in the registry.
|
||||||
To string `yaml:"to" json:"to"`
|
To string `yaml:"to" json:"to"`
|
||||||
|
|
||||||
// Regex indicates whether the 'from' field should be interpreted as a regular
|
// Regex indicates whether the 'from' field should be interpreted as a regular
|
||||||
// expression for matching model names. When true, this mapping is evaluated
|
// expression for matching model names. When true, this mapping is evaluated
|
||||||
// after exact matches and in the order provided. Defaults to false (exact match).
|
// after exact matches and in the order provided. Defaults to false (exact match).
|
||||||
Regex bool `yaml:"regex,omitempty" json:"regex,omitempty"`
|
Regex bool `yaml:"regex,omitempty" json:"regex,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AmpCode groups Amp CLI integration settings including upstream routing,
|
// AmpCode groups Amp CLI integration settings including upstream routing,
|
||||||
|
|||||||
Reference in New Issue
Block a user