fix(gemini): resolve customtools alias in mapping lookup
This commit is contained in:
@@ -515,6 +515,45 @@ func ensureAntigravityDefaultPassthroughs(mapping map[string]string, models []st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeRequestedModelForLookup(platform, requestedModel string) string {
|
||||||
|
trimmed := strings.TrimSpace(requestedModel)
|
||||||
|
if trimmed == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if platform != PlatformGemini && platform != PlatformAntigravity {
|
||||||
|
return trimmed
|
||||||
|
}
|
||||||
|
if trimmed == "gemini-3.1-pro-preview-customtools" {
|
||||||
|
return "gemini-3.1-pro-preview"
|
||||||
|
}
|
||||||
|
return trimmed
|
||||||
|
}
|
||||||
|
|
||||||
|
func mappingSupportsRequestedModel(mapping map[string]string, requestedModel string) bool {
|
||||||
|
if requestedModel == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if _, exists := mapping[requestedModel]; exists {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for pattern := range mapping {
|
||||||
|
if matchWildcard(pattern, requestedModel) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveRequestedModelInMapping(mapping map[string]string, requestedModel string) (mappedModel string, matched bool) {
|
||||||
|
if requestedModel == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
if mappedModel, exists := mapping[requestedModel]; exists {
|
||||||
|
return mappedModel, true
|
||||||
|
}
|
||||||
|
return matchWildcardMappingResult(mapping, requestedModel)
|
||||||
|
}
|
||||||
|
|
||||||
// IsModelSupported 检查模型是否在 model_mapping 中(支持通配符)
|
// IsModelSupported 检查模型是否在 model_mapping 中(支持通配符)
|
||||||
// 如果未配置 mapping,返回 true(允许所有模型)
|
// 如果未配置 mapping,返回 true(允许所有模型)
|
||||||
func (a *Account) IsModelSupported(requestedModel string) bool {
|
func (a *Account) IsModelSupported(requestedModel string) bool {
|
||||||
@@ -522,17 +561,11 @@ func (a *Account) IsModelSupported(requestedModel string) bool {
|
|||||||
if len(mapping) == 0 {
|
if len(mapping) == 0 {
|
||||||
return true // 无映射 = 允许所有
|
return true // 无映射 = 允许所有
|
||||||
}
|
}
|
||||||
// 精确匹配
|
if mappingSupportsRequestedModel(mapping, requestedModel) {
|
||||||
if _, exists := mapping[requestedModel]; exists {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// 通配符匹配
|
normalized := normalizeRequestedModelForLookup(a.Platform, requestedModel)
|
||||||
for pattern := range mapping {
|
return normalized != requestedModel && mappingSupportsRequestedModel(mapping, normalized)
|
||||||
if matchWildcard(pattern, requestedModel) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMappedModel 获取映射后的模型名(支持通配符,最长优先匹配)
|
// GetMappedModel 获取映射后的模型名(支持通配符,最长优先匹配)
|
||||||
@@ -549,12 +582,16 @@ func (a *Account) ResolveMappedModel(requestedModel string) (mappedModel string,
|
|||||||
if len(mapping) == 0 {
|
if len(mapping) == 0 {
|
||||||
return requestedModel, false
|
return requestedModel, false
|
||||||
}
|
}
|
||||||
// 精确匹配优先
|
if mappedModel, matched := resolveRequestedModelInMapping(mapping, requestedModel); matched {
|
||||||
if mappedModel, exists := mapping[requestedModel]; exists {
|
|
||||||
return mappedModel, true
|
return mappedModel, true
|
||||||
}
|
}
|
||||||
// 通配符匹配(最长优先)
|
normalized := normalizeRequestedModelForLookup(a.Platform, requestedModel)
|
||||||
return matchWildcardMappingResult(mapping, requestedModel)
|
if normalized != requestedModel {
|
||||||
|
if mappedModel, matched := resolveRequestedModelInMapping(mapping, normalized); matched {
|
||||||
|
return mappedModel, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return requestedModel, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Account) GetBaseURL() string {
|
func (a *Account) GetBaseURL() string {
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ func TestMatchWildcardMappingResult(t *testing.T) {
|
|||||||
func TestAccountIsModelSupported(t *testing.T) {
|
func TestAccountIsModelSupported(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
platform string
|
||||||
credentials map[string]any
|
credentials map[string]any
|
||||||
requestedModel string
|
requestedModel string
|
||||||
expected bool
|
expected bool
|
||||||
@@ -184,6 +185,17 @@ func TestAccountIsModelSupported(t *testing.T) {
|
|||||||
requestedModel: "claude-opus-4-5-thinking",
|
requestedModel: "claude-opus-4-5-thinking",
|
||||||
expected: true,
|
expected: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "gemini customtools alias matches normalized mapping",
|
||||||
|
platform: PlatformGemini,
|
||||||
|
credentials: map[string]any{
|
||||||
|
"model_mapping": map[string]any{
|
||||||
|
"gemini-3.1-pro-preview": "gemini-3.1-pro-preview",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
requestedModel: "gemini-3.1-pro-preview-customtools",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "wildcard match not supported",
|
name: "wildcard match not supported",
|
||||||
credentials: map[string]any{
|
credentials: map[string]any{
|
||||||
@@ -199,6 +211,7 @@ func TestAccountIsModelSupported(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) {
|
||||||
account := &Account{
|
account := &Account{
|
||||||
|
Platform: tt.platform,
|
||||||
Credentials: tt.credentials,
|
Credentials: tt.credentials,
|
||||||
}
|
}
|
||||||
result := account.IsModelSupported(tt.requestedModel)
|
result := account.IsModelSupported(tt.requestedModel)
|
||||||
@@ -212,6 +225,7 @@ func TestAccountIsModelSupported(t *testing.T) {
|
|||||||
func TestAccountGetMappedModel(t *testing.T) {
|
func TestAccountGetMappedModel(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
platform string
|
||||||
credentials map[string]any
|
credentials map[string]any
|
||||||
requestedModel string
|
requestedModel string
|
||||||
expected string
|
expected string
|
||||||
@@ -223,6 +237,13 @@ func TestAccountGetMappedModel(t *testing.T) {
|
|||||||
requestedModel: "claude-sonnet-4-5",
|
requestedModel: "claude-sonnet-4-5",
|
||||||
expected: "claude-sonnet-4-5",
|
expected: "claude-sonnet-4-5",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "no mapping preserves gemini customtools model",
|
||||||
|
platform: PlatformGemini,
|
||||||
|
credentials: nil,
|
||||||
|
requestedModel: "gemini-3.1-pro-preview-customtools",
|
||||||
|
expected: "gemini-3.1-pro-preview-customtools",
|
||||||
|
},
|
||||||
|
|
||||||
// 精确匹配
|
// 精确匹配
|
||||||
{
|
{
|
||||||
@@ -250,6 +271,29 @@ func TestAccountGetMappedModel(t *testing.T) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 无匹配返回原始模型
|
// 无匹配返回原始模型
|
||||||
|
{
|
||||||
|
name: "gemini customtools alias resolves through normalized mapping",
|
||||||
|
platform: PlatformGemini,
|
||||||
|
credentials: map[string]any{
|
||||||
|
"model_mapping": map[string]any{
|
||||||
|
"gemini-3.1-pro-preview": "gemini-3.1-pro-preview",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
requestedModel: "gemini-3.1-pro-preview-customtools",
|
||||||
|
expected: "gemini-3.1-pro-preview",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gemini customtools exact mapping wins over normalized fallback",
|
||||||
|
platform: PlatformGemini,
|
||||||
|
credentials: map[string]any{
|
||||||
|
"model_mapping": map[string]any{
|
||||||
|
"gemini-3.1-pro-preview": "gemini-3.1-pro-preview",
|
||||||
|
"gemini-3.1-pro-preview-customtools": "gemini-3.1-pro-preview-customtools",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
requestedModel: "gemini-3.1-pro-preview-customtools",
|
||||||
|
expected: "gemini-3.1-pro-preview-customtools",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "no match returns original",
|
name: "no match returns original",
|
||||||
credentials: map[string]any{
|
credentials: map[string]any{
|
||||||
@@ -265,6 +309,7 @@ func TestAccountGetMappedModel(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) {
|
||||||
account := &Account{
|
account := &Account{
|
||||||
|
Platform: tt.platform,
|
||||||
Credentials: tt.credentials,
|
Credentials: tt.credentials,
|
||||||
}
|
}
|
||||||
result := account.GetMappedModel(tt.requestedModel)
|
result := account.GetMappedModel(tt.requestedModel)
|
||||||
@@ -278,6 +323,7 @@ func TestAccountGetMappedModel(t *testing.T) {
|
|||||||
func TestAccountResolveMappedModel(t *testing.T) {
|
func TestAccountResolveMappedModel(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
platform string
|
||||||
credentials map[string]any
|
credentials map[string]any
|
||||||
requestedModel string
|
requestedModel string
|
||||||
expectedModel string
|
expectedModel string
|
||||||
@@ -312,6 +358,31 @@ func TestAccountResolveMappedModel(t *testing.T) {
|
|||||||
expectedModel: "gpt-5.4",
|
expectedModel: "gpt-5.4",
|
||||||
expectedMatch: true,
|
expectedMatch: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "gemini customtools alias reports normalized match",
|
||||||
|
platform: PlatformGemini,
|
||||||
|
credentials: map[string]any{
|
||||||
|
"model_mapping": map[string]any{
|
||||||
|
"gemini-3.1-pro-preview": "gemini-3.1-pro-preview",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
requestedModel: "gemini-3.1-pro-preview-customtools",
|
||||||
|
expectedModel: "gemini-3.1-pro-preview",
|
||||||
|
expectedMatch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gemini customtools exact mapping reports exact match",
|
||||||
|
platform: PlatformGemini,
|
||||||
|
credentials: map[string]any{
|
||||||
|
"model_mapping": map[string]any{
|
||||||
|
"gemini-3.1-pro-preview": "gemini-3.1-pro-preview",
|
||||||
|
"gemini-3.1-pro-preview-customtools": "gemini-3.1-pro-preview-customtools",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
requestedModel: "gemini-3.1-pro-preview-customtools",
|
||||||
|
expectedModel: "gemini-3.1-pro-preview-customtools",
|
||||||
|
expectedMatch: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "missing mapping reports unmatched",
|
name: "missing mapping reports unmatched",
|
||||||
credentials: map[string]any{
|
credentials: map[string]any{
|
||||||
@@ -328,6 +399,7 @@ func TestAccountResolveMappedModel(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) {
|
||||||
account := &Account{
|
account := &Account{
|
||||||
|
Platform: tt.platform,
|
||||||
Credentials: tt.credentials,
|
Credentials: tt.credentials,
|
||||||
}
|
}
|
||||||
mappedModel, matched := account.ResolveMappedModel(tt.requestedModel)
|
mappedModel, matched := account.ResolveMappedModel(tt.requestedModel)
|
||||||
|
|||||||
@@ -268,6 +268,12 @@ func TestMapAntigravityModel_WildcardTargetEqualsRequest(t *testing.T) {
|
|||||||
requestedModel: "gemini-2.5-flash",
|
requestedModel: "gemini-2.5-flash",
|
||||||
expected: "gemini-2.5-flash",
|
expected: "gemini-2.5-flash",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "customtools alias falls back to normalized preview mapping",
|
||||||
|
modelMapping: map[string]any{"gemini-3.1-pro-preview": "gemini-3.1-pro-high"},
|
||||||
|
requestedModel: "gemini-3.1-pro-preview-customtools",
|
||||||
|
expected: "gemini-3.1-pro-high",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|||||||
Reference in New Issue
Block a user