mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 04:50:52 +08:00
fix(thinking): use static lookup to avoid alias issues
This commit is contained in:
@@ -788,6 +788,7 @@ func LookupStaticModelInfo(modelID string) *ModelInfo {
|
|||||||
if modelID == "" {
|
if modelID == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
allModels := [][]*ModelInfo{
|
allModels := [][]*ModelInfo{
|
||||||
GetClaudeModels(),
|
GetClaudeModels(),
|
||||||
GetGeminiModels(),
|
GetGeminiModels(),
|
||||||
@@ -805,5 +806,16 @@ func LookupStaticModelInfo(modelID string) *ModelInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check Antigravity static config
|
||||||
|
if cfg := GetAntigravityModelConfig()[modelID]; cfg != nil && cfg.Thinking != nil {
|
||||||
|
return &ModelInfo{
|
||||||
|
ID: modelID,
|
||||||
|
Name: cfg.Name,
|
||||||
|
Thinking: cfg.Thinking,
|
||||||
|
MaxCompletionTokens: cfg.MaxCompletionTokens,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func RegisterProvider(name string, applier ProviderApplier) {
|
|||||||
// letting the upstream service validate the configuration.
|
// letting the upstream service validate the configuration.
|
||||||
func IsUserDefinedModel(modelInfo *registry.ModelInfo) bool {
|
func IsUserDefinedModel(modelInfo *registry.ModelInfo) bool {
|
||||||
if modelInfo == nil {
|
if modelInfo == nil {
|
||||||
return false
|
return true
|
||||||
}
|
}
|
||||||
return modelInfo.UserDefined
|
return modelInfo.UserDefined
|
||||||
}
|
}
|
||||||
@@ -87,28 +87,28 @@ func ApplyThinking(body []byte, model string, provider string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. Parse suffix and get modelInfo
|
// 2. Parse suffix and get modelInfo
|
||||||
|
// First try dynamic registry, then fall back to static lookup
|
||||||
suffixResult := ParseSuffix(model)
|
suffixResult := ParseSuffix(model)
|
||||||
baseModel := suffixResult.ModelName
|
baseModel := suffixResult.ModelName
|
||||||
modelInfo := registry.GetGlobalRegistry().GetModelInfo(baseModel)
|
modelInfo := registry.GetGlobalRegistry().GetModelInfo(baseModel)
|
||||||
|
if modelInfo == nil {
|
||||||
|
modelInfo = registry.LookupStaticModelInfo(baseModel)
|
||||||
|
}
|
||||||
|
|
||||||
// 3. Model capability check
|
// 3. Model capability check
|
||||||
if modelInfo == nil {
|
|
||||||
log.WithField("model", model).Debug("thinking: nil modelInfo, passthrough")
|
|
||||||
return body, nil
|
|
||||||
}
|
|
||||||
if modelInfo.Thinking == nil {
|
|
||||||
if IsUserDefinedModel(modelInfo) {
|
if IsUserDefinedModel(modelInfo) {
|
||||||
return applyUserDefinedModel(body, modelInfo, provider, suffixResult)
|
return applyUserDefinedModel(body, modelInfo, provider, suffixResult)
|
||||||
}
|
}
|
||||||
|
if modelInfo.Thinking == nil {
|
||||||
config := extractThinkingConfig(body, provider)
|
config := extractThinkingConfig(body, provider)
|
||||||
if hasThinkingConfig(config) {
|
if hasThinkingConfig(config) {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"model": modelInfo.ID,
|
"model": baseModel,
|
||||||
"provider": provider,
|
"provider": provider,
|
||||||
}).Debug("thinking: model does not support thinking, stripping config")
|
}).Debug("thinking: model does not support thinking, stripping config")
|
||||||
return StripThinkingConfig(body, provider), nil
|
return StripThinkingConfig(body, provider), nil
|
||||||
}
|
}
|
||||||
log.WithField("model", modelInfo.ID).Debug("thinking: model does not support thinking, passthrough")
|
log.WithField("model", baseModel).Debug("thinking: model does not support thinking, passthrough")
|
||||||
return body, nil
|
return body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,6 +212,14 @@ func parseSuffixToConfig(rawSuffix string) ThinkingConfig {
|
|||||||
// applyUserDefinedModel applies thinking configuration for user-defined models
|
// applyUserDefinedModel applies thinking configuration for user-defined models
|
||||||
// without ThinkingSupport validation.
|
// without ThinkingSupport validation.
|
||||||
func applyUserDefinedModel(body []byte, modelInfo *registry.ModelInfo, provider string, suffixResult SuffixResult) ([]byte, error) {
|
func applyUserDefinedModel(body []byte, modelInfo *registry.ModelInfo, provider string, suffixResult SuffixResult) ([]byte, error) {
|
||||||
|
// Get model ID for logging
|
||||||
|
modelID := ""
|
||||||
|
if modelInfo != nil {
|
||||||
|
modelID = modelInfo.ID
|
||||||
|
} else {
|
||||||
|
modelID = suffixResult.ModelName
|
||||||
|
}
|
||||||
|
|
||||||
// Get config: suffix priority over body
|
// Get config: suffix priority over body
|
||||||
var config ThinkingConfig
|
var config ThinkingConfig
|
||||||
if suffixResult.HasSuffix {
|
if suffixResult.HasSuffix {
|
||||||
@@ -222,7 +230,7 @@ func applyUserDefinedModel(body []byte, modelInfo *registry.ModelInfo, provider
|
|||||||
|
|
||||||
if !hasThinkingConfig(config) {
|
if !hasThinkingConfig(config) {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"model": modelInfo.ID,
|
"model": modelID,
|
||||||
"provider": provider,
|
"provider": provider,
|
||||||
"user_defined": true,
|
"user_defined": true,
|
||||||
"passthrough": true,
|
"passthrough": true,
|
||||||
@@ -233,7 +241,7 @@ func applyUserDefinedModel(body []byte, modelInfo *registry.ModelInfo, provider
|
|||||||
applier := GetProviderApplier(provider)
|
applier := GetProviderApplier(provider)
|
||||||
if applier == nil {
|
if applier == nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"model": modelInfo.ID,
|
"model": modelID,
|
||||||
"provider": provider,
|
"provider": provider,
|
||||||
"user_defined": true,
|
"user_defined": true,
|
||||||
"passthrough": true,
|
"passthrough": true,
|
||||||
@@ -242,7 +250,7 @@ func applyUserDefinedModel(body []byte, modelInfo *registry.ModelInfo, provider
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"model": modelInfo.ID,
|
"model": modelID,
|
||||||
"provider": provider,
|
"provider": provider,
|
||||||
"user_defined": true,
|
"user_defined": true,
|
||||||
"passthrough": false,
|
"passthrough": false,
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ func TestIsUserDefinedModel(t *testing.T) {
|
|||||||
modelInfo *registry.ModelInfo
|
modelInfo *registry.ModelInfo
|
||||||
want bool
|
want bool
|
||||||
}{
|
}{
|
||||||
{"nil modelInfo", nil, false},
|
{"nil modelInfo", nil, true},
|
||||||
{"not user-defined no flag", ®istry.ModelInfo{ID: "test"}, false},
|
{"not user-defined no flag", ®istry.ModelInfo{ID: "test"}, false},
|
||||||
{"not user-defined with type", ®istry.ModelInfo{ID: "test", Type: "openai"}, false},
|
{"not user-defined with type", ®istry.ModelInfo{ID: "test", Type: "openai"}, false},
|
||||||
{"user-defined with flag", ®istry.ModelInfo{ID: "test", Type: "openai", UserDefined: true}, true},
|
{"user-defined with flag", ®istry.ModelInfo{ID: "test", Type: "openai", UserDefined: true}, true},
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func init() {
|
|||||||
// }
|
// }
|
||||||
func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) {
|
func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) {
|
||||||
if modelInfo == nil {
|
if modelInfo == nil {
|
||||||
return body, nil
|
return applyCompatibleClaude(body, config)
|
||||||
}
|
}
|
||||||
if modelInfo.Thinking == nil {
|
if modelInfo.Thinking == nil {
|
||||||
if modelInfo.Type == "" {
|
if modelInfo.Type == "" {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func init() {
|
|||||||
// }
|
// }
|
||||||
func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) {
|
func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) {
|
||||||
if modelInfo == nil {
|
if modelInfo == nil {
|
||||||
return body, nil
|
return applyCompatibleCodex(body, config)
|
||||||
}
|
}
|
||||||
if modelInfo.Thinking == nil {
|
if modelInfo.Thinking == nil {
|
||||||
if modelInfo.Type == "" {
|
if modelInfo.Type == "" {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func init() {
|
|||||||
// }
|
// }
|
||||||
func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) {
|
func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) {
|
||||||
if modelInfo == nil {
|
if modelInfo == nil {
|
||||||
return body, nil
|
return a.applyCompatible(body, config)
|
||||||
}
|
}
|
||||||
if modelInfo.Thinking == nil {
|
if modelInfo.Thinking == nil {
|
||||||
if modelInfo.Type == "" {
|
if modelInfo.Type == "" {
|
||||||
|
|||||||
@@ -450,8 +450,9 @@ func TestGeminiApplyNilModelInfo(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Apply() with nil modelInfo should not error, got: %v", err)
|
t.Fatalf("Apply() with nil modelInfo should not error, got: %v", err)
|
||||||
}
|
}
|
||||||
if string(result) != string(body) {
|
// nil modelInfo now applies compatible config
|
||||||
t.Fatalf("Apply() with nil modelInfo should return original body, got: %s", result)
|
if !gjson.GetBytes(result, "generationConfig.thinkingConfig.thinkingBudget").Exists() {
|
||||||
|
t.Fatalf("Apply() with nil modelInfo should apply thinking config, got: %s", result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func init() {
|
|||||||
// Apply applies thinking configuration to Gemini CLI request body.
|
// Apply applies thinking configuration to Gemini CLI request body.
|
||||||
func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) {
|
func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) {
|
||||||
if modelInfo == nil {
|
if modelInfo == nil {
|
||||||
return body, nil
|
return a.applyCompatible(body, config)
|
||||||
}
|
}
|
||||||
if modelInfo.Thinking == nil {
|
if modelInfo.Thinking == nil {
|
||||||
if modelInfo.Type == "" {
|
if modelInfo.Type == "" {
|
||||||
|
|||||||
@@ -241,8 +241,9 @@ func TestGeminiCLIApplyNilModelInfo(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Apply() with nil modelInfo should not error, got: %v", err)
|
t.Fatalf("Apply() with nil modelInfo should not error, got: %v", err)
|
||||||
}
|
}
|
||||||
if string(result) != string(body) {
|
// nil modelInfo now applies compatible config
|
||||||
t.Fatalf("Apply() with nil modelInfo should return original body, got: %s", result)
|
if !gjson.GetBytes(result, "request.generationConfig.thinkingConfig.thinkingBudget").Exists() {
|
||||||
|
t.Fatalf("Apply() with nil modelInfo should apply thinking config, got: %s", result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,9 +278,9 @@ func TestGeminiCLIApplyModeBudgetWithLevels(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Apply() error = %v", err)
|
t.Fatalf("Apply() error = %v", err)
|
||||||
}
|
}
|
||||||
// ModeBudget with Levels model: Apply returns body unchanged (conversion is upper layer's job)
|
// ModeBudget applies budget format directly without conversion to levels
|
||||||
if string(result) != string(body) {
|
if !gjson.GetBytes(result, "request.generationConfig.thinkingConfig.thinkingBudget").Exists() {
|
||||||
t.Fatalf("Apply() ModeBudget with Levels should return original body, got: %s", result)
|
t.Fatalf("Apply() ModeBudget should apply budget format, got: %s", result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func init() {
|
|||||||
// }
|
// }
|
||||||
func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) {
|
func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) {
|
||||||
if modelInfo == nil {
|
if modelInfo == nil {
|
||||||
return body, nil
|
return applyCompatibleOpenAI(body, config)
|
||||||
}
|
}
|
||||||
if modelInfo.Thinking == nil {
|
if modelInfo.Thinking == nil {
|
||||||
if modelInfo.Type == "" {
|
if modelInfo.Type == "" {
|
||||||
|
|||||||
@@ -43,12 +43,14 @@ func TestApplierImplementsInterface(t *testing.T) {
|
|||||||
func TestApplyNilModelInfo(t *testing.T) {
|
func TestApplyNilModelInfo(t *testing.T) {
|
||||||
applier := NewApplier()
|
applier := NewApplier()
|
||||||
body := []byte(`{"model":"gpt-5.2"}`)
|
body := []byte(`{"model":"gpt-5.2"}`)
|
||||||
got, err := applier.Apply(body, thinking.ThinkingConfig{}, nil)
|
config := thinking.ThinkingConfig{Mode: thinking.ModeLevel, Level: thinking.LevelHigh}
|
||||||
|
got, err := applier.Apply(body, config, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected nil error, got %v", err)
|
t.Fatalf("expected nil error, got %v", err)
|
||||||
}
|
}
|
||||||
if string(got) != string(body) {
|
// nil modelInfo now applies compatible config
|
||||||
t.Fatalf("expected body unchanged, got %s", string(got))
|
if !gjson.GetBytes(got, "reasoning_effort").Exists() {
|
||||||
|
t.Fatalf("expected reasoning_effort applied, got %s", string(got))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user