diff --git a/internal/registry/model_definitions.go b/internal/registry/model_definitions.go index f0fb60cb..b8ca8757 100644 --- a/internal/registry/model_definitions.go +++ b/internal/registry/model_definitions.go @@ -788,6 +788,7 @@ func LookupStaticModelInfo(modelID string) *ModelInfo { if modelID == "" { return nil } + allModels := [][]*ModelInfo{ GetClaudeModels(), 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 } diff --git a/internal/thinking/apply.go b/internal/thinking/apply.go index 8ee60b8d..44566cab 100644 --- a/internal/thinking/apply.go +++ b/internal/thinking/apply.go @@ -40,7 +40,7 @@ func RegisterProvider(name string, applier ProviderApplier) { // letting the upstream service validate the configuration. func IsUserDefinedModel(modelInfo *registry.ModelInfo) bool { if modelInfo == nil { - return false + return true } return modelInfo.UserDefined } @@ -87,28 +87,28 @@ func ApplyThinking(body []byte, model string, provider string) ([]byte, error) { } // 2. Parse suffix and get modelInfo + // First try dynamic registry, then fall back to static lookup suffixResult := ParseSuffix(model) baseModel := suffixResult.ModelName modelInfo := registry.GetGlobalRegistry().GetModelInfo(baseModel) + if modelInfo == nil { + modelInfo = registry.LookupStaticModelInfo(baseModel) + } // 3. Model capability check - if modelInfo == nil { - log.WithField("model", model).Debug("thinking: nil modelInfo, passthrough") - return body, nil + if IsUserDefinedModel(modelInfo) { + return applyUserDefinedModel(body, modelInfo, provider, suffixResult) } if modelInfo.Thinking == nil { - if IsUserDefinedModel(modelInfo) { - return applyUserDefinedModel(body, modelInfo, provider, suffixResult) - } config := extractThinkingConfig(body, provider) if hasThinkingConfig(config) { log.WithFields(log.Fields{ - "model": modelInfo.ID, + "model": baseModel, "provider": provider, }).Debug("thinking: model does not support thinking, stripping config") 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 } @@ -212,6 +212,14 @@ func parseSuffixToConfig(rawSuffix string) ThinkingConfig { // applyUserDefinedModel applies thinking configuration for user-defined models // without ThinkingSupport validation. 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 var config ThinkingConfig if suffixResult.HasSuffix { @@ -222,7 +230,7 @@ func applyUserDefinedModel(body []byte, modelInfo *registry.ModelInfo, provider if !hasThinkingConfig(config) { log.WithFields(log.Fields{ - "model": modelInfo.ID, + "model": modelID, "provider": provider, "user_defined": true, "passthrough": true, @@ -233,7 +241,7 @@ func applyUserDefinedModel(body []byte, modelInfo *registry.ModelInfo, provider applier := GetProviderApplier(provider) if applier == nil { log.WithFields(log.Fields{ - "model": modelInfo.ID, + "model": modelID, "provider": provider, "user_defined": true, "passthrough": true, @@ -242,7 +250,7 @@ func applyUserDefinedModel(body []byte, modelInfo *registry.ModelInfo, provider } log.WithFields(log.Fields{ - "model": modelInfo.ID, + "model": modelID, "provider": provider, "user_defined": true, "passthrough": false, diff --git a/internal/thinking/apply_test.go b/internal/thinking/apply_test.go index d89fff32..b49079db 100644 --- a/internal/thinking/apply_test.go +++ b/internal/thinking/apply_test.go @@ -150,7 +150,7 @@ func TestIsUserDefinedModel(t *testing.T) { modelInfo *registry.ModelInfo want bool }{ - {"nil modelInfo", nil, false}, + {"nil modelInfo", nil, true}, {"not user-defined no flag", ®istry.ModelInfo{ID: "test"}, 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}, diff --git a/internal/thinking/provider/claude/apply.go b/internal/thinking/provider/claude/apply.go index e1409389..979ecd75 100644 --- a/internal/thinking/provider/claude/apply.go +++ b/internal/thinking/provider/claude/apply.go @@ -54,7 +54,7 @@ func init() { // } func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) { if modelInfo == nil { - return body, nil + return applyCompatibleClaude(body, config) } if modelInfo.Thinking == nil { if modelInfo.Type == "" { diff --git a/internal/thinking/provider/codex/apply.go b/internal/thinking/provider/codex/apply.go index 386185a6..228bb6fe 100644 --- a/internal/thinking/provider/codex/apply.go +++ b/internal/thinking/provider/codex/apply.go @@ -45,7 +45,7 @@ func init() { // } func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) { if modelInfo == nil { - return body, nil + return applyCompatibleCodex(body, config) } if modelInfo.Thinking == nil { if modelInfo.Type == "" { diff --git a/internal/thinking/provider/gemini/apply.go b/internal/thinking/provider/gemini/apply.go index eebc44d8..bb574c31 100644 --- a/internal/thinking/provider/gemini/apply.go +++ b/internal/thinking/provider/gemini/apply.go @@ -60,7 +60,7 @@ func init() { // } func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) { if modelInfo == nil { - return body, nil + return a.applyCompatible(body, config) } if modelInfo.Thinking == nil { if modelInfo.Type == "" { diff --git a/internal/thinking/provider/gemini/apply_test.go b/internal/thinking/provider/gemini/apply_test.go index 5f762a2f..1af2fa83 100644 --- a/internal/thinking/provider/gemini/apply_test.go +++ b/internal/thinking/provider/gemini/apply_test.go @@ -450,8 +450,9 @@ func TestGeminiApplyNilModelInfo(t *testing.T) { if err != nil { t.Fatalf("Apply() with nil modelInfo should not error, got: %v", err) } - if string(result) != string(body) { - t.Fatalf("Apply() with nil modelInfo should return original body, got: %s", result) + // nil modelInfo now applies compatible config + if !gjson.GetBytes(result, "generationConfig.thinkingConfig.thinkingBudget").Exists() { + t.Fatalf("Apply() with nil modelInfo should apply thinking config, got: %s", result) } } diff --git a/internal/thinking/provider/geminicli/apply.go b/internal/thinking/provider/geminicli/apply.go index a4607107..eb6d82a4 100644 --- a/internal/thinking/provider/geminicli/apply.go +++ b/internal/thinking/provider/geminicli/apply.go @@ -30,7 +30,7 @@ func init() { // Apply applies thinking configuration to Gemini CLI request body. func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) { if modelInfo == nil { - return body, nil + return a.applyCompatible(body, config) } if modelInfo.Thinking == nil { if modelInfo.Type == "" { diff --git a/internal/thinking/provider/geminicli/apply_test.go b/internal/thinking/provider/geminicli/apply_test.go index a606457c..6bf77dd2 100644 --- a/internal/thinking/provider/geminicli/apply_test.go +++ b/internal/thinking/provider/geminicli/apply_test.go @@ -241,8 +241,9 @@ func TestGeminiCLIApplyNilModelInfo(t *testing.T) { if err != nil { t.Fatalf("Apply() with nil modelInfo should not error, got: %v", err) } - if string(result) != string(body) { - t.Fatalf("Apply() with nil modelInfo should return original body, got: %s", result) + // nil modelInfo now applies compatible config + 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 { t.Fatalf("Apply() error = %v", err) } - // ModeBudget with Levels model: Apply returns body unchanged (conversion is upper layer's job) - if string(result) != string(body) { - t.Fatalf("Apply() ModeBudget with Levels should return original body, got: %s", result) + // ModeBudget applies budget format directly without conversion to levels + if !gjson.GetBytes(result, "request.generationConfig.thinkingConfig.thinkingBudget").Exists() { + t.Fatalf("Apply() ModeBudget should apply budget format, got: %s", result) } } diff --git a/internal/thinking/provider/openai/apply.go b/internal/thinking/provider/openai/apply.go index 810faf34..aea1c055 100644 --- a/internal/thinking/provider/openai/apply.go +++ b/internal/thinking/provider/openai/apply.go @@ -42,7 +42,7 @@ func init() { // } func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) { if modelInfo == nil { - return body, nil + return applyCompatibleOpenAI(body, config) } if modelInfo.Thinking == nil { if modelInfo.Type == "" { diff --git a/internal/thinking/provider/openai/apply_test.go b/internal/thinking/provider/openai/apply_test.go index 88c1800a..5be01e4e 100644 --- a/internal/thinking/provider/openai/apply_test.go +++ b/internal/thinking/provider/openai/apply_test.go @@ -43,12 +43,14 @@ func TestApplierImplementsInterface(t *testing.T) { func TestApplyNilModelInfo(t *testing.T) { applier := NewApplier() 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 { t.Fatalf("expected nil error, got %v", err) } - if string(got) != string(body) { - t.Fatalf("expected body unchanged, got %s", string(got)) + // nil modelInfo now applies compatible config + if !gjson.GetBytes(got, "reasoning_effort").Exists() { + t.Fatalf("expected reasoning_effort applied, got %s", string(got)) } }