File size: 4,449 Bytes
bf9e111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package thinking

import (
	"strings"

	"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
)

// levelToBudgetMap defines the standard Level β†’ Budget mapping.
// All keys are lowercase; lookups should use strings.ToLower.
var levelToBudgetMap = map[string]int{
	"none":    0,
	"auto":    -1,
	"minimal": 512,
	"low":     1024,
	"medium":  8192,
	"high":    24576,
	"xhigh":   32768,
}

// ConvertLevelToBudget converts a thinking level to a budget value.
//
// This is a semantic conversion that maps discrete levels to numeric budgets.
// Level matching is case-insensitive.
//
// Level β†’ Budget mapping:
//   - none    β†’ 0
//   - auto    β†’ -1
//   - minimal β†’ 512
//   - low     β†’ 1024
//   - medium  β†’ 8192
//   - high    β†’ 24576
//   - xhigh   β†’ 32768
//
// Returns:
//   - budget: The converted budget value
//   - ok: true if level is valid, false otherwise
func ConvertLevelToBudget(level string) (int, bool) {
	budget, ok := levelToBudgetMap[strings.ToLower(level)]
	return budget, ok
}

// BudgetThreshold constants define the upper bounds for each thinking level.
// These are used by ConvertBudgetToLevel for range-based mapping.
const (
	// ThresholdMinimal is the upper bound for "minimal" level (1-512)
	ThresholdMinimal = 512
	// ThresholdLow is the upper bound for "low" level (513-1024)
	ThresholdLow = 1024
	// ThresholdMedium is the upper bound for "medium" level (1025-8192)
	ThresholdMedium = 8192
	// ThresholdHigh is the upper bound for "high" level (8193-24576)
	ThresholdHigh = 24576
)

// ConvertBudgetToLevel converts a budget value to the nearest thinking level.
//
// This is a semantic conversion that maps numeric budgets to discrete levels.
// Uses threshold-based mapping for range conversion.
//
// Budget β†’ Level thresholds:
//   - -1        β†’ auto
//   - 0         β†’ none
//   - 1-512     β†’ minimal
//   - 513-1024  β†’ low
//   - 1025-8192 β†’ medium
//   - 8193-24576 β†’ high
//   - 24577+    β†’ xhigh
//
// Returns:
//   - level: The converted thinking level string
//   - ok: true if budget is valid, false for invalid negatives (< -1)
func ConvertBudgetToLevel(budget int) (string, bool) {
	switch {
	case budget < -1:
		// Invalid negative values
		return "", false
	case budget == -1:
		return string(LevelAuto), true
	case budget == 0:
		return string(LevelNone), true
	case budget <= ThresholdMinimal:
		return string(LevelMinimal), true
	case budget <= ThresholdLow:
		return string(LevelLow), true
	case budget <= ThresholdMedium:
		return string(LevelMedium), true
	case budget <= ThresholdHigh:
		return string(LevelHigh), true
	default:
		return string(LevelXHigh), true
	}
}

// ModelCapability describes the thinking format support of a model.
type ModelCapability int

const (
	// CapabilityUnknown indicates modelInfo is nil (passthrough behavior, internal use).
	CapabilityUnknown ModelCapability = iota - 1
	// CapabilityNone indicates model doesn't support thinking (Thinking is nil).
	CapabilityNone
	// CapabilityBudgetOnly indicates the model supports numeric budgets only.
	CapabilityBudgetOnly
	// CapabilityLevelOnly indicates the model supports discrete levels only.
	CapabilityLevelOnly
	// CapabilityHybrid indicates the model supports both budgets and levels.
	CapabilityHybrid
)

// detectModelCapability determines the thinking format capability of a model.
//
// This is an internal function used by validation and conversion helpers.
// It analyzes the model's ThinkingSupport configuration to classify the model:
//   - CapabilityNone: modelInfo.Thinking is nil (model doesn't support thinking)
//   - CapabilityBudgetOnly: Has Min/Max but no Levels (Claude, Gemini 2.5)
//   - CapabilityLevelOnly: Has Levels but no Min/Max (OpenAI, iFlow)
//   - CapabilityHybrid: Has both Min/Max and Levels (Gemini 3)
//
// Note: Returns a special sentinel value when modelInfo itself is nil (unknown model).
func detectModelCapability(modelInfo *registry.ModelInfo) ModelCapability {
	if modelInfo == nil {
		return CapabilityUnknown // sentinel for "passthrough" behavior
	}
	if modelInfo.Thinking == nil {
		return CapabilityNone
	}
	support := modelInfo.Thinking
	hasBudget := support.Min > 0 || support.Max > 0
	hasLevels := len(support.Levels) > 0

	switch {
	case hasBudget && hasLevels:
		return CapabilityHybrid
	case hasBudget:
		return CapabilityBudgetOnly
	case hasLevels:
		return CapabilityLevelOnly
	default:
		return CapabilityNone
	}
}