| |
| |
| |
| |
| |
| |
| package antigravity |
|
|
| import ( |
| "strings" |
|
|
| "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" |
| ) |
|
|
| |
| type Applier struct{} |
|
|
| var _ thinking.ProviderApplier = (*Applier)(nil) |
|
|
| |
| func NewApplier() *Applier { |
| return &Applier{} |
| } |
|
|
| func init() { |
| thinking.RegisterProvider("antigravity", NewApplier()) |
| } |
|
|
| |
| |
| |
| |
| |
| func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) { |
| if thinking.IsUserDefinedModel(modelInfo) { |
| return a.applyCompatible(body, config, modelInfo) |
| } |
| if modelInfo.Thinking == nil { |
| return body, nil |
| } |
|
|
| 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(`{}`) |
| } |
|
|
| isClaude := strings.Contains(strings.ToLower(modelInfo.ID), "claude") |
|
|
| |
| if config.Mode == thinking.ModeAuto { |
| return a.applyBudgetFormat(body, config, modelInfo, isClaude) |
| } |
| if config.Mode == thinking.ModeBudget { |
| return a.applyBudgetFormat(body, config, modelInfo, isClaude) |
| } |
|
|
| |
| support := modelInfo.Thinking |
| if len(support.Levels) > 0 { |
| return a.applyLevelFormat(body, config) |
| } |
| return a.applyBudgetFormat(body, config, modelInfo, isClaude) |
| } |
|
|
| func (a *Applier) applyCompatible(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]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(`{}`) |
| } |
|
|
| isClaude := false |
| if modelInfo != nil { |
| isClaude = strings.Contains(strings.ToLower(modelInfo.ID), "claude") |
| } |
|
|
| if config.Mode == thinking.ModeAuto { |
| return a.applyBudgetFormat(body, config, modelInfo, isClaude) |
| } |
|
|
| if config.Mode == thinking.ModeLevel || (config.Mode == thinking.ModeNone && config.Level != "") { |
| return a.applyLevelFormat(body, config) |
| } |
|
|
| return a.applyBudgetFormat(body, config, modelInfo, isClaude) |
| } |
|
|
| func (a *Applier) applyLevelFormat(body []byte, config thinking.ThinkingConfig) ([]byte, error) { |
| |
| result, _ := sjson.DeleteBytes(body, "request.generationConfig.thinkingConfig.thinkingBudget") |
| |
| result, _ = sjson.DeleteBytes(result, "request.generationConfig.thinkingConfig.include_thoughts") |
|
|
| 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 |
| } |
|
|
| |
| 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, modelInfo *registry.ModelInfo, isClaude bool) ([]byte, error) { |
| |
| result, _ := sjson.DeleteBytes(body, "request.generationConfig.thinkingConfig.thinkingLevel") |
| |
| result, _ = sjson.DeleteBytes(result, "request.generationConfig.thinkingConfig.include_thoughts") |
|
|
| budget := config.Budget |
| includeThoughts := false |
| switch config.Mode { |
| case thinking.ModeNone: |
| includeThoughts = false |
| case thinking.ModeAuto: |
| includeThoughts = true |
| default: |
| includeThoughts = budget > 0 |
| } |
|
|
| |
| if isClaude && modelInfo != nil { |
| budget, result = a.normalizeClaudeBudget(budget, result, modelInfo) |
| |
| if budget == -2 { |
| return result, nil |
| } |
| } |
|
|
| result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.thinkingBudget", budget) |
| result, _ = sjson.SetBytes(result, "request.generationConfig.thinkingConfig.includeThoughts", includeThoughts) |
| return result, nil |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| func (a *Applier) normalizeClaudeBudget(budget int, payload []byte, modelInfo *registry.ModelInfo) (int, []byte) { |
| if modelInfo == nil { |
| return budget, payload |
| } |
|
|
| |
| effectiveMax, setDefaultMax := a.effectiveMaxTokens(payload, modelInfo) |
| if effectiveMax > 0 && budget >= effectiveMax { |
| budget = effectiveMax - 1 |
| } |
|
|
| |
| minBudget := 0 |
| if modelInfo.Thinking != nil { |
| minBudget = modelInfo.Thinking.Min |
| } |
| if minBudget > 0 && budget >= 0 && budget < minBudget { |
| |
| payload, _ = sjson.DeleteBytes(payload, "request.generationConfig.thinkingConfig") |
| return -2, payload |
| } |
|
|
| |
| if setDefaultMax && effectiveMax > 0 { |
| payload, _ = sjson.SetBytes(payload, "request.generationConfig.maxOutputTokens", effectiveMax) |
| } |
|
|
| return budget, payload |
| } |
|
|
| |
| |
| |
| func (a *Applier) effectiveMaxTokens(payload []byte, modelInfo *registry.ModelInfo) (max int, fromModel bool) { |
| if maxTok := gjson.GetBytes(payload, "request.generationConfig.maxOutputTokens"); maxTok.Exists() && maxTok.Int() > 0 { |
| return int(maxTok.Int()), false |
| } |
| if modelInfo != nil && modelInfo.MaxCompletionTokens > 0 { |
| return modelInfo.MaxCompletionTokens, true |
| } |
| return 0, false |
| } |
|
|