fix(thinking): improve model lookup and validation

This commit is contained in:
hkfires
2026-01-14 16:30:28 +08:00
parent 40ee065eff
commit 7f1b2b3f6e
13 changed files with 78 additions and 137 deletions

View File

@@ -53,18 +53,11 @@ func init() {
// }
// }
func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) {
if modelInfo == nil {
if thinking.IsUserDefinedModel(modelInfo) {
return applyCompatibleClaude(body, config)
}
if modelInfo.Thinking == nil {
if modelInfo.Type == "" {
modelID := modelInfo.ID
if modelID == "" {
modelID = "unknown"
}
return nil, thinking.NewThinkingErrorWithModel(thinking.ErrThinkingNotSupported, "thinking not supported for this model", modelID)
}
return applyCompatibleClaude(body, config)
return body, nil
}
// Only process ModeBudget and ModeNone; other modes pass through

View File

@@ -44,18 +44,11 @@ func init() {
// }
// }
func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) {
if modelInfo == nil {
if thinking.IsUserDefinedModel(modelInfo) {
return applyCompatibleCodex(body, config)
}
if modelInfo.Thinking == nil {
if modelInfo.Type == "" {
modelID := modelInfo.ID
if modelID == "" {
modelID = "unknown"
}
return nil, thinking.NewThinkingErrorWithModel(thinking.ErrThinkingNotSupported, "thinking not supported for this model", modelID)
}
return applyCompatibleCodex(body, config)
return body, nil
}
// Only handle ModeLevel and ModeNone; other modes pass through unchanged.

View File

@@ -59,18 +59,11 @@ func init() {
// }
// }
func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) {
if modelInfo == nil {
if thinking.IsUserDefinedModel(modelInfo) {
return a.applyCompatible(body, config)
}
if modelInfo.Thinking == nil {
if modelInfo.Type == "" {
modelID := modelInfo.ID
if modelID == "" {
modelID = "unknown"
}
return nil, thinking.NewThinkingErrorWithModel(thinking.ErrThinkingNotSupported, "thinking not supported for this model", modelID)
}
return a.applyCompatible(body, config)
return body, nil
}
if config.Mode != thinking.ModeBudget && config.Mode != thinking.ModeLevel && config.Mode != thinking.ModeNone && config.Mode != thinking.ModeAuto {

View File

@@ -381,26 +381,21 @@ func TestGeminiApplyConflictingFields(t *testing.T) {
}
}
// TestGeminiApplyThinkingNotSupported tests error handling when modelInfo.Thinking is nil.
// TestGeminiApplyThinkingNotSupported tests passthrough handling when modelInfo.Thinking is nil.
func TestGeminiApplyThinkingNotSupported(t *testing.T) {
applier := NewApplier()
config := thinking.ThinkingConfig{Mode: thinking.ModeBudget, Budget: 8192}
body := []byte(`{"generationConfig":{"thinkingConfig":{"thinkingBudget":8192}}}`)
// Model with nil Thinking support
modelInfo := &registry.ModelInfo{ID: "gemini-unknown", Thinking: nil}
_, err := applier.Apply([]byte(`{}`), config, modelInfo)
if err == nil {
t.Fatal("Apply() expected error for nil Thinking, got nil")
got, err := applier.Apply(body, config, modelInfo)
if err != nil {
t.Fatalf("Apply() expected nil error for nil Thinking, got %v", err)
}
// Verify it's the correct error type
thinkErr, ok := err.(*thinking.ThinkingError)
if !ok {
t.Fatalf("Apply() error type = %T, want *thinking.ThinkingError", err)
}
if thinkErr.Code != thinking.ErrThinkingNotSupported {
t.Fatalf("Apply() error code = %v, want %v", thinkErr.Code, thinking.ErrThinkingNotSupported)
if string(got) != string(body) {
t.Fatalf("expected body unchanged, got %s", string(got))
}
}
@@ -462,17 +457,14 @@ func TestGeminiApplyEmptyModelID(t *testing.T) {
applier := NewApplier()
config := thinking.ThinkingConfig{Mode: thinking.ModeBudget, Budget: 8192}
modelInfo := &registry.ModelInfo{ID: "", Thinking: nil}
body := []byte(`{"generationConfig":{"thinkingConfig":{"thinkingBudget":8192}}}`)
_, err := applier.Apply([]byte(`{}`), config, modelInfo)
if err == nil {
t.Fatal("Apply() with empty modelID and nil Thinking should error")
got, err := applier.Apply(body, config, modelInfo)
if err != nil {
t.Fatalf("Apply() expected nil error, got %v", err)
}
thinkErr, ok := err.(*thinking.ThinkingError)
if !ok {
t.Fatalf("Apply() error type = %T, want *thinking.ThinkingError", err)
}
if thinkErr.Model != "unknown" {
t.Fatalf("Apply() error model = %q, want %q", thinkErr.Model, "unknown")
if string(got) != string(body) {
t.Fatalf("expected body unchanged, got %s", string(got))
}
}

View File

@@ -29,18 +29,11 @@ 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 {
if thinking.IsUserDefinedModel(modelInfo) {
return a.applyCompatible(body, config)
}
if modelInfo.Thinking == nil {
if modelInfo.Type == "" {
modelID := modelInfo.ID
if modelID == "" {
modelID = "unknown"
}
return nil, thinking.NewThinkingErrorWithModel(thinking.ErrThinkingNotSupported, "thinking not supported for this model", modelID)
}
return a.applyCompatible(body, config)
return body, nil
}
if config.Mode != thinking.ModeBudget && config.Mode != thinking.ModeLevel && config.Mode != thinking.ModeNone && config.Mode != thinking.ModeAuto {

View File

@@ -208,26 +208,21 @@ func TestGeminiCLIApplyConflictingFields(t *testing.T) {
}
}
// TestGeminiCLIApplyThinkingNotSupported tests error handling when modelInfo.Thinking is nil.
// TestGeminiCLIApplyThinkingNotSupported tests passthrough handling when modelInfo.Thinking is nil.
func TestGeminiCLIApplyThinkingNotSupported(t *testing.T) {
applier := NewApplier()
config := thinking.ThinkingConfig{Mode: thinking.ModeBudget, Budget: 8192}
body := []byte(`{"request":{"generationConfig":{"thinkingConfig":{"thinkingBudget":8192}}}}`)
// Model with nil Thinking support
modelInfo := &registry.ModelInfo{ID: "gemini-cli-unknown", Thinking: nil}
_, err := applier.Apply([]byte(`{}`), config, modelInfo)
if err == nil {
t.Fatal("Apply() expected error for nil Thinking, got nil")
got, err := applier.Apply(body, config, modelInfo)
if err != nil {
t.Fatalf("Apply() expected nil error for nil Thinking, got %v", err)
}
// Verify it's the correct error type
thinkErr, ok := err.(*thinking.ThinkingError)
if !ok {
t.Fatalf("Apply() error type = %T, want *thinking.ThinkingError", err)
}
if thinkErr.Code != thinking.ErrThinkingNotSupported {
t.Fatalf("Apply() error code = %v, want %v", thinkErr.Code, thinking.ErrThinkingNotSupported)
if string(got) != string(body) {
t.Fatalf("expected body unchanged, got %s", string(got))
}
}
@@ -252,17 +247,14 @@ func TestGeminiCLIApplyEmptyModelID(t *testing.T) {
applier := NewApplier()
config := thinking.ThinkingConfig{Mode: thinking.ModeBudget, Budget: 8192}
modelInfo := &registry.ModelInfo{ID: "", Thinking: nil}
body := []byte(`{"request":{"generationConfig":{"thinkingConfig":{"thinkingBudget":8192}}}}`)
_, err := applier.Apply([]byte(`{}`), config, modelInfo)
if err == nil {
t.Fatal("Apply() with empty modelID and nil Thinking should error")
got, err := applier.Apply(body, config, modelInfo)
if err != nil {
t.Fatalf("Apply() expected nil error, got %v", err)
}
thinkErr, ok := err.(*thinking.ThinkingError)
if !ok {
t.Fatalf("Apply() error type = %T, want *thinking.ThinkingError", err)
}
if thinkErr.Model != "unknown" {
t.Fatalf("Apply() error model = %q, want %q", thinkErr.Model, "unknown")
if string(got) != string(body) {
t.Fatalf("expected body unchanged, got %s", string(got))
}
}

View File

@@ -54,15 +54,11 @@ func init() {
// "reasoning_split": true
// }
func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) {
if modelInfo == nil {
if thinking.IsUserDefinedModel(modelInfo) {
return body, nil
}
if modelInfo.Thinking == nil {
modelID := modelInfo.ID
if modelID == "" {
modelID = "unknown"
}
return nil, thinking.NewThinkingErrorWithModel(thinking.ErrThinkingNotSupported, "thinking not supported for this model", modelID)
return body, nil
}
if isGLMModel(modelInfo.ID) {

View File

@@ -73,33 +73,23 @@ func TestApplyMissingThinkingSupport(t *testing.T) {
applier := NewApplier()
tests := []struct {
name string
modelID string
wantModel string
name string
modelID string
}{
{"model id", "glm-4.6", "glm-4.6"},
{"empty model id", "", "unknown"},
{"model id", "glm-4.6"},
{"empty model id", ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
modelInfo := &registry.ModelInfo{ID: tt.modelID}
got, err := applier.Apply([]byte(`{"model":"`+tt.modelID+`"}`), thinking.ThinkingConfig{}, modelInfo)
if err == nil {
t.Fatalf("expected error, got nil")
body := []byte(`{"model":"` + tt.modelID + `"}`)
got, err := applier.Apply(body, thinking.ThinkingConfig{}, modelInfo)
if err != nil {
t.Fatalf("expected nil error, got %v", err)
}
if got != nil {
t.Fatalf("expected nil body on error, got %s", string(got))
}
thinkingErr, ok := err.(*thinking.ThinkingError)
if !ok {
t.Fatalf("expected ThinkingError, got %T", err)
}
if thinkingErr.Code != thinking.ErrThinkingNotSupported {
t.Fatalf("expected code %s, got %s", thinking.ErrThinkingNotSupported, thinkingErr.Code)
}
if thinkingErr.Model != tt.wantModel {
t.Fatalf("expected model %s, got %s", tt.wantModel, thinkingErr.Model)
if string(got) != string(body) {
t.Fatalf("expected body unchanged, got %s", string(got))
}
})
}

View File

@@ -41,18 +41,11 @@ func init() {
// "reasoning_effort": "high"
// }
func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) {
if modelInfo == nil {
if thinking.IsUserDefinedModel(modelInfo) {
return applyCompatibleOpenAI(body, config)
}
if modelInfo.Thinking == nil {
if modelInfo.Type == "" {
modelID := modelInfo.ID
if modelID == "" {
modelID = "unknown"
}
return nil, thinking.NewThinkingErrorWithModel(thinking.ErrThinkingNotSupported, "thinking not supported for this model", modelID)
}
return applyCompatibleOpenAI(body, config)
return body, nil
}
// Only handle ModeLevel and ModeNone; other modes pass through unchanged.

View File

@@ -57,22 +57,13 @@ func TestApplyNilModelInfo(t *testing.T) {
func TestApplyMissingThinkingSupport(t *testing.T) {
applier := NewApplier()
modelInfo := &registry.ModelInfo{ID: "gpt-5.2"}
got, err := applier.Apply([]byte(`{"model":"gpt-5.2"}`), thinking.ThinkingConfig{}, modelInfo)
if err == nil {
t.Fatalf("expected error, got nil")
body := []byte(`{"model":"gpt-5.2"}`)
got, err := applier.Apply(body, thinking.ThinkingConfig{}, modelInfo)
if err != nil {
t.Fatalf("expected nil error, got %v", err)
}
if got != nil {
t.Fatalf("expected nil body on error, got %s", string(got))
}
thinkingErr, ok := err.(*thinking.ThinkingError)
if !ok {
t.Fatalf("expected ThinkingError, got %T", err)
}
if thinkingErr.Code != thinking.ErrThinkingNotSupported {
t.Fatalf("expected code %s, got %s", thinking.ErrThinkingNotSupported, thinkingErr.Code)
}
if thinkingErr.Model != "gpt-5.2" {
t.Fatalf("expected model gpt-5.2, got %s", thinkingErr.Model)
if string(got) != string(body) {
t.Fatalf("expected body unchanged, got %s", string(got))
}
}