| |
| |
| |
| |
| |
| |
| package claude |
|
|
| 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" |
| ) |
|
|
| |
| |
| type Applier struct{} |
|
|
| |
| func NewApplier() *Applier { |
| return &Applier{} |
| } |
|
|
| func init() { |
| thinking.RegisterProvider("claude", NewApplier()) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func (a *Applier) Apply(body []byte, config thinking.ThinkingConfig, modelInfo *registry.ModelInfo) ([]byte, error) { |
| if thinking.IsUserDefinedModel(modelInfo) { |
| return applyCompatibleClaude(body, config) |
| } |
| if modelInfo.Thinking == nil { |
| return body, nil |
| } |
|
|
| |
| |
| if config.Mode != thinking.ModeBudget && config.Mode != thinking.ModeNone { |
| return body, nil |
| } |
|
|
| if len(body) == 0 || !gjson.ValidBytes(body) { |
| body = []byte(`{}`) |
| } |
|
|
| |
| |
| if config.Budget == 0 { |
| result, _ := sjson.SetBytes(body, "thinking.type", "disabled") |
| result, _ = sjson.DeleteBytes(result, "thinking.budget_tokens") |
| return result, nil |
| } |
|
|
| result, _ := sjson.SetBytes(body, "thinking.type", "enabled") |
| result, _ = sjson.SetBytes(result, "thinking.budget_tokens", config.Budget) |
|
|
| |
| result = a.normalizeClaudeBudget(result, config.Budget, modelInfo) |
| return result, nil |
| } |
|
|
| |
| |
| func (a *Applier) normalizeClaudeBudget(body []byte, budgetTokens int, modelInfo *registry.ModelInfo) []byte { |
| if budgetTokens <= 0 { |
| return body |
| } |
|
|
| |
| |
| |
| |
| |
|
|
| effectiveMax, setDefaultMax := a.effectiveMaxTokens(body, modelInfo) |
| if setDefaultMax && effectiveMax > 0 { |
| body, _ = sjson.SetBytes(body, "max_tokens", effectiveMax) |
| } |
|
|
| |
| adjustedBudget := budgetTokens |
| if effectiveMax > 0 && adjustedBudget >= effectiveMax { |
| adjustedBudget = effectiveMax - 1 |
| } |
|
|
| minBudget := 0 |
| if modelInfo != nil && modelInfo.Thinking != nil { |
| minBudget = modelInfo.Thinking.Min |
| } |
| if minBudget > 0 && adjustedBudget > 0 && adjustedBudget < minBudget { |
| |
| |
| return body |
| } |
|
|
| if adjustedBudget != budgetTokens { |
| body, _ = sjson.SetBytes(body, "thinking.budget_tokens", adjustedBudget) |
| } |
|
|
| return body |
| } |
|
|
| |
| |
| |
| func (a *Applier) effectiveMaxTokens(body []byte, modelInfo *registry.ModelInfo) (max int, fromModel bool) { |
| if maxTok := gjson.GetBytes(body, "max_tokens"); maxTok.Exists() && maxTok.Int() > 0 { |
| return int(maxTok.Int()), false |
| } |
| if modelInfo != nil && modelInfo.MaxCompletionTokens > 0 { |
| return modelInfo.MaxCompletionTokens, true |
| } |
| return 0, false |
| } |
|
|
| func applyCompatibleClaude(body []byte, config thinking.ThinkingConfig) ([]byte, error) { |
| if config.Mode != thinking.ModeBudget && config.Mode != thinking.ModeNone && config.Mode != thinking.ModeAuto { |
| return body, nil |
| } |
|
|
| if len(body) == 0 || !gjson.ValidBytes(body) { |
| body = []byte(`{}`) |
| } |
|
|
| switch config.Mode { |
| case thinking.ModeNone: |
| result, _ := sjson.SetBytes(body, "thinking.type", "disabled") |
| result, _ = sjson.DeleteBytes(result, "thinking.budget_tokens") |
| return result, nil |
| case thinking.ModeAuto: |
| result, _ := sjson.SetBytes(body, "thinking.type", "enabled") |
| result, _ = sjson.DeleteBytes(result, "thinking.budget_tokens") |
| return result, nil |
| default: |
| result, _ := sjson.SetBytes(body, "thinking.type", "enabled") |
| result, _ = sjson.SetBytes(result, "thinking.budget_tokens", config.Budget) |
| return result, nil |
| } |
| } |
|
|