@narumitw/pi-btw 0.59.0 → 0.60.0
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 +6 -1
- package/dist/index.ts +209 -23
- package/dist/index.ts.map +4 -4
- package/package.json +1 -1
- package/src/btw.ts +1 -12
- package/src/menu.ts +244 -15
- package/src/settings.ts +5 -0
package/package.json
CHANGED
package/src/btw.ts
CHANGED
|
@@ -357,19 +357,8 @@ async function showCommandMenuForBtw(
|
|
|
357
357
|
ctx: ExtensionCommandContext,
|
|
358
358
|
resumeThreads: readonly BtwResumeThreadSummary[],
|
|
359
359
|
): Promise<BtwCommandMenuResult> {
|
|
360
|
-
const currentModel = ctx.model;
|
|
361
|
-
const availableModels = ctx.modelRegistry.getAll();
|
|
362
|
-
const currentThinkingLevel = pi.getThinkingLevel();
|
|
363
|
-
const loaded = await readBtwSettings();
|
|
364
|
-
const settings = loaded.kind === "loaded" ? loaded.settings : {};
|
|
365
|
-
const configured = settings.model ? parseBtwModelReference(settings.model) : undefined;
|
|
366
|
-
const configuredModel = configured
|
|
367
|
-
? availableModels.find((model) => model.provider === configured.provider && model.id === configured.modelId)
|
|
368
|
-
: undefined;
|
|
369
|
-
const model = configuredModel ?? currentModel;
|
|
370
360
|
return showBtwCommandMenu(ctx, {
|
|
371
|
-
currentThinkingLevel,
|
|
372
|
-
availableThinkingLevels: model ? getSupportedThinkingLevels(model) : BTW_THINKING_LEVELS,
|
|
361
|
+
currentThinkingLevel: pi.getThinkingLevel(),
|
|
373
362
|
resumeThreads,
|
|
374
363
|
});
|
|
375
364
|
}
|
package/src/menu.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Api, getSupportedThinkingLevels, type Model } from "@earendil-works/pi-ai";
|
|
2
|
+
import {
|
|
3
|
+
BorderedLoader,
|
|
4
|
+
type ExtensionCommandContext,
|
|
5
|
+
type KeybindingsManager,
|
|
6
|
+
type Theme,
|
|
7
|
+
} from "@earendil-works/pi-coding-agent";
|
|
2
8
|
import type { Component, TUI } from "@earendil-works/pi-tui";
|
|
3
9
|
import type { MenuContext, RunMenuResult } from "@narumitw/pi-tui-kit";
|
|
4
10
|
import {
|
|
@@ -14,6 +20,7 @@ import {
|
|
|
14
20
|
btwSettingsPath,
|
|
15
21
|
effectiveFullscreenCopyOnSelect,
|
|
16
22
|
effectiveRememberThinkingLevelChanges,
|
|
23
|
+
parseBtwModelReference,
|
|
17
24
|
readBtwSettings,
|
|
18
25
|
type UpdateBtwSettingsOptions,
|
|
19
26
|
updateBtwSettings,
|
|
@@ -35,7 +42,11 @@ export interface BtwResumeThreadSummary {
|
|
|
35
42
|
|
|
36
43
|
export interface ShowBtwCommandMenuOptions {
|
|
37
44
|
currentThinkingLevel: BtwThinkingLevel;
|
|
38
|
-
|
|
45
|
+
/** Deterministic test override; production derives levels from each selected model. */
|
|
46
|
+
availableThinkingLevels?: readonly BtwThinkingLevel[];
|
|
47
|
+
availableModels?: readonly Model<Api>[];
|
|
48
|
+
currentModel?: Model<Api>;
|
|
49
|
+
scopedModels?: ExtensionCommandContext["scopedModels"];
|
|
39
50
|
resumeThreads?: readonly BtwResumeThreadSummary[];
|
|
40
51
|
settingsPath?: string;
|
|
41
52
|
readSettings?: typeof readBtwSettings;
|
|
@@ -44,11 +55,13 @@ export interface ShowBtwCommandMenuOptions {
|
|
|
44
55
|
|
|
45
56
|
export type BtwCommandMenuResult = "start" | "tree" | "closed" | { kind: "resume"; threadId: string };
|
|
46
57
|
|
|
47
|
-
type BtwMenuScreen = "main" | "resume" | "settings" | "invalid" | "shortcut" | "shortcut-input";
|
|
58
|
+
type BtwMenuScreen = "main" | "resume" | "settings" | "model" | "invalid" | "shortcut" | "shortcut-input";
|
|
48
59
|
type BtwMenuAction =
|
|
49
60
|
| "start"
|
|
50
61
|
| "start-tree"
|
|
51
62
|
| "resume"
|
|
63
|
+
| "open-model"
|
|
64
|
+
| "set-model"
|
|
52
65
|
| "set-thinking"
|
|
53
66
|
| "set-remember"
|
|
54
67
|
| "set-fullscreen-copy"
|
|
@@ -70,15 +83,32 @@ export async function showBtwCommandMenu(
|
|
|
70
83
|
options: ShowBtwCommandMenuOptions,
|
|
71
84
|
): Promise<BtwCommandMenuResult> {
|
|
72
85
|
if (ctx.mode !== "tui") return "closed";
|
|
73
|
-
const { defineMenu, runMenu } = await import("@narumitw/pi-tui-kit");
|
|
86
|
+
const { defineMenu, runMenu, sanitizeTerminalText } = await import("@narumitw/pi-tui-kit");
|
|
74
87
|
if (ctx.signal?.aborted) return "closed";
|
|
75
88
|
const settingsPath = options.settingsPath ?? btwSettingsPath();
|
|
76
89
|
const readSettings = options.readSettings ?? readBtwSettings;
|
|
77
90
|
const updateSettings = options.updateSettings ?? updateBtwSettings;
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
91
|
+
const getAvailable = ctx.modelRegistry.getAvailable;
|
|
92
|
+
const allAvailableModels = deduplicateModels(
|
|
93
|
+
options.availableModels ??
|
|
94
|
+
(typeof getAvailable === "function" ? getAvailable.call(ctx.modelRegistry) : ctx.modelRegistry.getAll()),
|
|
95
|
+
);
|
|
96
|
+
const currentModel = options.currentModel ?? ctx.model;
|
|
97
|
+
const scopedModels = options.scopedModels ?? ctx.scopedModels ?? [];
|
|
98
|
+
const selectableModels = availableModelsInScope(allAvailableModels, scopedModels);
|
|
99
|
+
const modelItemIds = new Map(selectableModels.map((model, index) => [model, `btw-settings-model:${index}`]));
|
|
100
|
+
const modelsByItemId = new Map(selectableModels.map((model) => [modelItemIds.get(model) as string, model]));
|
|
101
|
+
const safeModelMetadata = (value: string, fallback: string): string => {
|
|
102
|
+
const safe = sanitizeTerminalText(value).trim() || fallback;
|
|
103
|
+
return [...safe].slice(0, 512).join("");
|
|
104
|
+
};
|
|
105
|
+
const displayModelReference = (model: Pick<Model<Api>, "provider" | "id">): string =>
|
|
106
|
+
`${safeModelMetadata(model.id, "unknown model")} [${safeModelMetadata(model.provider, "unknown provider")}]`;
|
|
107
|
+
const rawModelReference = (model: Pick<Model<Api>, "provider" | "id">): string | undefined => {
|
|
108
|
+
const reference = `${model.provider}/${model.id}`;
|
|
109
|
+
const parsed = parseBtwModelReference(reference);
|
|
110
|
+
return parsed?.provider === model.provider && parsed.modelId === model.id ? reference : undefined;
|
|
111
|
+
};
|
|
82
112
|
const displaySettingsPath = sanitizeSingleLine(settingsPath);
|
|
83
113
|
const resumeThreads = options.resumeThreads ?? [];
|
|
84
114
|
let startSelected = false;
|
|
@@ -139,6 +169,53 @@ export async function showBtwCommandMenu(
|
|
|
139
169
|
}
|
|
140
170
|
};
|
|
141
171
|
|
|
172
|
+
const saveModel = async (
|
|
173
|
+
model: Model<Api> | undefined,
|
|
174
|
+
signal: AbortSignal,
|
|
175
|
+
): Promise<{ kind: "saved" } | { kind: "cancelled" } | { kind: "failed"; error: unknown }> => {
|
|
176
|
+
const modelReference = model ? rawModelReference(model) : undefined;
|
|
177
|
+
if (model && !modelReference) return { kind: "cancelled" };
|
|
178
|
+
const result = await ctx.ui.custom<{ kind: "saved" } | { kind: "cancelled" } | { kind: "failed"; error: unknown }>(
|
|
179
|
+
(tui, theme, _keybindings, done) => {
|
|
180
|
+
const loader = new BorderedLoader(tui, theme, "Saving Pi BTW model...");
|
|
181
|
+
const ownerController = new AbortController();
|
|
182
|
+
const saveSignal = AbortSignal.any([signal, loader.signal, ownerController.signal]);
|
|
183
|
+
let settled = false;
|
|
184
|
+
const finish = (value: { kind: "saved" } | { kind: "cancelled" } | { kind: "failed"; error: unknown }) => {
|
|
185
|
+
if (settled) return;
|
|
186
|
+
settled = true;
|
|
187
|
+
done(value);
|
|
188
|
+
};
|
|
189
|
+
const cancel = () => finish({ kind: "cancelled" });
|
|
190
|
+
loader.onAbort = cancel;
|
|
191
|
+
saveSignal.addEventListener("abort", cancel, { once: true });
|
|
192
|
+
queueMicrotask(() => {
|
|
193
|
+
void (async () => {
|
|
194
|
+
// Let the host mount the loader before a synchronous test double or cached write can settle it.
|
|
195
|
+
await Promise.resolve();
|
|
196
|
+
if (saveSignal.aborted) return;
|
|
197
|
+
try {
|
|
198
|
+
await updateSettings({ model: modelReference }, { settingsPath, signal: saveSignal });
|
|
199
|
+
finish({ kind: "saved" });
|
|
200
|
+
} catch (error) {
|
|
201
|
+
finish(saveSignal.aborted ? { kind: "cancelled" } : { kind: "failed", error });
|
|
202
|
+
}
|
|
203
|
+
})();
|
|
204
|
+
});
|
|
205
|
+
return {
|
|
206
|
+
render: (width: number) => loader.render(width),
|
|
207
|
+
invalidate: () => loader.invalidate(),
|
|
208
|
+
handleInput: (data: string) => loader.handleInput(data),
|
|
209
|
+
dispose() {
|
|
210
|
+
ownerController.abort(new DOMException("Pi BTW model save disposed", "AbortError"));
|
|
211
|
+
loader.dispose();
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
},
|
|
215
|
+
);
|
|
216
|
+
return result ?? { kind: "cancelled" };
|
|
217
|
+
};
|
|
218
|
+
|
|
142
219
|
const loadState = async (): Promise<BtwMenuState> => {
|
|
143
220
|
const loaded = await readSettings(settingsPath);
|
|
144
221
|
if (loaded.kind === "invalid") {
|
|
@@ -146,19 +223,104 @@ export async function showBtwCommandMenu(
|
|
|
146
223
|
}
|
|
147
224
|
return { kind: "valid", settings: loaded.kind === "loaded" ? loaded.settings : {} };
|
|
148
225
|
};
|
|
149
|
-
const
|
|
226
|
+
const configuredModel = (settings: BtwSettings): Model<Api> | undefined => {
|
|
227
|
+
if (!settings.model) return undefined;
|
|
228
|
+
const reference = parseBtwModelReference(settings.model);
|
|
229
|
+
return reference
|
|
230
|
+
? allAvailableModels.find((model) => model.provider === reference.provider && model.id === reference.modelId)
|
|
231
|
+
: undefined;
|
|
232
|
+
};
|
|
233
|
+
const selectableConfiguredModel = (settings: BtwSettings): Model<Api> | undefined => {
|
|
234
|
+
const configured = configuredModel(settings);
|
|
235
|
+
return configured ? selectableModels.find((model) => sameModel(model, configured)) : undefined;
|
|
236
|
+
};
|
|
237
|
+
const thinkingLevels = (settings: BtwSettings): BtwThinkingLevel[] => {
|
|
238
|
+
const overridden = options.availableThinkingLevels;
|
|
239
|
+
const effectiveModel = configuredModel(settings) ?? currentModel;
|
|
240
|
+
const available =
|
|
241
|
+
overridden && overridden.length > 0
|
|
242
|
+
? overridden
|
|
243
|
+
: effectiveModel
|
|
244
|
+
? getSupportedThinkingLevels(effectiveModel)
|
|
245
|
+
: BTW_THINKING_LEVELS;
|
|
246
|
+
return available.length > 0 ? [...available] : ["off"];
|
|
247
|
+
};
|
|
248
|
+
const currentMainThinkingLevel = (settings: BtwSettings): BtwThinkingLevel =>
|
|
249
|
+
clampToAvailableThinkingLevel(options.currentThinkingLevel, thinkingLevels(settings));
|
|
150
250
|
const displayThinkingLevel = (settings: BtwSettings): string =>
|
|
151
251
|
settings.thinkingLevel === undefined
|
|
152
252
|
? SAME_AS_MAIN_THREAD
|
|
153
|
-
: clampToAvailableThinkingLevel(settings.thinkingLevel,
|
|
253
|
+
: clampToAvailableThinkingLevel(settings.thinkingLevel, thinkingLevels(settings));
|
|
154
254
|
const displayThinkingSummary = (settings: BtwSettings): string =>
|
|
155
255
|
settings.thinkingLevel === undefined
|
|
156
|
-
? `${SAME_AS_MAIN_THREAD} (currently ${currentMainThinkingLevel})`
|
|
256
|
+
? `${SAME_AS_MAIN_THREAD} (currently ${currentMainThinkingLevel(settings)})`
|
|
157
257
|
: displayThinkingLevel(settings);
|
|
158
258
|
const displayRememberSummary = (settings: BtwSettings): string => {
|
|
159
259
|
const value = effectiveRememberThinkingLevelChanges(settings) ? "On" : "Off";
|
|
160
260
|
return settings.thinkingLevel === undefined ? `${value} (fixed levels only)` : value;
|
|
161
261
|
};
|
|
262
|
+
const displayModelValue = (settings: BtwSettings): string => {
|
|
263
|
+
if (!settings.model) {
|
|
264
|
+
return currentModel ? `${SAME_AS_MAIN_THREAD} (${displayModelReference(currentModel)})` : SAME_AS_MAIN_THREAD;
|
|
265
|
+
}
|
|
266
|
+
const available = configuredModel(settings);
|
|
267
|
+
if (!available) return `${SAME_AS_MAIN_THREAD} · ${safeModelMetadata(settings.model, "unknown model")} unavailable`;
|
|
268
|
+
const reference = displayModelReference(available);
|
|
269
|
+
return selectableConfiguredModel(settings) ? reference : `${reference} · outside current scope`;
|
|
270
|
+
};
|
|
271
|
+
const modelItems = (settings: BtwSettings) => {
|
|
272
|
+
const configured = configuredModel(settings);
|
|
273
|
+
const selectable = selectableConfiguredModel(settings);
|
|
274
|
+
const retained = settings.model && !selectable;
|
|
275
|
+
return [
|
|
276
|
+
{
|
|
277
|
+
id: "same-as-main",
|
|
278
|
+
label: SAME_AS_MAIN_THREAD,
|
|
279
|
+
description: currentModel
|
|
280
|
+
? `Currently ${displayModelReference(currentModel)}`
|
|
281
|
+
: "Use the main thread model when /btw starts.",
|
|
282
|
+
},
|
|
283
|
+
...selectableModels.map((model) => {
|
|
284
|
+
const reference = displayModelReference(model);
|
|
285
|
+
const name = safeModelMetadata(model.name ?? "", "");
|
|
286
|
+
const validReference = rawModelReference(model);
|
|
287
|
+
return {
|
|
288
|
+
id: modelItemIds.get(model) as string,
|
|
289
|
+
label: reference,
|
|
290
|
+
...(name ? { details: [`Model Name: ${name}`] } : {}),
|
|
291
|
+
searchText: [reference, name].filter(Boolean).join(" "),
|
|
292
|
+
...(!validReference
|
|
293
|
+
? {
|
|
294
|
+
disabled: true,
|
|
295
|
+
disabledReason: "This model identity cannot be stored in pi-btw.json.",
|
|
296
|
+
}
|
|
297
|
+
: {}),
|
|
298
|
+
};
|
|
299
|
+
}),
|
|
300
|
+
...(retained
|
|
301
|
+
? [
|
|
302
|
+
{
|
|
303
|
+
id: "configured-model",
|
|
304
|
+
label: configured
|
|
305
|
+
? displayModelReference(configured)
|
|
306
|
+
: safeModelMetadata(settings.model as string, "unknown model"),
|
|
307
|
+
...(configured
|
|
308
|
+
? { description: "Configured outside the current model scope; retained until changed." }
|
|
309
|
+
: {
|
|
310
|
+
disabled: true,
|
|
311
|
+
disabledReason: "Configured model is unavailable; /btw falls back to the main model.",
|
|
312
|
+
}),
|
|
313
|
+
},
|
|
314
|
+
]
|
|
315
|
+
: []),
|
|
316
|
+
];
|
|
317
|
+
};
|
|
318
|
+
const selectedModelItemId = (settings: BtwSettings): string => {
|
|
319
|
+
const selected = selectableConfiguredModel(settings);
|
|
320
|
+
return selected ? (modelItemIds.get(selected) as string) : settings.model ? "configured-model" : "same-as-main";
|
|
321
|
+
};
|
|
322
|
+
const currentModelItemId = (settings: BtwSettings): string =>
|
|
323
|
+
settings.model && configuredModel(settings) ? selectedModelItemId(settings) : "same-as-main";
|
|
162
324
|
|
|
163
325
|
const menu = defineMenu<BtwMenuState, BtwMenuScreen, BtwMenuAction, MenuContext>({
|
|
164
326
|
start: "main",
|
|
@@ -167,6 +329,7 @@ export async function showBtwCommandMenu(
|
|
|
167
329
|
kind: "actions",
|
|
168
330
|
title: "Pi BTW",
|
|
169
331
|
lines: [
|
|
332
|
+
`Model: ${displayModelValue(state.settings)}`,
|
|
170
333
|
`Thinking: ${displayThinkingSummary(state.settings)} · Remember changes: ${displayRememberSummary(state.settings)}`,
|
|
171
334
|
`Copy on select: ${effectiveFullscreenCopyOnSelect(state.settings) ? "On" : "Off"}`,
|
|
172
335
|
],
|
|
@@ -196,7 +359,7 @@ export async function showBtwCommandMenu(
|
|
|
196
359
|
{
|
|
197
360
|
id: "settings",
|
|
198
361
|
label: "Settings",
|
|
199
|
-
description: "Choose thinking, keybindings, and selection copying",
|
|
362
|
+
description: "Choose model, thinking, keybindings, and selection copying",
|
|
200
363
|
to: state.kind === "invalid" ? "invalid" : "settings",
|
|
201
364
|
},
|
|
202
365
|
],
|
|
@@ -220,12 +383,19 @@ export async function showBtwCommandMenu(
|
|
|
220
383
|
title: "Pi BTW Settings",
|
|
221
384
|
lines: [`User settings · ${displaySettingsPath}`],
|
|
222
385
|
items: [
|
|
386
|
+
{
|
|
387
|
+
id: "model",
|
|
388
|
+
label: "Model",
|
|
389
|
+
description: "Choose the model for future pi-btw side threads without changing the main session.",
|
|
390
|
+
currentValue: displayModelValue(state.settings),
|
|
391
|
+
action: "open-model",
|
|
392
|
+
},
|
|
223
393
|
{
|
|
224
394
|
id: "thinkingLevel",
|
|
225
395
|
label: "Thinking level",
|
|
226
|
-
description: `Set the starting level for future pi-btw side threads. Currently ${currentMainThinkingLevel}.`,
|
|
396
|
+
description: `Set the starting level for future pi-btw side threads. Currently ${currentMainThinkingLevel(state.settings)}.`,
|
|
227
397
|
currentValue: displayThinkingLevel(state.settings),
|
|
228
|
-
values: [SAME_AS_MAIN_THREAD, ...
|
|
398
|
+
values: [SAME_AS_MAIN_THREAD, ...thinkingLevels(state.settings)],
|
|
229
399
|
action: "set-thinking",
|
|
230
400
|
},
|
|
231
401
|
{
|
|
@@ -253,6 +423,23 @@ export async function showBtwCommandMenu(
|
|
|
253
423
|
})),
|
|
254
424
|
],
|
|
255
425
|
}),
|
|
426
|
+
model: ({ state }) => ({
|
|
427
|
+
kind: "choice",
|
|
428
|
+
title: "Pi BTW Model",
|
|
429
|
+
lines: [
|
|
430
|
+
"Same as main thread is the default and fallback when a configured model is unavailable.",
|
|
431
|
+
...(state.settings.model && !selectableConfiguredModel(state.settings)
|
|
432
|
+
? [`Configured: ${displayModelValue(state.settings)}`]
|
|
433
|
+
: []),
|
|
434
|
+
],
|
|
435
|
+
items: modelItems(state.settings),
|
|
436
|
+
action: "set-model",
|
|
437
|
+
currentItemId: currentModelItemId(state.settings),
|
|
438
|
+
initialItemId: selectedModelItemId(state.settings),
|
|
439
|
+
enableSearch: true,
|
|
440
|
+
viewportSize: 10,
|
|
441
|
+
hint: "back",
|
|
442
|
+
}),
|
|
256
443
|
shortcut: ({ state }) => ({
|
|
257
444
|
kind: "actions",
|
|
258
445
|
title: shortcutLabels[shortcut],
|
|
@@ -289,6 +476,26 @@ export async function showBtwCommandMenu(
|
|
|
289
476
|
},
|
|
290
477
|
"save-shortcut": ({ state, value, signal }) => saveShortcut(state, value?.trim() ?? "", signal),
|
|
291
478
|
"reset-shortcut": ({ state, signal }) => saveShortcut(state, undefined, signal),
|
|
479
|
+
"open-model": async () => ({ kind: "to", screen: "model" }),
|
|
480
|
+
"set-model": async ({ state, itemId, signal }) => {
|
|
481
|
+
if (itemId === "configured-model" && configuredModel(state.settings)) {
|
|
482
|
+
return { kind: "back" };
|
|
483
|
+
}
|
|
484
|
+
const model = itemId ? modelsByItemId.get(itemId) : undefined;
|
|
485
|
+
if (itemId !== "same-as-main" && (!model || !rawModelReference(model))) return { kind: "rejected" };
|
|
486
|
+
const result = await saveModel(model, signal);
|
|
487
|
+
if (result.kind === "failed") {
|
|
488
|
+
notifySaveFailure(ctx, result.error);
|
|
489
|
+
return { kind: "rejected" };
|
|
490
|
+
}
|
|
491
|
+
if (result.kind === "cancelled" || signal.aborted) return { kind: "close" };
|
|
492
|
+
notifySafely(
|
|
493
|
+
ctx,
|
|
494
|
+
model ? `Pi BTW model: ${displayModelReference(model)}.` : `Pi BTW model: ${SAME_AS_MAIN_THREAD}.`,
|
|
495
|
+
"info",
|
|
496
|
+
);
|
|
497
|
+
return { kind: "back" };
|
|
498
|
+
},
|
|
292
499
|
start: async () => {
|
|
293
500
|
startSelected = true;
|
|
294
501
|
return { kind: "close" };
|
|
@@ -304,8 +511,9 @@ export async function showBtwCommandMenu(
|
|
|
304
511
|
resumedThreadId = itemId;
|
|
305
512
|
return { kind: "close" } as const;
|
|
306
513
|
},
|
|
307
|
-
"set-thinking": async ({ value, signal }) => {
|
|
514
|
+
"set-thinking": async ({ state, value, signal }) => {
|
|
308
515
|
if (!value) return { kind: "rejected" };
|
|
516
|
+
const levels = thinkingLevels(state.settings);
|
|
309
517
|
const patch =
|
|
310
518
|
value === SAME_AS_MAIN_THREAD
|
|
311
519
|
? ({ thinkingLevel: undefined } satisfies BtwSettingsPatch)
|
|
@@ -429,6 +637,27 @@ export async function runBtwMenuPreservingEditor(
|
|
|
429
637
|
return result;
|
|
430
638
|
}
|
|
431
639
|
|
|
640
|
+
function deduplicateModels(models: readonly Model<Api>[]): Model<Api>[] {
|
|
641
|
+
return models.filter((model, index) => models.findIndex((candidate) => sameModel(candidate, model)) === index);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function availableModelsInScope(
|
|
645
|
+
availableModels: readonly Model<Api>[],
|
|
646
|
+
scopedModels: ExtensionCommandContext["scopedModels"],
|
|
647
|
+
): Model<Api>[] {
|
|
648
|
+
if (scopedModels.length === 0) return [...availableModels];
|
|
649
|
+
return deduplicateModels(
|
|
650
|
+
scopedModels.flatMap((entry) => {
|
|
651
|
+
const available = availableModels.find((model) => sameModel(model, entry.model));
|
|
652
|
+
return available ? [available] : [];
|
|
653
|
+
}),
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function sameModel(left: Pick<Model<Api>, "provider" | "id">, right: Pick<Model<Api>, "provider" | "id">): boolean {
|
|
658
|
+
return left.provider === right.provider && left.id === right.id;
|
|
659
|
+
}
|
|
660
|
+
|
|
432
661
|
function clampToAvailableThinkingLevel(
|
|
433
662
|
requested: BtwThinkingLevel,
|
|
434
663
|
available: readonly BtwThinkingLevel[],
|
package/src/settings.ts
CHANGED
|
@@ -26,6 +26,7 @@ export type BtwSettingsLoadResult =
|
|
|
26
26
|
|
|
27
27
|
export interface BtwSettingsPatch {
|
|
28
28
|
keybindings?: BtwKeybindingOverrides;
|
|
29
|
+
model?: string;
|
|
29
30
|
thinkingLevel?: BtwThinkingLevel;
|
|
30
31
|
rememberThinkingLevelChanges?: boolean;
|
|
31
32
|
fullscreenCopyOnSelect?: boolean;
|
|
@@ -254,6 +255,10 @@ function applyBtwSettingsPatch(current: SettingsDocument, patch: BtwSettingsPatc
|
|
|
254
255
|
if (Object.keys(keys).length) updated.keybindings = keys;
|
|
255
256
|
else delete updated.keybindings;
|
|
256
257
|
}
|
|
258
|
+
if (Object.hasOwn(patch, "model")) {
|
|
259
|
+
if (patch.model === undefined) delete updated.model;
|
|
260
|
+
else updated.model = patch.model;
|
|
261
|
+
}
|
|
257
262
|
if (Object.hasOwn(patch, "thinkingLevel")) {
|
|
258
263
|
if (patch.thinkingLevel === undefined) delete updated.thinkingLevel;
|
|
259
264
|
else updated.thinkingLevel = patch.thinkingLevel;
|