/** * model-picker.ts — Interactive model selection dialog. * * Presents all available models from ~/.pi/agent/models.json as a selectable list, * grouped by provider with context window and reasoning capability indicators. */ import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent"; import { getModelPickerSections } from "../model-resolver.js"; /** * Show model picker dialog. * Returns "provider/modelId" string, "inherit", or undefined if cancelled. */ export async function showModelPicker( ctx: ExtensionCommandContext, initialModel?: string, ): Promise { const sections = getModelPickerSections(); if (sections.length === 0) { // No models available at all — just offer inherit and custom const choice = await ctx.ui.select("Select model", [ "Inherit from parent", "Custom... (type provider/modelId)", ]); if (!choice) return undefined; if (choice.startsWith("Inherit")) return "inherit"; return promptCustomModel(ctx); } // Build a flat options list with non-selectable separators between sections const options: string[] = []; const valueMap = new Map(); // display label → stored value for (const section of sections) { if (section.label) { // Section header (non-selectable) options.push(`─── ${section.label} ───`); } for (const item of section.models) { const desc = item.description ? ` — ${item.description}` : ""; const label = ` ${item.label}${desc}`; options.push(label); valueMap.set(label, item.value); } } const choice = await ctx.ui.select("Select model", options); if (!choice) return undefined; const value = valueMap.get(choice); if (!value) return undefined; // selected a header or unknown if (value === "__custom__") { return promptCustomModel(ctx); } return value; } /** * Prompt the user to type a provider/modelId manually. */ async function promptCustomModel( ctx: ExtensionCommandContext, ): Promise { const custom = await ctx.ui.input( 'Custom model (e.g. "anthropic/claude-sonnet-4-20250514")', "", ); if (!custom) return undefined; const trimmed = custom.trim(); if (!trimmed) return undefined; // If just a model name without provider, store as-is; // actual provider resolution happens later via resolveModel() if (!trimmed.includes("/") && trimmed !== "inherit") { return trimmed; } return trimmed; }