mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-18 20:30:51 +08:00
refactor: improve thinking logic
This commit is contained in:
128
internal/thinking/provider/geminicli/apply.go
Normal file
128
internal/thinking/provider/geminicli/apply.go
Normal file
@@ -0,0 +1,128 @@
|
||||
// Package geminicli implements thinking configuration for Gemini CLI API format.
|
||||
//
|
||||
// Gemini CLI uses request.generationConfig.thinkingConfig.* path instead of
|
||||
// generationConfig.thinkingConfig.* used by standard Gemini API.
|
||||
package geminicli
|
||||
|
||||
import (
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/thinking"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
|
||||
// Applier applies thinking configuration for Gemini CLI API format.
|
||||
type Applier struct{}
|
||||
|
||||
var _ thinking.ProviderApplier = (*Applier)(nil)
|
||||
|
||||
// NewApplier creates a new Gemini CLI thinking applier.
|
||||
func NewApplier() *Applier {
|
||||
return &Applier{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
applier := NewApplier()
|
||||
thinking.RegisterProvider("gemini-cli", applier)
|
||||
thinking.RegisterProvider("antigravity", applier)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
if config.Mode != thinking.ModeBudget && config.Mode != thinking.ModeLevel && config.Mode != thinking.ModeNone && config.Mode != thinking.ModeAuto {
|
||||
return body, nil
|
||||
}
|
||||
|
||||
if len(body) == 0 || !gjson.ValidBytes(body) {
|
||||
body = []byte(`{}`)
|
||||
}
|
||||
|
||||
// ModeAuto: Always use Budget format with thinkingBudget=-1
|
||||
if config.Mode == thinking.ModeAuto {
|
||||
return a.applyBudgetFormat(body, config)
|
||||
}
|
||||
|
||||
// For non-auto modes, choose format based on model capabilities
|
||||
support := modelInfo.Thinking
|
||||
if len(support.Levels) > 0 {
|
||||
return a.applyLevelFormat(body, config)
|
||||
}
|
||||
return a.applyBudgetFormat(body, config)
|
||||
}
|
||||
|
||||
func (a *Applier) applyCompatible(body []byte, config thinking.ThinkingConfig) ([]byte, error) {
|
||||
if config.Mode != thinking.ModeBudget && config.Mode != thinking.ModeLevel && config.Mode != thinking.ModeNone && config.Mode != thinking.ModeAuto {
|
||||
return body, nil
|
||||
}
|
||||
|
||||
if len(body) == 0 || !gjson.ValidBytes(body) {
|
||||
body = []byte(`{}`)
|
||||
}
|
||||
|
||||
if config.Mode == thinking.ModeAuto {
|
||||
return a.applyBudgetFormat(body, config)
|
||||
}
|
||||
|
||||
if config.Mode == thinking.ModeLevel || (config.Mode == thinking.ModeNone && config.Level != "") {
|
||||
return a.applyLevelFormat(body, config)
|
||||
}
|
||||
|
||||
return a.applyBudgetFormat(body, config)
|
||||
}
|
||||
|
||||
func (a *Applier) applyLevelFormat(body []byte, config thinking.ThinkingConfig) ([]byte, error) {
|
||||
// Remove conflicting field to avoid both thinkingLevel and thinkingBudget in output
|
||||
result, _ := sjson.DeleteBytes(body, "request.generationConfig.thinkingConfig.thinkingBudget")
|
||||
|
||||
if config.Mode == thinking.ModeNone {
|
||||
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.includeThoughts", false)
|
||||
if config.Level != "" {
|
||||
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.thinkingLevel", string(config.Level))
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Only handle ModeLevel - budget conversion should be done by upper layer
|
||||
if config.Mode != thinking.ModeLevel {
|
||||
return body, nil
|
||||
}
|
||||
|
||||
level := string(config.Level)
|
||||
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.thinkingLevel", level)
|
||||
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.includeThoughts", true)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (a *Applier) applyBudgetFormat(body []byte, config thinking.ThinkingConfig) ([]byte, error) {
|
||||
// Remove conflicting field to avoid both thinkingLevel and thinkingBudget in output
|
||||
result, _ := sjson.DeleteBytes(body, "request.generationConfig.thinkingConfig.thinkingLevel")
|
||||
|
||||
budget := config.Budget
|
||||
includeThoughts := false
|
||||
switch config.Mode {
|
||||
case thinking.ModeNone:
|
||||
includeThoughts = false
|
||||
case thinking.ModeAuto:
|
||||
includeThoughts = true
|
||||
default:
|
||||
includeThoughts = budget > 0
|
||||
}
|
||||
|
||||
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.thinkingBudget", budget)
|
||||
result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.includeThoughts", includeThoughts)
|
||||
return result, nil
|
||||
}
|
||||
382
internal/thinking/provider/geminicli/apply_test.go
Normal file
382
internal/thinking/provider/geminicli/apply_test.go
Normal file
@@ -0,0 +1,382 @@
|
||||
// Package geminicli implements thinking configuration for Gemini CLI API format.
|
||||
package geminicli
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/thinking"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func TestNewApplier(t *testing.T) {
|
||||
applier := NewApplier()
|
||||
if applier == nil {
|
||||
t.Fatal("NewApplier() returned nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplierImplementsInterface(t *testing.T) {
|
||||
// Compile-time check: if Applier doesn't implement the interface, this won't compile
|
||||
var _ thinking.ProviderApplier = (*Applier)(nil)
|
||||
}
|
||||
|
||||
// TestGeminiCLIApply tests the Gemini CLI thinking applier.
|
||||
//
|
||||
// Gemini CLI uses request.generationConfig.thinkingConfig.* path.
|
||||
// Behavior mirrors Gemini applier but with different JSON path prefix.
|
||||
func TestGeminiCLIApply(t *testing.T) {
|
||||
applier := NewApplier()
|
||||
tests := []struct {
|
||||
name string
|
||||
model string
|
||||
config thinking.ThinkingConfig
|
||||
wantField string
|
||||
wantValue interface{}
|
||||
wantIncludeThoughts bool
|
||||
}{
|
||||
// Budget mode (no Levels)
|
||||
{"budget 8k", "gemini-cli-budget", thinking.ThinkingConfig{Mode: thinking.ModeBudget, Budget: 8192}, "thinkingBudget", 8192, true},
|
||||
{"budget zero", "gemini-cli-budget", thinking.ThinkingConfig{Mode: thinking.ModeBudget, Budget: 0}, "thinkingBudget", 0, false},
|
||||
{"none mode", "gemini-cli-budget", thinking.ThinkingConfig{Mode: thinking.ModeNone, Budget: 0}, "thinkingBudget", 0, false},
|
||||
{"auto mode", "gemini-cli-budget", thinking.ThinkingConfig{Mode: thinking.ModeAuto, Budget: -1}, "thinkingBudget", -1, true},
|
||||
|
||||
// Level mode (has Levels)
|
||||
{"level high", "gemini-cli-level", thinking.ThinkingConfig{Mode: thinking.ModeLevel, Level: thinking.LevelHigh}, "thinkingLevel", "high", true},
|
||||
{"level low", "gemini-cli-level", thinking.ThinkingConfig{Mode: thinking.ModeLevel, Level: thinking.LevelLow}, "thinkingLevel", "low", true},
|
||||
{"level minimal", "gemini-cli-level", thinking.ThinkingConfig{Mode: thinking.ModeLevel, Level: thinking.LevelMinimal}, "thinkingLevel", "minimal", true},
|
||||
// ModeAuto with Levels model still uses thinkingBudget=-1
|
||||
{"auto with levels", "gemini-cli-level", thinking.ThinkingConfig{Mode: thinking.ModeAuto, Budget: -1}, "thinkingBudget", -1, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
modelInfo := buildGeminiCLIModelInfo(tt.model)
|
||||
result, err := applier.Apply([]byte(`{}`), tt.config, modelInfo)
|
||||
if err != nil {
|
||||
t.Fatalf("Apply() error = %v", err)
|
||||
}
|
||||
|
||||
gotField := gjson.GetBytes(result, "request.generationConfig.thinkingConfig."+tt.wantField)
|
||||
switch want := tt.wantValue.(type) {
|
||||
case int:
|
||||
if int(gotField.Int()) != want {
|
||||
t.Fatalf("%s = %d, want %d", tt.wantField, gotField.Int(), want)
|
||||
}
|
||||
case string:
|
||||
if gotField.String() != want {
|
||||
t.Fatalf("%s = %q, want %q", tt.wantField, gotField.String(), want)
|
||||
}
|
||||
case bool:
|
||||
if gotField.Bool() != want {
|
||||
t.Fatalf("%s = %v, want %v", tt.wantField, gotField.Bool(), want)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("unsupported wantValue type %T", tt.wantValue)
|
||||
}
|
||||
|
||||
gotIncludeThoughts := gjson.GetBytes(result, "request.generationConfig.thinkingConfig.includeThoughts").Bool()
|
||||
if gotIncludeThoughts != tt.wantIncludeThoughts {
|
||||
t.Fatalf("includeThoughts = %v, want %v", gotIncludeThoughts, tt.wantIncludeThoughts)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGeminiCLIApplyModeNoneWithLevel tests ModeNone with Level model.
|
||||
// When ModeNone is used with a model that has Levels, includeThoughts should be false.
|
||||
func TestGeminiCLIApplyModeNoneWithLevel(t *testing.T) {
|
||||
applier := NewApplier()
|
||||
modelInfo := buildGeminiCLIModelInfo("gemini-cli-level")
|
||||
config := thinking.ThinkingConfig{Mode: thinking.ModeNone, Level: thinking.LevelLow}
|
||||
|
||||
result, err := applier.Apply([]byte(`{}`), config, modelInfo)
|
||||
if err != nil {
|
||||
t.Fatalf("Apply() error = %v", err)
|
||||
}
|
||||
|
||||
gotIncludeThoughts := gjson.GetBytes(result, "request.generationConfig.thinkingConfig.includeThoughts").Bool()
|
||||
if gotIncludeThoughts != false {
|
||||
t.Fatalf("includeThoughts = %v, want %v", gotIncludeThoughts, false)
|
||||
}
|
||||
|
||||
gotLevel := gjson.GetBytes(result, "request.generationConfig.thinkingConfig.thinkingLevel").String()
|
||||
if gotLevel != "low" {
|
||||
t.Fatalf("thinkingLevel = %q, want %q", gotLevel, "low")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGeminiCLIApplyInvalidBody tests Apply behavior with invalid body inputs.
|
||||
func TestGeminiCLIApplyInvalidBody(t *testing.T) {
|
||||
applier := NewApplier()
|
||||
modelInfo := buildGeminiCLIModelInfo("gemini-cli-budget")
|
||||
config := thinking.ThinkingConfig{Mode: thinking.ModeBudget, Budget: 8192}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
body []byte
|
||||
}{
|
||||
{"nil body", nil},
|
||||
{"empty body", []byte{}},
|
||||
{"invalid json", []byte("{\"not json\"")},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := applier.Apply(tt.body, config, modelInfo)
|
||||
if err != nil {
|
||||
t.Fatalf("Apply() error = %v", err)
|
||||
}
|
||||
|
||||
gotBudget := int(gjson.GetBytes(result, "request.generationConfig.thinkingConfig.thinkingBudget").Int())
|
||||
if gotBudget != 8192 {
|
||||
t.Fatalf("thinkingBudget = %d, want %d", gotBudget, 8192)
|
||||
}
|
||||
|
||||
gotIncludeThoughts := gjson.GetBytes(result, "request.generationConfig.thinkingConfig.includeThoughts").Bool()
|
||||
if !gotIncludeThoughts {
|
||||
t.Fatalf("includeThoughts = %v, want %v", gotIncludeThoughts, true)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGeminiCLIApplyConflictingFields tests that conflicting fields are removed.
|
||||
//
|
||||
// When applying Budget format, any existing thinkingLevel should be removed.
|
||||
// When applying Level format, any existing thinkingBudget should be removed.
|
||||
func TestGeminiCLIApplyConflictingFields(t *testing.T) {
|
||||
applier := NewApplier()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
model string
|
||||
config thinking.ThinkingConfig
|
||||
existingBody string
|
||||
wantField string // expected field to exist
|
||||
wantNoField string // expected field to NOT exist
|
||||
}{
|
||||
// Budget format should remove existing thinkingLevel
|
||||
{
|
||||
"budget removes level",
|
||||
"gemini-cli-budget",
|
||||
thinking.ThinkingConfig{Mode: thinking.ModeBudget, Budget: 8192},
|
||||
`{"request":{"generationConfig":{"thinkingConfig":{"thinkingLevel":"high"}}}}`,
|
||||
"thinkingBudget",
|
||||
"thinkingLevel",
|
||||
},
|
||||
// Level format should remove existing thinkingBudget
|
||||
{
|
||||
"level removes budget",
|
||||
"gemini-cli-level",
|
||||
thinking.ThinkingConfig{Mode: thinking.ModeLevel, Level: thinking.LevelHigh},
|
||||
`{"request":{"generationConfig":{"thinkingConfig":{"thinkingBudget":8192}}}}`,
|
||||
"thinkingLevel",
|
||||
"thinkingBudget",
|
||||
},
|
||||
// ModeAuto uses budget format, should remove thinkingLevel
|
||||
{
|
||||
"auto removes level",
|
||||
"gemini-cli-level",
|
||||
thinking.ThinkingConfig{Mode: thinking.ModeAuto, Budget: -1},
|
||||
`{"request":{"generationConfig":{"thinkingConfig":{"thinkingLevel":"high"}}}}`,
|
||||
"thinkingBudget",
|
||||
"thinkingLevel",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
modelInfo := buildGeminiCLIModelInfo(tt.model)
|
||||
result, err := applier.Apply([]byte(tt.existingBody), tt.config, modelInfo)
|
||||
if err != nil {
|
||||
t.Fatalf("Apply() error = %v", err)
|
||||
}
|
||||
|
||||
// Verify expected field exists
|
||||
wantPath := "request.generationConfig.thinkingConfig." + tt.wantField
|
||||
if !gjson.GetBytes(result, wantPath).Exists() {
|
||||
t.Fatalf("%s should exist in result: %s", tt.wantField, string(result))
|
||||
}
|
||||
|
||||
// Verify conflicting field was removed
|
||||
noPath := "request.generationConfig.thinkingConfig." + tt.wantNoField
|
||||
if gjson.GetBytes(result, noPath).Exists() {
|
||||
t.Fatalf("%s should NOT exist in result: %s", tt.wantNoField, string(result))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGeminiCLIApplyThinkingNotSupported tests error handling when modelInfo.Thinking is nil.
|
||||
func TestGeminiCLIApplyThinkingNotSupported(t *testing.T) {
|
||||
applier := NewApplier()
|
||||
config := thinking.ThinkingConfig{Mode: thinking.ModeBudget, Budget: 8192}
|
||||
|
||||
// Model with nil Thinking support
|
||||
modelInfo := ®istry.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")
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGeminiCLIApplyNilModelInfo tests Apply behavior when modelInfo is nil.
|
||||
func TestGeminiCLIApplyNilModelInfo(t *testing.T) {
|
||||
applier := NewApplier()
|
||||
config := thinking.ThinkingConfig{Mode: thinking.ModeBudget, Budget: 8192}
|
||||
body := []byte(`{"existing": "data"}`)
|
||||
|
||||
result, err := applier.Apply(body, config, nil)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGeminiCLIApplyEmptyModelID tests Apply when modelID is empty.
|
||||
func TestGeminiCLIApplyEmptyModelID(t *testing.T) {
|
||||
applier := NewApplier()
|
||||
config := thinking.ThinkingConfig{Mode: thinking.ModeBudget, Budget: 8192}
|
||||
modelInfo := ®istry.ModelInfo{ID: "", Thinking: nil}
|
||||
|
||||
_, err := applier.Apply([]byte(`{}`), config, modelInfo)
|
||||
if err == nil {
|
||||
t.Fatal("Apply() with empty modelID and nil Thinking should error")
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGeminiCLIApplyModeBudgetWithLevels tests that ModeBudget with Levels model passes through.
|
||||
// Apply layer doesn't convert - upper layer should handle Budget→Level conversion.
|
||||
func TestGeminiCLIApplyModeBudgetWithLevels(t *testing.T) {
|
||||
applier := NewApplier()
|
||||
modelInfo := buildGeminiCLIModelInfo("gemini-cli-level")
|
||||
config := thinking.ThinkingConfig{Mode: thinking.ModeBudget, Budget: 8192}
|
||||
body := []byte(`{"existing": "data"}`)
|
||||
|
||||
result, err := applier.Apply(body, config, modelInfo)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGeminiCLIApplyUnsupportedMode tests behavior with unsupported Mode types.
|
||||
func TestGeminiCLIApplyUnsupportedMode(t *testing.T) {
|
||||
applier := NewApplier()
|
||||
body := []byte(`{"existing": "data"}`)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
model string
|
||||
config thinking.ThinkingConfig
|
||||
}{
|
||||
{"unknown mode with budget model", "gemini-cli-budget", thinking.ThinkingConfig{Mode: thinking.ThinkingMode(99), Budget: 8192}},
|
||||
{"unknown mode with level model", "gemini-cli-level", thinking.ThinkingConfig{Mode: thinking.ThinkingMode(99), Level: thinking.LevelHigh}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
modelInfo := buildGeminiCLIModelInfo(tt.model)
|
||||
result, err := applier.Apply(body, tt.config, modelInfo)
|
||||
if err != nil {
|
||||
t.Fatalf("Apply() error = %v", err)
|
||||
}
|
||||
// Unsupported modes return original body unchanged
|
||||
if string(result) != string(body) {
|
||||
t.Fatalf("Apply() with unsupported mode should return original body, got: %s", result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestAntigravityUsesGeminiCLIFormat tests that antigravity provider uses gemini-cli format.
|
||||
// Antigravity is registered with the same applier as gemini-cli.
|
||||
func TestAntigravityUsesGeminiCLIFormat(t *testing.T) {
|
||||
applier := NewApplier()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config thinking.ThinkingConfig
|
||||
modelInfo *registry.ModelInfo
|
||||
wantField string
|
||||
}{
|
||||
{
|
||||
"claude model budget",
|
||||
thinking.ThinkingConfig{Mode: thinking.ModeBudget, Budget: 16384},
|
||||
®istry.ModelInfo{ID: "gemini-claude-sonnet-4-5-thinking", Thinking: ®istry.ThinkingSupport{Min: 1024, Max: 200000}},
|
||||
"request.generationConfig.thinkingConfig.thinkingBudget",
|
||||
},
|
||||
{
|
||||
"opus model budget",
|
||||
thinking.ThinkingConfig{Mode: thinking.ModeBudget, Budget: 32768},
|
||||
®istry.ModelInfo{ID: "gemini-claude-opus-4-5-thinking", Thinking: ®istry.ThinkingSupport{Min: 1024, Max: 200000}},
|
||||
"request.generationConfig.thinkingConfig.thinkingBudget",
|
||||
},
|
||||
{
|
||||
"model with levels",
|
||||
thinking.ThinkingConfig{Mode: thinking.ModeLevel, Level: thinking.LevelHigh},
|
||||
®istry.ModelInfo{ID: "some-model-with-levels", Thinking: ®istry.ThinkingSupport{Min: 1024, Max: 200000, Levels: []string{"low", "high"}}},
|
||||
"request.generationConfig.thinkingConfig.thinkingLevel",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := applier.Apply([]byte(`{}`), tt.config, tt.modelInfo)
|
||||
if err != nil {
|
||||
t.Fatalf("Apply() error = %v", err)
|
||||
}
|
||||
|
||||
if !gjson.GetBytes(got, tt.wantField).Exists() {
|
||||
t.Fatalf("expected field %s in output: %s", tt.wantField, string(got))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func buildGeminiCLIModelInfo(modelID string) *registry.ModelInfo {
|
||||
support := ®istry.ThinkingSupport{}
|
||||
switch modelID {
|
||||
case "gemini-cli-budget":
|
||||
support.Min = 0
|
||||
support.Max = 32768
|
||||
support.ZeroAllowed = true
|
||||
support.DynamicAllowed = true
|
||||
case "gemini-cli-level":
|
||||
support.Min = 128
|
||||
support.Max = 32768
|
||||
support.ZeroAllowed = false
|
||||
support.DynamicAllowed = true
|
||||
support.Levels = []string{"minimal", "low", "medium", "high"}
|
||||
default:
|
||||
// Unknown model - return nil Thinking to trigger error path
|
||||
return ®istry.ModelInfo{ID: modelID, Thinking: nil}
|
||||
}
|
||||
return ®istry.ModelInfo{
|
||||
ID: modelID,
|
||||
Thinking: support,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user