1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: MIT
4
5import {
6 type ExtensionAPI,
7 type ExtensionContext,
8 ModelSelectorComponent,
9 SettingsManager,
10} from "@earendil-works/pi-coding-agent";
11
12export type SetModelResult =
13 | { ok: true }
14 | { ok: false; reason: "invalid-format" | "unknown-model" | "set-failed"; detail: string };
15
16/**
17 * Parse a "provider/modelId" string, look it up in the model registry, and
18 * set it as the active model. Returns a result describing what happened.
19 */
20export async function parseAndSetModel(spec: string, ctx: ExtensionContext, pi: ExtensionAPI): Promise<SetModelResult> {
21 const slashIdx = spec.indexOf("/");
22 if (slashIdx <= 0 || slashIdx === spec.length - 1) {
23 return { ok: false, reason: "invalid-format", detail: `Invalid model format "${spec}", expected provider/modelId` };
24 }
25
26 const provider = spec.slice(0, slashIdx);
27 const modelId = spec.slice(slashIdx + 1);
28 const model = ctx.modelRegistry.find(provider, modelId);
29
30 if (!model) {
31 return { ok: false, reason: "unknown-model", detail: `Unknown model: ${spec}` };
32 }
33
34 try {
35 await pi.setModel(model);
36 } catch (err) {
37 const msg = err instanceof Error ? err.message : String(err);
38 return { ok: false, reason: "set-failed", detail: `Failed to set model ${spec}: ${msg}` };
39 }
40
41 return { ok: true };
42}
43
44/**
45 * Convenience wrapper: parse and set the model, notifying the user on failure.
46 */
47export async function parseAndSetModelWithNotify(
48 spec: string,
49 ctx: ExtensionContext,
50 pi: ExtensionAPI,
51): Promise<SetModelResult> {
52 const result = await parseAndSetModel(spec, ctx, pi);
53 if (!result.ok) {
54 ctx.ui.notify(result.detail, "warning");
55 }
56 return result;
57}
58
59/**
60 * Show Pi's built-in model picker and return the user's selection without
61 * changing Pi's active model or default model setting.
62 */
63export async function pickModel(
64 ctx: ExtensionContext,
65 currentModel: ExtensionContext["model"] = ctx.model,
66 initialSearchInput?: string,
67): Promise<ExtensionContext["model"]> {
68 return ctx.ui.custom<ExtensionContext["model"]>((tui, _theme, _keybindings, done) => {
69 const selector = new ModelSelectorComponent(
70 tui,
71 currentModel,
72 SettingsManager.inMemory(),
73 ctx.modelRegistry,
74 [],
75 (model) => done(model),
76 () => done(undefined),
77 initialSearchInput,
78 );
79
80 return selector;
81 });
82}