| package util |
|
|
| import ( |
| "encoding/json" |
| "strconv" |
| "strings" |
| ) |
|
|
| const ( |
| ThinkingBudgetMetadataKey = "thinking_budget" |
| ThinkingIncludeThoughtsMetadataKey = "thinking_include_thoughts" |
| ReasoningEffortMetadataKey = "reasoning_effort" |
| ThinkingOriginalModelMetadataKey = "thinking_original_model" |
| ModelMappingOriginalModelMetadataKey = "model_mapping_original_model" |
| ) |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func NormalizeThinkingModel(modelName string) (string, map[string]any) { |
| if modelName == "" { |
| return modelName, nil |
| } |
|
|
| baseModel := modelName |
|
|
| var ( |
| budgetOverride *int |
| reasoningEffort *string |
| matched bool |
| ) |
|
|
| |
| if idx := strings.LastIndex(modelName, "("); idx != -1 { |
| if !strings.HasSuffix(modelName, ")") { |
| |
| return baseModel, nil |
| } |
|
|
| value := modelName[idx+1 : len(modelName)-1] |
| if value == "" { |
| |
| return baseModel, nil |
| } |
|
|
| candidateBase := modelName[:idx] |
|
|
| |
| if parsed, ok := parseIntPrefix(value); ok { |
| |
| baseModel = candidateBase |
| budgetOverride = &parsed |
| matched = true |
| } else { |
| |
| baseModel = candidateBase |
| raw := strings.ToLower(strings.TrimSpace(value)) |
| if raw != "" { |
| reasoningEffort = &raw |
| matched = true |
| } |
| } |
| } |
|
|
| if !matched { |
| return baseModel, nil |
| } |
|
|
| metadata := map[string]any{ |
| ThinkingOriginalModelMetadataKey: modelName, |
| } |
| if budgetOverride != nil { |
| metadata[ThinkingBudgetMetadataKey] = *budgetOverride |
| } |
| if reasoningEffort != nil { |
| metadata[ReasoningEffortMetadataKey] = *reasoningEffort |
| } |
| return baseModel, metadata |
| } |
|
|
| |
| |
| func ThinkingFromMetadata(metadata map[string]any) (*int, *bool, *string, bool) { |
| if len(metadata) == 0 { |
| return nil, nil, nil, false |
| } |
|
|
| var ( |
| budgetPtr *int |
| includePtr *bool |
| effortPtr *string |
| matched bool |
| ) |
|
|
| readBudget := func(key string) { |
| if budgetPtr != nil { |
| return |
| } |
| if raw, ok := metadata[key]; ok { |
| if v, okNumber := parseNumberToInt(raw); okNumber { |
| budget := v |
| budgetPtr = &budget |
| matched = true |
| } |
| } |
| } |
|
|
| readInclude := func(key string) { |
| if includePtr != nil { |
| return |
| } |
| if raw, ok := metadata[key]; ok { |
| switch v := raw.(type) { |
| case bool: |
| val := v |
| includePtr = &val |
| matched = true |
| case *bool: |
| if v != nil { |
| val := *v |
| includePtr = &val |
| matched = true |
| } |
| } |
| } |
| } |
|
|
| readEffort := func(key string) { |
| if effortPtr != nil { |
| return |
| } |
| if raw, ok := metadata[key]; ok { |
| if val, okStr := raw.(string); okStr && strings.TrimSpace(val) != "" { |
| normalized := strings.ToLower(strings.TrimSpace(val)) |
| effortPtr = &normalized |
| matched = true |
| } |
| } |
| } |
|
|
| readBudget(ThinkingBudgetMetadataKey) |
| readBudget(GeminiThinkingBudgetMetadataKey) |
| readInclude(ThinkingIncludeThoughtsMetadataKey) |
| readInclude(GeminiIncludeThoughtsMetadataKey) |
| readEffort(ReasoningEffortMetadataKey) |
| readEffort("reasoning.effort") |
|
|
| return budgetPtr, includePtr, effortPtr, matched |
| } |
|
|
| |
| |
| func ResolveThinkingConfigFromMetadata(model string, metadata map[string]any) (*int, *bool, bool) { |
| budget, include, effort, matched := ThinkingFromMetadata(metadata) |
| if !matched { |
| return nil, nil, false |
| } |
| |
| |
| if ModelUsesThinkingLevels(model) { |
| return nil, nil, false |
| } |
|
|
| if budget == nil && effort != nil { |
| if derived, ok := ThinkingEffortToBudget(model, *effort); ok { |
| budget = &derived |
| } |
| } |
| return budget, include, budget != nil || include != nil || effort != nil |
| } |
|
|
| |
| |
| func ReasoningEffortFromMetadata(metadata map[string]any) (string, bool) { |
| budget, include, effort, matched := ThinkingFromMetadata(metadata) |
| if !matched { |
| return "", false |
| } |
| if effort != nil && *effort != "" { |
| return strings.ToLower(strings.TrimSpace(*effort)), true |
| } |
| if budget != nil { |
| switch *budget { |
| case -1: |
| return "auto", true |
| case 0: |
| return "none", true |
| } |
| } |
| if include != nil && !*include { |
| return "none", true |
| } |
| return "", true |
| } |
|
|
| |
| |
| func ResolveOriginalModel(model string, metadata map[string]any) string { |
| normalize := func(name string) string { |
| if name == "" { |
| return "" |
| } |
| if base, _ := NormalizeThinkingModel(name); base != "" { |
| return base |
| } |
| return strings.TrimSpace(name) |
| } |
|
|
| if metadata != nil { |
| if v, ok := metadata[ModelMappingOriginalModelMetadataKey]; ok { |
| if s, okStr := v.(string); okStr && strings.TrimSpace(s) != "" { |
| if base := normalize(s); base != "" { |
| return base |
| } |
| } |
| } |
| if v, ok := metadata[ThinkingOriginalModelMetadataKey]; ok { |
| if s, okStr := v.(string); okStr && strings.TrimSpace(s) != "" { |
| if base := normalize(s); base != "" { |
| return base |
| } |
| } |
| } |
| if v, ok := metadata[GeminiOriginalModelMetadataKey]; ok { |
| if s, okStr := v.(string); okStr && strings.TrimSpace(s) != "" { |
| if base := normalize(s); base != "" { |
| return base |
| } |
| } |
| } |
| } |
| |
| if base := normalize(model); base != "" { |
| return base |
| } |
| return model |
| } |
|
|
| func parseIntPrefix(value string) (int, bool) { |
| if value == "" { |
| return 0, false |
| } |
| digits := strings.TrimLeft(value, "-") |
| if digits == "" { |
| return 0, false |
| } |
| end := len(digits) |
| for i := 0; i < len(digits); i++ { |
| if digits[i] < '0' || digits[i] > '9' { |
| end = i |
| break |
| } |
| } |
| if end == 0 { |
| return 0, false |
| } |
| val, err := strconv.Atoi(digits[:end]) |
| if err != nil { |
| return 0, false |
| } |
| return val, true |
| } |
|
|
| func parseNumberToInt(raw any) (int, bool) { |
| switch v := raw.(type) { |
| case int: |
| return v, true |
| case int32: |
| return int(v), true |
| case int64: |
| return int(v), true |
| case float64: |
| return int(v), true |
| case json.Number: |
| if val, err := v.Int64(); err == nil { |
| return int(val), true |
| } |
| case string: |
| if strings.TrimSpace(v) == "" { |
| return 0, false |
| } |
| if parsed, err := strconv.Atoi(strings.TrimSpace(v)); err == nil { |
| return parsed, true |
| } |
| } |
| return 0, false |
| } |
|
|