@bubblebrain-ai/bubble 0.0.35 → 0.0.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -7
- package/dist/agent/categories.d.ts +66 -2
- package/dist/agent/categories.js +120 -19
- package/dist/agent/discovery-barrier.js +1 -1
- package/dist/agent/profiles.d.ts +1 -0
- package/dist/agent/profiles.js +25 -11
- package/dist/agent/routing-catalog.d.ts +113 -0
- package/dist/agent/routing-catalog.js +240 -0
- package/dist/agent/workflow/control.d.ts +14 -0
- package/dist/agent/workflow/control.js +30 -9
- package/dist/agent.d.ts +75 -5
- package/dist/agent.js +313 -61
- package/dist/approval/controller.js +28 -0
- package/dist/approval/tool-helper.js +2 -0
- package/dist/approval/types.d.ts +21 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +2 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js +4 -0
- package/dist/context/compact-llm.d.ts +2 -2
- package/dist/external-runtime/grok-acp.d.ts +56 -0
- package/dist/external-runtime/grok-acp.js +386 -0
- package/dist/external-runtime/grok-binary.d.ts +25 -0
- package/dist/external-runtime/grok-binary.js +108 -0
- package/dist/external-runtime/grok-errors.d.ts +7 -0
- package/dist/external-runtime/grok-errors.js +15 -0
- package/dist/external-runtime/grok-input-policy.d.ts +58 -0
- package/dist/external-runtime/grok-input-policy.js +52 -0
- package/dist/external-runtime/grok-ndjson.d.ts +14 -0
- package/dist/external-runtime/grok-ndjson.js +193 -0
- package/dist/external-runtime/grok-network.d.ts +15 -0
- package/dist/external-runtime/grok-network.js +81 -0
- package/dist/external-runtime/grok-preflight.d.ts +28 -0
- package/dist/external-runtime/grok-preflight.js +194 -0
- package/dist/external-runtime/grok-process.d.ts +26 -0
- package/dist/external-runtime/grok-process.js +184 -0
- package/dist/external-runtime/grok-profile.d.ts +32 -0
- package/dist/external-runtime/grok-profile.js +419 -0
- package/dist/external-runtime/grok-provider.d.ts +31 -0
- package/dist/external-runtime/grok-provider.js +41 -0
- package/dist/external-runtime/grok-runtime.d.ts +91 -0
- package/dist/external-runtime/grok-runtime.js +882 -0
- package/dist/external-runtime/index.d.ts +13 -0
- package/dist/external-runtime/index.js +13 -0
- package/dist/external-runtime/session-policy.d.ts +11 -0
- package/dist/external-runtime/session-policy.js +30 -0
- package/dist/external-runtime/types.d.ts +65 -0
- package/dist/external-runtime/types.js +1 -0
- package/dist/feishu/agent-host/approval-card.js +21 -0
- package/dist/feishu/agent-host/run-driver.js +19 -3
- package/dist/feishu/serve.js +1 -0
- package/dist/goal/tools.d.ts +6 -3
- package/dist/goal/tools.js +7 -19
- package/dist/main.js +105 -27
- package/dist/memory/db.d.ts +2 -0
- package/dist/memory/db.js +10 -0
- package/dist/memory/index.d.ts +1 -0
- package/dist/memory/index.js +1 -0
- package/dist/memory/phase1.js +14 -2
- package/dist/memory/phase2.js +21 -2
- package/dist/memory/prompts.js +1 -1
- package/dist/memory/session-policy.d.ts +25 -0
- package/dist/memory/session-policy.js +55 -0
- package/dist/memory/storage.d.ts +1 -0
- package/dist/memory/storage.js +13 -0
- package/dist/model-catalog.d.ts +18 -0
- package/dist/model-catalog.js +87 -41
- package/dist/model-config.d.ts +3 -1
- package/dist/model-config.js +11 -0
- package/dist/oauth/grok.d.ts +25 -0
- package/dist/oauth/grok.js +218 -0
- package/dist/oauth/index.d.ts +1 -0
- package/dist/oauth/index.js +1 -0
- package/dist/oauth/storage.d.ts +9 -0
- package/dist/oauth/storage.js +25 -0
- package/dist/orchestrator/default-hooks.js +17 -7
- package/dist/prompt/compose.d.ts +3 -0
- package/dist/prompt/compose.js +3 -0
- package/dist/prompt/delegation.js +22 -3
- package/dist/prompt/environment.js +0 -4
- package/dist/prompt/reminders.d.ts +0 -1
- package/dist/prompt/reminders.js +1 -11
- package/dist/prompt/routing.d.ts +14 -0
- package/dist/prompt/routing.js +71 -0
- package/dist/prompt/runtime.js +1 -1
- package/dist/prompt/task-reminders.d.ts +19 -0
- package/dist/prompt/task-reminders.js +49 -0
- package/dist/provider-ai-sdk.js +2 -2
- package/dist/provider-grok.d.ts +26 -0
- package/dist/provider-grok.js +59 -0
- package/dist/provider-openai-codex.d.ts +17 -1
- package/dist/provider-openai-codex.js +101 -29
- package/dist/provider-registry.d.ts +72 -0
- package/dist/provider-registry.js +336 -59
- package/dist/provider-transform.d.ts +1 -1
- package/dist/provider-transform.js +28 -5
- package/dist/provider.d.ts +3 -0
- package/dist/provider.js +14 -0
- package/dist/session-types.d.ts +19 -1
- package/dist/session.d.ts +1 -0
- package/dist/session.js +4 -0
- package/dist/slash-commands/commands.js +224 -14
- package/dist/slash-commands/types.d.ts +14 -1
- package/dist/system-prompt.d.ts +2 -0
- package/dist/tools/agent-lifecycle.js +9 -11
- package/dist/tools/bash.js +1 -1
- package/dist/tools/child-tools.js +2 -0
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js +7 -5
- package/dist/tools/lsp.js +1 -0
- package/dist/tools/memory.d.ts +7 -2
- package/dist/tools/memory.js +28 -43
- package/dist/tools/server.js +4 -0
- package/dist/tools/tool-search.d.ts +1 -1
- package/dist/tools/tool-search.js +23 -3
- package/dist/tui/model-switch.d.ts +7 -0
- package/dist/tui/model-switch.js +15 -5
- package/dist/tui-ink/app.d.ts +3 -1
- package/dist/tui-ink/app.js +648 -232
- package/dist/tui-ink/approval/approval-dialog.js +36 -0
- package/dist/tui-ink/composer-buffer.d.ts +55 -0
- package/dist/tui-ink/composer-buffer.js +317 -0
- package/dist/tui-ink/footer.d.ts +1 -0
- package/dist/tui-ink/footer.js +7 -2
- package/dist/tui-ink/input-box.d.ts +35 -1
- package/dist/tui-ink/input-box.js +215 -122
- package/dist/tui-ink/model-picker.d.ts +61 -4
- package/dist/tui-ink/model-picker.js +453 -123
- package/dist/tui-ink/paste-operation-tracker.d.ts +13 -0
- package/dist/tui-ink/paste-operation-tracker.js +25 -0
- package/dist/tui-ink/run.d.ts +3 -0
- package/dist/tui-ink/run.js +1 -1
- package/dist/tui-ink/subagent-view.d.ts +31 -0
- package/dist/tui-ink/subagent-view.js +82 -1
- package/dist/types.d.ts +5 -5
- package/dist/types.js +1 -1
- package/dist/variant/thinking-level.d.ts +2 -2
- package/dist/variant/thinking-level.js +2 -1
- package/dist/variant/variant-resolver.d.ts +9 -0
- package/dist/variant/variant-resolver.js +17 -0
- package/package.json +2 -1
- package/dist/tools/task.d.ts +0 -2
- package/dist/tools/task.js +0 -58
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect, useMemo } from "react";
|
|
2
|
+
import { useState, useEffect, useMemo, useRef } from "react";
|
|
3
3
|
import { Box, Text, useInput, usePaste, useStdout } from "ink";
|
|
4
4
|
import { isKeyReleaseEvent } from "./key-events.js";
|
|
5
5
|
import { useTheme } from "./theme.js";
|
|
6
|
-
import { encodeModel, decodeModel, displayModel, isUserVisibleProvider } from "../provider-registry.js";
|
|
6
|
+
import { encodeModel, decodeModel, displayModel, isUserVisibleProvider, } from "../provider-registry.js";
|
|
7
7
|
import { listBuiltinModels } from "../model-catalog.js";
|
|
8
8
|
import { padVisual, truncateVisual } from "../text-display.js";
|
|
9
9
|
import { hasTerminalMouseSequence } from "./terminal-mouse.js";
|
|
@@ -53,7 +53,8 @@ export function formatSkillPickerRow(skill, options) {
|
|
|
53
53
|
return padVisual(truncateVisual(row, width), width);
|
|
54
54
|
}
|
|
55
55
|
export const MODEL_PICKER_MAX_BODY_ROWS = 10;
|
|
56
|
-
export const MODEL_PICKER_CHROME_ROWS =
|
|
56
|
+
export const MODEL_PICKER_CHROME_ROWS = 14;
|
|
57
|
+
export const MODEL_PICKER_RECENT_FAMILY_LIMIT = 3;
|
|
57
58
|
export function modelPickerBodyRows(termHeight) {
|
|
58
59
|
const rows = Number.isFinite(termHeight) ? Math.floor(termHeight) : 24;
|
|
59
60
|
return Math.max(1, Math.min(MODEL_PICKER_MAX_BODY_ROWS, rows - MODEL_PICKER_CHROME_ROWS));
|
|
@@ -78,24 +79,98 @@ export function padPickerRows(rows, bodyRows, width) {
|
|
|
78
79
|
}
|
|
79
80
|
return padded;
|
|
80
81
|
}
|
|
82
|
+
export function modelPickerRecentKey(recent) {
|
|
83
|
+
return recent.join("\u0000");
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Returns a provider-scoped family key for model ordering.
|
|
87
|
+
*
|
|
88
|
+
* Keep this deliberately conservative: suffixes such as `mini`, `codex`,
|
|
89
|
+
* `spark`, or `highspeed` often describe distinct products rather than
|
|
90
|
+
* interchangeable siblings. The Codex Sol/Terra/Luna trio is the one family
|
|
91
|
+
* whose relationship is explicitly declared by the provider catalog.
|
|
92
|
+
*/
|
|
93
|
+
export function modelPickerFamilyKey(provider, modelId) {
|
|
94
|
+
const isCodexOAuth = provider.id === "openai-codex"
|
|
95
|
+
|| (provider.id === "openai" && provider.authType === "oauth");
|
|
96
|
+
if (isCodexOAuth) {
|
|
97
|
+
const match = /^(gpt-\d+(?:\.\d+)+)-(sol|terra|luna)$/i.exec(modelId);
|
|
98
|
+
if (match)
|
|
99
|
+
return `${provider.id}:${match[1].toLowerCase()}:sol-terra-luna`;
|
|
100
|
+
}
|
|
101
|
+
return `${provider.id}:${modelId}`;
|
|
102
|
+
}
|
|
103
|
+
export function buildModelPickerDisplayItems(options) {
|
|
104
|
+
const items = [];
|
|
105
|
+
let previousGroup;
|
|
106
|
+
let headerIndex = 0;
|
|
107
|
+
for (const option of options) {
|
|
108
|
+
if (option.group !== previousGroup) {
|
|
109
|
+
previousGroup = option.group;
|
|
110
|
+
items.push({
|
|
111
|
+
kind: "header",
|
|
112
|
+
key: `header:${headerIndex++}:${option.group}`,
|
|
113
|
+
label: option.group,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
items.push({ kind: "model", key: option.id, option });
|
|
117
|
+
}
|
|
118
|
+
return items;
|
|
119
|
+
}
|
|
120
|
+
export function resolveModelPickerDisplayIndex(items, selectedId) {
|
|
121
|
+
if (selectedId) {
|
|
122
|
+
const selectedIndex = items.findIndex((item) => item.kind === "model" && item.option.id === selectedId);
|
|
123
|
+
if (selectedIndex >= 0)
|
|
124
|
+
return selectedIndex;
|
|
125
|
+
}
|
|
126
|
+
const firstModelIndex = items.findIndex((item) => item.kind === "model");
|
|
127
|
+
return firstModelIndex >= 0 ? firstModelIndex : 0;
|
|
128
|
+
}
|
|
129
|
+
export function filterAndRankModelPickerOptions(options, query) {
|
|
130
|
+
const normalized = query.trim().toLowerCase();
|
|
131
|
+
if (!normalized)
|
|
132
|
+
return [...options];
|
|
133
|
+
return options
|
|
134
|
+
.map((option, index) => {
|
|
135
|
+
const { modelId } = decodeModel(option.id);
|
|
136
|
+
const fields = [option.label, modelId, option.id, option.providerBadge]
|
|
137
|
+
.map((value) => value.toLowerCase());
|
|
138
|
+
const rank = fields.some((value) => value === normalized)
|
|
139
|
+
? 0
|
|
140
|
+
: fields.some((value) => value.startsWith(normalized))
|
|
141
|
+
? 1
|
|
142
|
+
: fields.some((value) => value.includes(normalized))
|
|
143
|
+
? 2
|
|
144
|
+
: Number.POSITIVE_INFINITY;
|
|
145
|
+
return { option, index, rank };
|
|
146
|
+
})
|
|
147
|
+
.filter((entry) => Number.isFinite(entry.rank))
|
|
148
|
+
.sort((left, right) => left.rank - right.rank || left.index - right.index)
|
|
149
|
+
.map((entry) => entry.option);
|
|
150
|
+
}
|
|
81
151
|
function isThinkingToggleOption(option) {
|
|
82
152
|
const decoded = decodeModel(option.id);
|
|
83
153
|
return isThinkingToggleModel(decoded.providerId || option.providerBadge, decoded.modelId);
|
|
84
154
|
}
|
|
85
155
|
export function formatReasoningLevelsLabel(levels, asToggle = false) {
|
|
156
|
+
if (levels.length === 0)
|
|
157
|
+
return "checking";
|
|
86
158
|
if (isThinkingOnlyLevels(levels))
|
|
87
159
|
return "on";
|
|
88
160
|
if (asToggle)
|
|
89
161
|
return "thinking on/off";
|
|
90
|
-
|
|
91
|
-
return normalized.join("/");
|
|
162
|
+
return levels.join("/");
|
|
92
163
|
}
|
|
93
164
|
export function formatModelPickerRow(option, options) {
|
|
94
165
|
const width = Math.max(24, options.width);
|
|
95
166
|
const marker = options.selected ? "> " : " ";
|
|
96
167
|
const label = option.label.replace(/\s+/g, " ").trim();
|
|
97
168
|
const provider = option.providerBadge.replace(/\s+/g, " ").trim();
|
|
98
|
-
const effort =
|
|
169
|
+
const effort = option.pending
|
|
170
|
+
? "checking"
|
|
171
|
+
: (option.available === false || option.reasoningLevels.length === 0)
|
|
172
|
+
? "unavailable"
|
|
173
|
+
: formatReasoningLevelsLabel(option.reasoningLevels, isThinkingToggleOption(option));
|
|
99
174
|
const current = options.current ? " ●" : "";
|
|
100
175
|
const providerWidth = Math.max(6, Math.min(16, Math.floor(width * 0.18)));
|
|
101
176
|
const effortWidth = Math.max(12, Math.min(30, Math.floor(width * 0.32)));
|
|
@@ -127,6 +202,14 @@ export function formatNoModelResultsRow(query, width) {
|
|
|
127
202
|
return padVisual(truncateVisual(row, rowWidth), rowWidth);
|
|
128
203
|
}
|
|
129
204
|
export function preferredEffortIndex(option, currentThinkingLevel) {
|
|
205
|
+
const currentIndex = option.reasoningLevels.indexOf(currentThinkingLevel);
|
|
206
|
+
if (currentIndex >= 0)
|
|
207
|
+
return currentIndex;
|
|
208
|
+
if (option.defaultReasoningLevel) {
|
|
209
|
+
const defaultIndex = option.reasoningLevels.indexOf(option.defaultReasoningLevel);
|
|
210
|
+
if (defaultIndex >= 0)
|
|
211
|
+
return defaultIndex;
|
|
212
|
+
}
|
|
130
213
|
const preferred = normalizeThinkingLevel(currentThinkingLevel, option.reasoningLevels);
|
|
131
214
|
const index = option.reasoningLevels.indexOf(preferred);
|
|
132
215
|
return index >= 0 ? index : 0;
|
|
@@ -134,6 +217,46 @@ export function preferredEffortIndex(option, currentThinkingLevel) {
|
|
|
134
217
|
export function shouldOpenEffortPicker(option) {
|
|
135
218
|
return option.reasoningLevels.length > 1;
|
|
136
219
|
}
|
|
220
|
+
export function isModelOptionSelectable(option) {
|
|
221
|
+
return option.available && !option.pending && option.reasoningLevels.length > 0;
|
|
222
|
+
}
|
|
223
|
+
export function resolveSelectedModelId(options, selectedId) {
|
|
224
|
+
if (selectedId) {
|
|
225
|
+
const selected = options.find((option) => option.id === selectedId);
|
|
226
|
+
if (selected && isModelOptionSelectable(selected))
|
|
227
|
+
return selected.id;
|
|
228
|
+
}
|
|
229
|
+
return options.find(isModelOptionSelectable)?.id;
|
|
230
|
+
}
|
|
231
|
+
export function moveModelSelection(options, selectedId, direction) {
|
|
232
|
+
const selectableIndexes = options
|
|
233
|
+
.map((option, index) => isModelOptionSelectable(option) ? index : -1)
|
|
234
|
+
.filter((index) => index >= 0);
|
|
235
|
+
if (selectableIndexes.length === 0)
|
|
236
|
+
return undefined;
|
|
237
|
+
const currentIndex = options.findIndex((option) => option.id === selectedId);
|
|
238
|
+
if (currentIndex < 0) {
|
|
239
|
+
return options[direction > 0 ? selectableIndexes[0] : selectableIndexes[selectableIndexes.length - 1]]?.id;
|
|
240
|
+
}
|
|
241
|
+
const target = direction > 0
|
|
242
|
+
? selectableIndexes.find((index) => index > currentIndex)
|
|
243
|
+
: [...selectableIndexes].reverse().find((index) => index < currentIndex);
|
|
244
|
+
return options[target ?? currentIndex]?.id ?? selectedId;
|
|
245
|
+
}
|
|
246
|
+
export function reconcileModelPickerPhase(phase, options, currentThinkingLevel) {
|
|
247
|
+
if (phase.kind !== "effort")
|
|
248
|
+
return phase;
|
|
249
|
+
const latest = options.find((option) => option.id === phase.model.id);
|
|
250
|
+
if (!latest || !isModelOptionSelectable(latest))
|
|
251
|
+
return { kind: "model" };
|
|
252
|
+
const selectedLevel = phase.model.reasoningLevels[phase.selectedIndex];
|
|
253
|
+
const selectedIndex = selectedLevel && latest.reasoningLevels.includes(selectedLevel)
|
|
254
|
+
? latest.reasoningLevels.indexOf(selectedLevel)
|
|
255
|
+
: preferredEffortIndex(latest, currentThinkingLevel);
|
|
256
|
+
if (selectedIndex === phase.selectedIndex && sameModelOptionMetadata(phase.model, latest))
|
|
257
|
+
return phase;
|
|
258
|
+
return { kind: "effort", model: latest, selectedIndex };
|
|
259
|
+
}
|
|
137
260
|
function effortDescription(level, asToggle) {
|
|
138
261
|
if (asToggle)
|
|
139
262
|
return level === "off" ? "thinking disabled" : "thinking enabled";
|
|
@@ -152,6 +275,8 @@ function effortDescription(level, asToggle) {
|
|
|
152
275
|
return "extra high reasoning";
|
|
153
276
|
case "max":
|
|
154
277
|
return "maximum provider effort";
|
|
278
|
+
case "ultra":
|
|
279
|
+
return "maximum effort with delegation";
|
|
155
280
|
default:
|
|
156
281
|
return "reasoning effort";
|
|
157
282
|
}
|
|
@@ -169,81 +294,97 @@ export function ModelPicker({ registry, current, currentThinkingLevel, recent, o
|
|
|
169
294
|
const terminalColumns = stdout?.columns || 80;
|
|
170
295
|
const bodyRows = modelPickerBodyRows(termHeight);
|
|
171
296
|
const rowWidth = Math.max(36, Math.min(110, terminalColumns - 6));
|
|
172
|
-
const
|
|
297
|
+
const recentKey = modelPickerRecentKey(recent);
|
|
298
|
+
const enabledSnapshot = registry.getEnabled().filter((provider) => isUserVisibleProvider(provider.id));
|
|
299
|
+
const providerKey = enabledSnapshot
|
|
300
|
+
.map((provider) => [
|
|
301
|
+
provider.id,
|
|
302
|
+
provider.name,
|
|
303
|
+
provider.baseURL,
|
|
304
|
+
provider.authType ?? "",
|
|
305
|
+
provider.protocol ?? "",
|
|
306
|
+
].join("\u0000"))
|
|
307
|
+
.join("\u0001");
|
|
308
|
+
const enabledProviders = useMemo(() => registry.getEnabled().filter((provider) => isUserVisibleProvider(provider.id)), [registry, providerKey]);
|
|
309
|
+
const localModelsByProvider = useMemo(() => new Map(enabledProviders.map((provider) => [
|
|
310
|
+
provider.id,
|
|
311
|
+
localModelsForProvider(registry, provider),
|
|
312
|
+
])), [registry, providerKey]);
|
|
313
|
+
const [discoveries, setDiscoveries] = useState(() => new Map());
|
|
314
|
+
const [refreshGeneration, setRefreshGeneration] = useState(0);
|
|
315
|
+
const requestGenerationRef = useRef(0);
|
|
316
|
+
const rawOptions = useMemo(() => buildMergedModelOptions({
|
|
317
|
+
providers: enabledProviders,
|
|
318
|
+
localModelsByProvider,
|
|
319
|
+
discoveries,
|
|
320
|
+
current,
|
|
321
|
+
recent,
|
|
322
|
+
}), [enabledProviders, localModelsByProvider, discoveries, current, recentKey]);
|
|
173
323
|
const [query, setQuery] = useState("");
|
|
174
|
-
const [
|
|
324
|
+
const [selectedId, setSelectedId] = useState(() => resolveSelectedModelId(buildLocalModelOptions(registry, current, recent), current));
|
|
175
325
|
const [phase, setPhase] = useState({ kind: "model" });
|
|
176
326
|
useEffect(() => {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const { providerId } = decodeModel(m);
|
|
188
|
-
const provider = enabled.find((p) => p.id === providerId);
|
|
189
|
-
appendModelOption(opts, seen, {
|
|
190
|
-
id: m,
|
|
191
|
-
label: displayModel(m),
|
|
192
|
-
group: "Recent",
|
|
193
|
-
providerBadge: provider?.name || providerId || "",
|
|
327
|
+
const generation = ++requestGenerationRef.current;
|
|
328
|
+
setDiscoveries((previous) => {
|
|
329
|
+
const next = new Map();
|
|
330
|
+
for (const provider of enabledProviders) {
|
|
331
|
+
const prior = previous.get(provider.id);
|
|
332
|
+
next.set(provider.id, {
|
|
333
|
+
status: "pending",
|
|
334
|
+
models: prior?.models ?? [],
|
|
335
|
+
source: prior?.source,
|
|
336
|
+
authoritative: prior?.authoritative ?? false,
|
|
194
337
|
});
|
|
195
338
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
return
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
appendModelOption(opts, seen, {
|
|
208
|
-
id: encodeModel(m.providerId, m.id),
|
|
209
|
-
label: m.name,
|
|
210
|
-
group: provider.name,
|
|
211
|
-
providerBadge: provider.name,
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
if (current && !seen.has(current)) {
|
|
216
|
-
const { providerId } = decodeModel(current);
|
|
217
|
-
const provider = enabled.find((p) => p.id === providerId);
|
|
218
|
-
opts.unshift({
|
|
219
|
-
id: current,
|
|
220
|
-
label: displayModel(current),
|
|
221
|
-
group: "Current",
|
|
222
|
-
providerBadge: provider?.name || providerId || "",
|
|
223
|
-
reasoningLevels: reasoningLevelsForModel(current),
|
|
339
|
+
return next;
|
|
340
|
+
});
|
|
341
|
+
for (const provider of enabledProviders) {
|
|
342
|
+
void discoverModelsForPicker(registry, provider, refreshGeneration > 0)
|
|
343
|
+
.then((result) => {
|
|
344
|
+
if (requestGenerationRef.current !== generation)
|
|
345
|
+
return;
|
|
346
|
+
setDiscoveries((previous) => {
|
|
347
|
+
const next = new Map(previous);
|
|
348
|
+
next.set(provider.id, { status: "complete", ...result });
|
|
349
|
+
return next;
|
|
224
350
|
});
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
:
|
|
351
|
+
})
|
|
352
|
+
.catch((error) => {
|
|
353
|
+
if (requestGenerationRef.current !== generation)
|
|
354
|
+
return;
|
|
355
|
+
setDiscoveries((previous) => {
|
|
356
|
+
const next = new Map(previous);
|
|
357
|
+
next.set(provider.id, {
|
|
358
|
+
status: "complete",
|
|
359
|
+
models: localModelsByProvider.get(provider.id) ?? [],
|
|
360
|
+
source: "fallback",
|
|
361
|
+
authoritative: false,
|
|
362
|
+
error: error instanceof Error ? error.message : String(error),
|
|
363
|
+
});
|
|
364
|
+
return next;
|
|
233
365
|
});
|
|
234
|
-
}
|
|
366
|
+
});
|
|
235
367
|
}
|
|
236
|
-
void refreshRemote();
|
|
237
368
|
return () => {
|
|
238
|
-
|
|
369
|
+
if (requestGenerationRef.current === generation)
|
|
370
|
+
requestGenerationRef.current++;
|
|
239
371
|
};
|
|
240
|
-
}, [registry,
|
|
372
|
+
}, [registry, providerKey, refreshGeneration]);
|
|
241
373
|
const options = useMemo(() => {
|
|
242
|
-
|
|
243
|
-
return rawOptions;
|
|
244
|
-
const q = query.toLowerCase();
|
|
245
|
-
return rawOptions.filter((opt) => opt.label.toLowerCase().includes(q) || opt.providerBadge.toLowerCase().includes(q));
|
|
374
|
+
return filterAndRankModelPickerOptions(rawOptions, query);
|
|
246
375
|
}, [rawOptions, query]);
|
|
376
|
+
const resolvedSelectedId = resolveSelectedModelId(options, selectedId);
|
|
377
|
+
const selectedIndex = resolvedSelectedId
|
|
378
|
+
? options.findIndex((option) => option.id === resolvedSelectedId)
|
|
379
|
+
: -1;
|
|
380
|
+
useEffect(() => {
|
|
381
|
+
if (selectedId !== resolvedSelectedId)
|
|
382
|
+
setSelectedId(resolvedSelectedId);
|
|
383
|
+
}, [selectedId, resolvedSelectedId]);
|
|
384
|
+
useEffect(() => {
|
|
385
|
+
setPhase((currentPhase) => reconcileModelPickerPhase(currentPhase, rawOptions, currentThinkingLevel));
|
|
386
|
+
}, [rawOptions, currentThinkingLevel]);
|
|
387
|
+
const discoveryStatus = formatModelDiscoveryStatus(enabledProviders, discoveries);
|
|
247
388
|
useInput((input, key) => {
|
|
248
389
|
if (isKeyReleaseEvent(key))
|
|
249
390
|
return;
|
|
@@ -255,7 +396,9 @@ export function ModelPicker({ registry, current, currentThinkingLevel, recent, o
|
|
|
255
396
|
return;
|
|
256
397
|
}
|
|
257
398
|
if (action === "enter") {
|
|
258
|
-
|
|
399
|
+
const level = levels[clampPickerIndex(phase.selectedIndex, levels.length)];
|
|
400
|
+
if (level)
|
|
401
|
+
onSelect(phase.model.id, level);
|
|
259
402
|
return;
|
|
260
403
|
}
|
|
261
404
|
if (action === "up") {
|
|
@@ -272,13 +415,17 @@ export function ModelPicker({ registry, current, currentThinkingLevel, recent, o
|
|
|
272
415
|
}
|
|
273
416
|
return;
|
|
274
417
|
}
|
|
418
|
+
if (key.ctrl && (input.toLowerCase() === "r" || input === "\x12")) {
|
|
419
|
+
setRefreshGeneration((generation) => generation + 1);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
275
422
|
if (action === "escape") {
|
|
276
423
|
onCancel();
|
|
277
424
|
return;
|
|
278
425
|
}
|
|
279
426
|
if (action === "enter") {
|
|
280
|
-
const opt = options[
|
|
281
|
-
if (opt) {
|
|
427
|
+
const opt = selectedIndex >= 0 ? options[selectedIndex] : undefined;
|
|
428
|
+
if (opt && isModelOptionSelectable(opt)) {
|
|
282
429
|
if (shouldOpenEffortPicker(opt)) {
|
|
283
430
|
setPhase({
|
|
284
431
|
kind: "effort",
|
|
@@ -287,23 +434,25 @@ export function ModelPicker({ registry, current, currentThinkingLevel, recent, o
|
|
|
287
434
|
});
|
|
288
435
|
}
|
|
289
436
|
else {
|
|
290
|
-
|
|
437
|
+
const level = opt.reasoningLevels[0];
|
|
438
|
+
if (level)
|
|
439
|
+
onSelect(opt.id, level);
|
|
291
440
|
}
|
|
292
441
|
}
|
|
293
442
|
return;
|
|
294
443
|
}
|
|
295
444
|
if (action === "up") {
|
|
296
|
-
|
|
445
|
+
setSelectedId((id) => moveModelSelection(options, id, -1));
|
|
297
446
|
return;
|
|
298
447
|
}
|
|
299
448
|
if (action === "down") {
|
|
300
|
-
|
|
449
|
+
setSelectedId((id) => moveModelSelection(options, id, 1));
|
|
301
450
|
return;
|
|
302
451
|
}
|
|
303
452
|
if (action === "backspace" || action === "delete") {
|
|
304
453
|
setQuery((q) => {
|
|
305
454
|
const next = q.slice(0, -1);
|
|
306
|
-
|
|
455
|
+
setSelectedId(undefined);
|
|
307
456
|
return next;
|
|
308
457
|
});
|
|
309
458
|
return;
|
|
@@ -311,47 +460,60 @@ export function ModelPicker({ registry, current, currentThinkingLevel, recent, o
|
|
|
311
460
|
if (isPrintablePickerInput(input) && !key.ctrl && !key.meta) {
|
|
312
461
|
setQuery((q) => {
|
|
313
462
|
const next = q + input;
|
|
314
|
-
|
|
463
|
+
setSelectedId(undefined);
|
|
315
464
|
return next;
|
|
316
465
|
});
|
|
317
466
|
return;
|
|
318
467
|
}
|
|
319
468
|
});
|
|
320
|
-
const
|
|
321
|
-
const
|
|
322
|
-
const
|
|
469
|
+
const displayItems = buildModelPickerDisplayItems(options);
|
|
470
|
+
const safeSelectedDisplayIndex = resolveModelPickerDisplayIndex(displayItems, resolvedSelectedId);
|
|
471
|
+
const start = pickerWindowStart(safeSelectedDisplayIndex, displayItems.length, bodyRows);
|
|
472
|
+
const visible = displayItems.slice(start, start + bodyRows);
|
|
323
473
|
const rawModelRows = options.length === 0
|
|
324
474
|
? [{
|
|
325
475
|
key: "no-results",
|
|
326
476
|
row: formatNoModelResultsRow(query, rowWidth),
|
|
327
477
|
selected: false,
|
|
478
|
+
header: false,
|
|
328
479
|
}]
|
|
329
|
-
: visible.map((
|
|
330
|
-
|
|
331
|
-
|
|
480
|
+
: visible.map((item) => {
|
|
481
|
+
if (item.kind === "header") {
|
|
482
|
+
return {
|
|
483
|
+
key: item.key,
|
|
484
|
+
row: padVisual(truncateVisual(` ${item.label}`, rowWidth), rowWidth),
|
|
485
|
+
selected: false,
|
|
486
|
+
header: true,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
const isSelected = item.option.id === resolvedSelectedId;
|
|
332
490
|
return {
|
|
333
|
-
key:
|
|
334
|
-
row: formatModelPickerRow(
|
|
491
|
+
key: item.key,
|
|
492
|
+
row: formatModelPickerRow(item.option, {
|
|
335
493
|
selected: isSelected,
|
|
336
|
-
current:
|
|
494
|
+
current: item.option.id === current,
|
|
337
495
|
width: rowWidth,
|
|
338
496
|
}),
|
|
339
497
|
selected: isSelected,
|
|
498
|
+
header: false,
|
|
340
499
|
};
|
|
341
500
|
});
|
|
342
501
|
const modelRows = padPickerRows(rawModelRows.map((row) => row.row), bodyRows, rowWidth).map((row, index) => ({
|
|
343
502
|
key: rawModelRows[index]?.key ?? `blank-${index}`,
|
|
344
503
|
row,
|
|
345
504
|
selected: rawModelRows[index]?.selected ?? false,
|
|
505
|
+
header: rawModelRows[index]?.header ?? false,
|
|
346
506
|
}));
|
|
347
|
-
const highlightedOption = options[
|
|
348
|
-
const enterAction = highlightedOption
|
|
349
|
-
? (
|
|
350
|
-
|
|
507
|
+
const highlightedOption = selectedIndex >= 0 ? options[selectedIndex] : undefined;
|
|
508
|
+
const enterAction = highlightedOption
|
|
509
|
+
? (shouldOpenEffortPicker(highlightedOption)
|
|
510
|
+
? (isThinkingToggleOption(highlightedOption) ? "choose mode" : "choose effort")
|
|
511
|
+
: "select")
|
|
512
|
+
: "wait";
|
|
351
513
|
const effortTitle = phase.kind === "effort" && isThinkingToggleOption(phase.model)
|
|
352
514
|
? "Select Thinking Mode"
|
|
353
515
|
: "Select Reasoning Effort";
|
|
354
|
-
return (_jsxs(Box, { flexDirection: "column", marginY: 1, paddingX: 1, borderStyle: "round", borderColor: theme.borderActive, children: [_jsx(Text, { bold: true, color: theme.accent, children: phase.kind === "effort" ? effortTitle : "Select Model" }), phase.kind === "effort" ? (_jsx(EffortPickerView, { model: phase.model, selectedIndex: phase.selectedIndex, bodyRows: bodyRows, rowWidth: rowWidth })) : (_jsxs(_Fragment, { children: [_jsx(SearchField, { query: query, placeholder: "Type to search models...", width: rowWidth }), _jsxs(Text, { color: theme.muted, children: ["\u2191/\u2193 navigate \u00B7 Enter ", enterAction, " \u00B7 Esc cancel \u00B7
|
|
516
|
+
return (_jsxs(Box, { flexDirection: "column", marginY: 1, paddingX: 1, borderStyle: "round", borderColor: theme.borderActive, children: [_jsx(Text, { bold: true, color: theme.accent, children: phase.kind === "effort" ? effortTitle : "Select Model" }), phase.kind === "effort" ? (_jsx(EffortPickerView, { model: phase.model, selectedIndex: phase.selectedIndex, bodyRows: bodyRows, rowWidth: rowWidth })) : (_jsxs(_Fragment, { children: [_jsx(SearchField, { query: query, placeholder: "Type to search models...", width: rowWidth }), _jsxs(Text, { color: theme.muted, children: ["\u2191/\u2193 navigate \u00B7 Enter ", enterAction, " \u00B7 Esc cancel \u00B7 Ctrl+R retry"] }), _jsx(Box, { height: 1, overflow: "hidden", children: _jsx(Text, { color: discoveryStatus.hasError ? theme.warning : theme.muted, children: discoveryStatus.text || " " }) })] })), phase.kind === "model" && _jsx(Box, { flexDirection: "column", height: bodyRows, overflow: "hidden", marginTop: 1, children: modelRows.map(({ key, row, selected, header }) => (_jsx(Box, { height: 1, overflow: "hidden", children: _jsx(Text, { color: selected ? theme.accent : (header || key === "no-results" ? theme.muted : undefined), bold: selected || header, children: row }) }, key))) })] }));
|
|
355
517
|
}
|
|
356
518
|
function EffortPickerView({ model, selectedIndex, bodyRows, rowWidth, }) {
|
|
357
519
|
const theme = useTheme();
|
|
@@ -388,40 +550,124 @@ function SearchField({ query, placeholder, width }) {
|
|
|
388
550
|
return (_jsxs(Box, { height: 1, overflow: "hidden", marginTop: 1, children: [_jsx(Text, { color: theme.accent, children: "❯ " }), _jsx(Text, { children: visibleQuery }), _jsx(Text, { color: theme.accent, inverse: cursorVisible, children: " " }), visiblePlaceholder && _jsx(Text, { color: theme.muted, dimColor: true, children: visiblePlaceholder })] }));
|
|
389
551
|
}
|
|
390
552
|
export function buildLocalModelOptions(registry, current, recent) {
|
|
391
|
-
const
|
|
553
|
+
const providers = registry.getEnabled().filter((provider) => isUserVisibleProvider(provider.id));
|
|
554
|
+
const localModelsByProvider = new Map(providers.map((provider) => [
|
|
555
|
+
provider.id,
|
|
556
|
+
localModelsForProvider(registry, provider),
|
|
557
|
+
]));
|
|
558
|
+
return buildMergedModelOptions({
|
|
559
|
+
providers,
|
|
560
|
+
localModelsByProvider,
|
|
561
|
+
discoveries: new Map(),
|
|
562
|
+
current,
|
|
563
|
+
recent,
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
export function buildMergedModelOptions(input) {
|
|
392
567
|
const opts = [];
|
|
393
568
|
const seen = new Set();
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
569
|
+
const providerById = new Map(input.providers.map((provider) => [provider.id, provider]));
|
|
570
|
+
const effectiveModelsByProvider = new Map(input.providers.map((provider) => {
|
|
571
|
+
const localModels = input.localModelsByProvider.get(provider.id) ?? [];
|
|
572
|
+
const discovery = input.discoveries.get(provider.id);
|
|
573
|
+
return [provider.id, effectiveProviderModels(localModels, discovery)];
|
|
574
|
+
}));
|
|
575
|
+
const promotedFamilies = new Set();
|
|
576
|
+
const promotedRecentProviderIds = [];
|
|
577
|
+
const familyKeyForId = (id) => {
|
|
578
|
+
const { providerId, modelId } = decodeModel(id);
|
|
579
|
+
const provider = providerId ? providerById.get(providerId) : undefined;
|
|
580
|
+
return provider
|
|
581
|
+
? modelPickerFamilyKey(provider, modelId)
|
|
582
|
+
: `unknown:${id}`;
|
|
583
|
+
};
|
|
584
|
+
const appendPinned = (id, group) => {
|
|
585
|
+
appendModelOption(opts, seen, pinnedModelOption({
|
|
586
|
+
id,
|
|
587
|
+
group,
|
|
588
|
+
providers: input.providers,
|
|
589
|
+
localModelsByProvider: input.localModelsByProvider,
|
|
590
|
+
discoveries: input.discoveries,
|
|
591
|
+
}));
|
|
592
|
+
};
|
|
593
|
+
const appendFamily = (seedId, group) => {
|
|
594
|
+
const { providerId, modelId } = decodeModel(seedId);
|
|
595
|
+
const provider = providerId ? providerById.get(providerId) : undefined;
|
|
596
|
+
if (!provider) {
|
|
597
|
+
appendPinned(seedId, group);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
const familyKey = modelPickerFamilyKey(provider, modelId);
|
|
601
|
+
const familyModels = (effectiveModelsByProvider.get(provider.id) ?? [])
|
|
602
|
+
.filter((model) => modelPickerFamilyKey(provider, model.id) === familyKey);
|
|
603
|
+
for (const model of familyModels) {
|
|
604
|
+
appendModelOption(opts, seen, {
|
|
605
|
+
...modelOptionMetadata(encodeModel(provider.id, model.id), model, true),
|
|
606
|
+
group,
|
|
607
|
+
providerBadge: provider.name,
|
|
608
|
+
available: true,
|
|
609
|
+
pending: false,
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
// Keep an exact Current/Recent pin visible even when an authoritative
|
|
613
|
+
// catalog no longer contains it. It remains unavailable and never causes
|
|
614
|
+
// local siblings to be reintroduced.
|
|
615
|
+
if (!familyModels.some((model) => model.id === modelId))
|
|
616
|
+
appendPinned(seedId, group);
|
|
617
|
+
};
|
|
618
|
+
if (input.current) {
|
|
619
|
+
appendPinned(input.current, "Current");
|
|
620
|
+
const currentFamilyKey = familyKeyForId(input.current);
|
|
621
|
+
promotedFamilies.add(currentFamilyKey);
|
|
622
|
+
appendFamily(input.current, "Current");
|
|
403
623
|
}
|
|
404
|
-
|
|
405
|
-
|
|
624
|
+
let recentFamilyCount = 0;
|
|
625
|
+
for (const recentId of [...new Set(input.recent)]) {
|
|
626
|
+
if (!recentId || recentId === input.current)
|
|
627
|
+
continue;
|
|
628
|
+
const familyKey = familyKeyForId(recentId);
|
|
629
|
+
if (promotedFamilies.has(familyKey)) {
|
|
630
|
+
// A previously promoted family may not contain this exact Recent model
|
|
631
|
+
// in an authoritative catalog. Preserve that pin as unavailable; when
|
|
632
|
+
// it is present, exact-ID deduplication makes this a no-op.
|
|
633
|
+
appendPinned(recentId, "Recent");
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
promotedFamilies.add(familyKey);
|
|
637
|
+
recentFamilyCount += 1;
|
|
638
|
+
const { providerId } = decodeModel(recentId);
|
|
639
|
+
if (providerId && providerById.has(providerId) && !promotedRecentProviderIds.includes(providerId)) {
|
|
640
|
+
promotedRecentProviderIds.push(providerId);
|
|
641
|
+
}
|
|
642
|
+
appendFamily(recentId, "Recent");
|
|
643
|
+
if (recentFamilyCount >= MODEL_PICKER_RECENT_FAMILY_LIMIT)
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
const orderedProviderIds = [];
|
|
647
|
+
const addProviderId = (providerId) => {
|
|
648
|
+
if (providerId && providerById.has(providerId) && !orderedProviderIds.includes(providerId)) {
|
|
649
|
+
orderedProviderIds.push(providerId);
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
addProviderId(decodeModel(input.current).providerId);
|
|
653
|
+
for (const providerId of promotedRecentProviderIds)
|
|
654
|
+
addProviderId(providerId);
|
|
655
|
+
for (const provider of input.providers)
|
|
656
|
+
addProviderId(provider.id);
|
|
657
|
+
for (const providerId of orderedProviderIds) {
|
|
658
|
+
const provider = providerById.get(providerId);
|
|
659
|
+
if (!provider)
|
|
660
|
+
continue;
|
|
661
|
+
for (const model of effectiveModelsByProvider.get(provider.id) ?? []) {
|
|
406
662
|
appendModelOption(opts, seen, {
|
|
407
|
-
|
|
408
|
-
label: model.name,
|
|
663
|
+
...modelOptionMetadata(encodeModel(provider.id, model.id), model, true),
|
|
409
664
|
group: provider.name,
|
|
410
665
|
providerBadge: provider.name,
|
|
666
|
+
available: true,
|
|
667
|
+
pending: false,
|
|
411
668
|
});
|
|
412
669
|
}
|
|
413
670
|
}
|
|
414
|
-
if (current && !seen.has(current)) {
|
|
415
|
-
const { providerId } = decodeModel(current);
|
|
416
|
-
const provider = enabled.find((item) => item.id === providerId);
|
|
417
|
-
opts.unshift({
|
|
418
|
-
id: current,
|
|
419
|
-
label: displayModel(current),
|
|
420
|
-
group: "Current",
|
|
421
|
-
providerBadge: provider?.name || providerId || "",
|
|
422
|
-
reasoningLevels: reasoningLevelsForModel(current),
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
671
|
return opts;
|
|
426
672
|
}
|
|
427
673
|
function localModelsForProvider(registry, provider) {
|
|
@@ -435,20 +681,104 @@ function localModelsForProvider(registry, provider) {
|
|
|
435
681
|
id: model.id,
|
|
436
682
|
name: model.name,
|
|
437
683
|
providerId: provider.id,
|
|
684
|
+
reasoningLevels: model.reasoningLevels,
|
|
685
|
+
defaultReasoningLevel: model.defaultReasoningLevel,
|
|
686
|
+
contextWindow: model.contextWindow,
|
|
687
|
+
toolOutputTokenLimit: model.toolOutputTokenLimit,
|
|
438
688
|
}));
|
|
439
689
|
}
|
|
440
690
|
function appendModelOption(options, seen, option) {
|
|
441
691
|
if (seen.has(option.id))
|
|
442
692
|
return;
|
|
443
693
|
seen.add(option.id);
|
|
444
|
-
options.push(
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
});
|
|
694
|
+
options.push(option);
|
|
695
|
+
}
|
|
696
|
+
function pinnedModelOption(input) {
|
|
697
|
+
const { providerId, modelId } = decodeModel(input.id);
|
|
698
|
+
const provider = input.providers.find((item) => item.id === providerId);
|
|
699
|
+
if (!provider) {
|
|
700
|
+
return {
|
|
701
|
+
...modelOptionMetadata(input.id, undefined, false),
|
|
702
|
+
group: input.group,
|
|
703
|
+
providerBadge: providerId || "",
|
|
704
|
+
available: false,
|
|
705
|
+
pending: false,
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
const local = input.localModelsByProvider.get(provider.id) ?? [];
|
|
709
|
+
const discovery = input.discoveries.get(provider.id);
|
|
710
|
+
const discoveredModel = discovery?.models.find((model) => model.id === modelId);
|
|
711
|
+
const localModel = local.find((model) => model.id === modelId);
|
|
712
|
+
const metadata = discoveredModel ?? localModel;
|
|
713
|
+
const hasKnownMetadata = !!metadata;
|
|
714
|
+
let available = hasKnownMetadata;
|
|
715
|
+
let pending = false;
|
|
716
|
+
if (!discovery || discovery.status === "pending") {
|
|
717
|
+
pending = !hasKnownMetadata;
|
|
718
|
+
}
|
|
719
|
+
else if (discovery.authoritative) {
|
|
720
|
+
available = !!discoveredModel;
|
|
721
|
+
}
|
|
722
|
+
return {
|
|
723
|
+
...modelOptionMetadata(input.id, metadata, hasKnownMetadata),
|
|
724
|
+
group: input.group,
|
|
725
|
+
providerBadge: provider.name,
|
|
726
|
+
available,
|
|
727
|
+
pending,
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
function effectiveProviderModels(localModels, discovery) {
|
|
731
|
+
if (!discovery)
|
|
732
|
+
return localModels;
|
|
733
|
+
if (discovery.authoritative)
|
|
734
|
+
return discovery.models;
|
|
735
|
+
return discovery.models.length > 0 ? discovery.models : localModels;
|
|
736
|
+
}
|
|
737
|
+
function modelOptionMetadata(id, rawModel, known) {
|
|
738
|
+
const model = rawModel;
|
|
739
|
+
const reasoningLevels = model?.reasoningLevels
|
|
740
|
+
? [...model.reasoningLevels]
|
|
741
|
+
: (known ? reasoningLevelsForModel(id) : []);
|
|
742
|
+
const defaultReasoningLevel = model?.defaultReasoningLevel
|
|
743
|
+
&& reasoningLevels.includes(model.defaultReasoningLevel)
|
|
744
|
+
? model.defaultReasoningLevel
|
|
745
|
+
: undefined;
|
|
746
|
+
return {
|
|
747
|
+
id,
|
|
748
|
+
label: model?.name || displayModel(id),
|
|
749
|
+
reasoningLevels,
|
|
750
|
+
defaultReasoningLevel,
|
|
751
|
+
contextWindow: model?.contextWindow,
|
|
752
|
+
toolOutputTokenLimit: model?.toolOutputTokenLimit,
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
async function discoverModelsForPicker(registry, provider, forceRefresh) {
|
|
756
|
+
return registry.discoverModels(provider, { forceRefresh });
|
|
757
|
+
}
|
|
758
|
+
export function formatModelDiscoveryStatus(providers, discoveries) {
|
|
759
|
+
if (providers.some((provider) => discoveries.get(provider.id)?.status !== "complete")) {
|
|
760
|
+
return { text: "Refreshing model catalog…", hasError: false };
|
|
761
|
+
}
|
|
762
|
+
const failed = providers.filter((provider) => discoveries.get(provider.id)?.error);
|
|
763
|
+
if (failed.length > 0) {
|
|
764
|
+
const names = failed.slice(0, 2).map((provider) => provider.name).join(", ");
|
|
765
|
+
const suffix = failed.length > 2 ? ` +${failed.length - 2}` : "";
|
|
766
|
+
return { text: `Showing fallback models for ${names}${suffix} · Ctrl+R retry`, hasError: true };
|
|
767
|
+
}
|
|
768
|
+
return { text: "", hasError: false };
|
|
448
769
|
}
|
|
449
|
-
function
|
|
450
|
-
|
|
451
|
-
|
|
770
|
+
function sameModelOptionMetadata(left, right) {
|
|
771
|
+
return left.id === right.id
|
|
772
|
+
&& left.label === right.label
|
|
773
|
+
&& left.group === right.group
|
|
774
|
+
&& left.providerBadge === right.providerBadge
|
|
775
|
+
&& left.defaultReasoningLevel === right.defaultReasoningLevel
|
|
776
|
+
&& left.contextWindow === right.contextWindow
|
|
777
|
+
&& left.toolOutputTokenLimit === right.toolOutputTokenLimit
|
|
778
|
+
&& left.available === right.available
|
|
779
|
+
&& left.pending === right.pending
|
|
780
|
+
&& left.reasoningLevels.length === right.reasoningLevels.length
|
|
781
|
+
&& left.reasoningLevels.every((level, index) => level === right.reasoningLevels[index]);
|
|
452
782
|
}
|
|
453
783
|
function reasoningLevelsForModel(model) {
|
|
454
784
|
const { providerId, modelId } = decodeModel(model);
|