v0.3.91 9a5c565

reasoning.go RawHistoryFolder

1package provider
2
3import "strings"
4
5var reasoningLevelOrder = []string{"", "minimum", "low", "medium", "high", "xhigh", "max"}
6
7// AvailableReasoningLevels returns the distinct reasoning levels supported by
8// a model. Optional per-model overrides can remove, remap, or extend protocol
9// defaults. The empty string represents off.
10func AvailableReasoningLevels(model Model) []string {
11	defaults := defaultReasoningLevels(model)
12	if !model.Reasoning || len(model.ReasoningLevelMap) == 0 {
13		return defaults
14	}
15
16	available := map[string]bool{"": containsReasoningLevel(defaults, "")}
17	for _, level := range reasoningLevelOrder[1:] {
18		if !containsReasoningLevel(defaults, level) {
19			if _, overridden := model.ReasoningLevelMap[level]; !overridden {
20				continue
21			}
22		}
23		effective := level
24		if mapped, overridden := model.ReasoningLevelMap[level]; overridden {
25			effective = NormalizeReasoning(mapped)
26		}
27		if reasoningLevelRank(effective) > 0 {
28			available[effective] = true
29		}
30	}
31
32	levels := []string{}
33	for _, level := range reasoningLevelOrder {
34		if available[level] {
35			levels = append(levels, level)
36		}
37	}
38	return levels
39}
40
41func defaultReasoningLevels(model Model) []string {
42	if !model.Reasoning {
43		return []string{""}
44	}
45
46	id := strings.ToLower(model.ID)
47	// Opus 5.5 always uses adaptive thinking, including at its lowest effort.
48	if id == "claude-opus-5-5" || id == "claude-opus-5.5" {
49		return []string{"low", "medium", "high", "xhigh", "max"}
50	}
51	if (model.Provider == "google" || model.Provider == "google-vertex") && strings.Contains(id, "gemini-3") {
52		if strings.Contains(id, "-pro") {
53			return []string{"", "low", "high"}
54		}
55		return []string{"", "minimum", "low", "medium", "high"}
56	}
57	if model.AdaptiveThinkingCompat {
58		return []string{"", "high"}
59	}
60	if model.AdaptiveThinking {
61		return []string{"", "low", "medium", "high", "xhigh", "max"}
62	}
63	if model.API == APIResponses || model.Provider == "openai-codex" || model.Provider == "openai-responses" || model.Provider == "azure-openai-responses" {
64		levels := []string{"", "low", "medium", "high", "xhigh"}
65		if supportsResponsesMaxEffort(id) {
66			levels = append(levels, "max")
67		}
68		return levels
69	}
70	if model.Provider == "google" || model.Provider == "google-vertex" {
71		if strings.Contains(id, "gemini-2.5") {
72			return []string{"", "minimum", "low", "medium", "high", "xhigh"}
73		}
74		return []string{""}
75	}
76	if usesReasoningBudget(model) {
77		return []string{"", "minimum", "low", "medium", "high", "xhigh"}
78	}
79	if model.Provider == "amazon-bedrock" {
80		return []string{""}
81	}
82	return []string{"", "low", "medium", "high"}
83}
84
85func containsReasoningLevel(levels []string, target string) bool {
86	for _, level := range levels {
87		if level == target {
88			return true
89		}
90	}
91	return false
92}
93
94func hasReasoningLevelOverride(model Model, level string) bool {
95	_, ok := model.ReasoningLevelMap[NormalizeReasoning(level)]
96	return ok
97}
98
99// ClampReasoningForModel maps a configured level to the nearest level exposed
100// for the active model. Ties prefer the higher level.
101func ClampReasoningForModel(model Model, level string) string {
102	normalized := NormalizeReasoning(level)
103	available := AvailableReasoningLevels(model)
104	if mapped, overridden := model.ReasoningLevelMap[normalized]; overridden {
105		target := NormalizeReasoning(mapped)
106		if target != "" && containsReasoningLevel(available, target) {
107			return target
108		}
109	}
110	for _, candidate := range available {
111		if candidate == normalized {
112			return candidate
113		}
114	}
115	return nearestReasoningLevel(available, normalized)
116}
117
118func nearestReasoningLevel(available []string, requested string) string {
119	if len(available) == 0 {
120		return ""
121	}
122	if requested == "" || len(available) == 1 {
123		return available[0]
124	}
125	requestedRank := reasoningLevelRank(requested)
126	if requestedRank == 0 {
127		return available[0]
128	}
129	best, bestDistance := available[0], len(reasoningLevelOrder)
130	for _, candidate := range available {
131		if candidate == "" {
132			continue
133		}
134		distance := reasoningLevelRank(candidate) - requestedRank
135		if distance < 0 {
136			distance = -distance
137		}
138		if distance <= bestDistance {
139			best, bestDistance = candidate, distance
140		}
141	}
142	return best
143}
144
145func reasoningLevelRank(level string) int {
146	for rank, candidate := range reasoningLevelOrder {
147		if candidate == level {
148			return rank
149		}
150	}
151	return 0
152}
153
154func usesReasoningBudget(model Model) bool {
155	switch model.Provider {
156	case "anthropic", "fireworks", "kimi", "minimax", "minimax-cn", "vercel-ai-gateway":
157		return true
158	}
159	return model.API == "anthropic"
160}
161
162// NormalizeReasoning canonicalizes zot's user-facing reasoning levels.
163// Empty string means reasoning is disabled. "maximum" remains
164// an alias for xhigh; "max" is the separate opt-in tier above it.
165func NormalizeReasoning(level string) string {
166	switch strings.ToLower(strings.TrimSpace(level)) {
167	case "", "off", "none", "no", "false", "disabled":
168		return ""
169	case "min", "minimal", "minimum":
170		return "minimum"
171	case "low":
172		return "low"
173	case "med", "medium":
174		return "medium"
175	case "hi", "high":
176		return "high"
177	case "xhigh", "maximum":
178		return "xhigh"
179	case "max":
180		return "max"
181	default:
182		return strings.ToLower(strings.TrimSpace(level))
183	}
184}
185
186// ReasoningBudget returns zot's approximate token budget for reasoning-capable
187// providers that accept explicit budgets.
188func ReasoningBudget(level string) int {
189	switch NormalizeReasoning(level) {
190	case "minimum":
191		return 1024
192	case "low":
193		return 2048
194	case "medium":
195		return 8192
196	case "high":
197		return 16384
198	case "xhigh", "max":
199		return 32768
200	default:
201		return 0
202	}
203}
204
205// AnthropicAdaptiveEffort maps zot's user-facing reasoning levels onto the
206// effort enum used by adaptive-thinking models. These models reject explicit
207// thinking budgets; reasoning depth is controlled by output_config.effort.
208func AnthropicAdaptiveEffort(level string) string {
209	switch NormalizeReasoning(level) {
210	case "minimum", "low":
211		return "low"
212	case "medium":
213		return "medium"
214	case "high":
215		return "high"
216	case "xhigh":
217		return "xhigh"
218	case "max":
219		return "max"
220	default:
221		return ""
222	}
223}
224
225// OpenAIReasoningEffort maps zot's thinking setting onto the effort enum
226// accepted by generic OpenAI-compatible chat-completions endpoints.
227func OpenAIReasoningEffort(level string) string {
228	switch NormalizeReasoning(level) {
229	case "minimum", "low":
230		// Many compatible endpoints only accept low/medium/high.
231		return "low"
232	case "medium":
233		return "medium"
234	case "high", "xhigh", "max":
235		return "high"
236	default:
237		return ""
238	}
239}
240
241// OpenAICompatAnthropicEffort maps zot's thinking setting when an adaptive
242// Anthropic model is served over an OpenAI-compatible chat-completions wire.
243// Adaptive models accept native xhigh and max effort values.
244func OpenAICompatAnthropicEffort(level string) string {
245	switch NormalizeReasoning(level) {
246	case "minimum", "low":
247		return "low"
248	case "medium":
249		return "medium"
250	case "high":
251		return "high"
252	case "xhigh":
253		return "xhigh"
254	case "max":
255		return "max"
256	default:
257		return ""
258	}
259}
260
261// OpenAICodexReasoningEffort maps zot levels onto the Responses API effort
262// enum. GPT-5.6 and GPT-6 support native max; other models clamp max to xhigh.
263func OpenAICodexReasoningEffort(level, model string) string {
264	switch NormalizeReasoning(level) {
265	case "minimum", "low":
266		return "low"
267	case "medium":
268		return "medium"
269	case "high":
270		return "high"
271	case "xhigh":
272		return "xhigh"
273	case "max":
274		if supportsResponsesMaxEffort(model) {
275			return "max"
276		}
277		return "xhigh"
278	default:
279		return ""
280	}
281}
282
283func supportsResponsesMaxEffort(model string) bool {
284	id := strings.ToLower(model)
285	return strings.HasPrefix(id, "gpt-5.6-") || id == "gpt-6-astra" || id == "gpt-6-sol" || id == "gpt-6-luna"
286}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 476

↑↓ moveenter openesc close