/** * ask_smarter_model — let any agent consult a stronger model on hard problems. * * Default target is Claude Fable 5 (currently the smartest model available here), * called with maximum thinking ("xhigh"). Fable runs through the * claude-code-subscription-provider, so we resolve it from the model registry and * call its registered streamSimple via completeSimple. */ import { completeSimple, type Api, type Model, type UserMessage } from "@earendil-works/pi-ai"; import { keyHint, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent"; import { Text } from "@earendil-works/pi-tui"; import { Type, type Static } from "typebox"; // Collapsed view shows at most this many lines; Ctrl+O (app.tools.expand) shows all. const COLLAPSED_LINES = 3; function clip(text: string, expanded: boolean): { body: string; hidden: number } { const lines = text.replace(/\n+$/, "").split("\n"); if (expanded || lines.length <= COLLAPSED_LINES) return { body: lines.join("\n"), hidden: 0 }; return { body: lines.slice(0, COLLAPSED_LINES).join("\n"), hidden: lines.length - COLLAPSED_LINES }; } // Default to the subscription Fable, fall back to the API one if subscription // provider isn't loaded. const DEFAULT_TARGETS = [ { provider: "claude-code-subscription-provider", id: "fable-5" }, { provider: "anthropic", id: "claude-fable-5" }, ]; const schema = Type.Object({ prompt: Type.String({ description: "The question or task to send to the smarter model. Include all context it needs — it does not see this conversation.", }), model: Type.Optional( Type.String({ description: "Optional model override as 'provider/id' (e.g. 'anthropic/claude-opus-4-8') or a bare model id. Defaults to Claude Fable 5, which is currently the smartest model available.", }), ), }); export type AskSmarterModelInput = Static; type RenderResult = { content: Array<{ type: string; text?: string }>; details?: { model?: string }; isError?: boolean; }; function resolveModel(ctx: ExtensionContext, override?: string): Model | undefined { const registry = ctx.modelRegistry; if (override?.trim()) { const spec = override.trim(); if (spec.includes("/")) { const [provider, ...rest] = spec.split("/"); return registry.find(provider, rest.join("/")); } // Bare id: first exact match across providers. return registry.getAll().find((m) => m.id === spec); } for (const target of DEFAULT_TARGETS) { const model = registry.find(target.provider, target.id); if (model) return model; } return undefined; } export default function (pi: ExtensionAPI) { pi.registerTool({ name: "ask_smarter_model", label: "Ask Smarter Model", description: "Consult a stronger model (default: Claude Fable 5, the smartest model available right now) with maximum thinking. " + "Use for hard reasoning, tricky debugging, architecture/design judgment, or a second opinion when you're unsure. " + "The model sees ONLY the prompt you pass — include all relevant context, code, and constraints inline. " + "Only use this when you genuinely need it (a problem you can't crack on your own) or when the user explicitly asks for it — not by default.", promptSnippet: "Ask a stronger model (Fable 5, xhigh thinking) for help on hard problems; pass full context in the prompt.", parameters: schema, async execute(_toolCallId, params: AskSmarterModelInput, signal, onUpdate, ctx) { const model = resolveModel(ctx, params.model); if (!model) { const wanted = params.model ?? "fable-5"; return { content: [{ type: "text", text: `Model not found: ${wanted}. Check available models with --list-models.` }], isError: true, }; } const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model); if (!auth.ok) { return { content: [{ type: "text", text: `Auth error for ${model.provider}/${model.id}: ${auth.error}` }], isError: true }; } onUpdate?.({ content: [{ type: "text", text: `Asking ${model.provider}/${model.id} (thinking: xhigh)...` }] }); const message: UserMessage = { role: "user", content: [{ type: "text", text: params.prompt }], timestamp: Date.now(), }; const response = await completeSimple( model, { messages: [message] }, { apiKey: auth.apiKey, headers: auth.headers, reasoning: "xhigh", maxTokens: model.maxTokens, signal, }, ); if (response.stopReason === "aborted") { return { content: [{ type: "text", text: "Aborted." }], isError: true }; } const text = response.content .filter((c): c is { type: "text"; text: string } => c.type === "text") .map((c) => c.text) .join("\n") .trim(); return { content: [{ type: "text", text: text || "(empty response)" }], details: { model: `${model.provider}/${model.id}`, usage: response.usage }, }; }, renderCall(args: AskSmarterModelInput, theme, { expanded }) { const target = args.model?.trim() || "fable-5"; let out = theme.fg("toolTitle", theme.bold("ask_smarter_model ")) + theme.fg("muted", target); if (args.prompt) { const { body, hidden } = clip(args.prompt, expanded); out += "\n" + theme.fg("dim", body); if (hidden > 0) out += "\n" + theme.fg("dim", `… +${hidden} more lines (${keyHint("app.tools.expand", "to expand")})`); } return new Text(out, 0, 0); }, renderResult(result: RenderResult, { expanded }, theme) { const text = result.content .filter((c) => c.type === "text" && typeof c.text === "string") .map((c) => c.text as string) .join("\n"); if (result.isError) return new Text(theme.fg("error", text || "Error"), 0, 0); const { body, hidden } = clip(text, expanded); let out = theme.fg("success", body); if (hidden > 0) out += "\n" + theme.fg("dim", `… +${hidden} more lines (${keyHint("app.tools.expand", "to expand")})`); return new Text(out, 0, 0); }, }); }