import { LLMError } from "./llm-error.js"; import type { LLMClient } from "./llm-client.js"; /** * Pi LLM Client — uses Pi's built-in `complete()` from `@mariozechner/pi-ai`. * * This runs inside the Pi extension runtime and calls Pi's internal AI layer, * which respects the user's configured model (/model) and auth (/login). * No external fetch() — Pi handles transport, retries, and token accounting. */ export class PiLLMClient implements LLMClient { constructor(private extensionContext?: unknown) {} async complete(prompt: string): Promise { const ctx = this.extensionContext as any; if (!ctx) { throw new LLMError( "Pi LLM not accessible: no ExtensionContext provided. " + "This tool must run inside Pi.", ); } // Get the active model from Pi's runtime const model = ctx.model ?? ctx.modelRegistry?.get?.(); if (!model) { throw new LLMError( "Pi LLM not accessible: no model configured. " + "Set a model via /model before using project-map tools.", ); } try { // Dynamically import Pi's AI module (available in the Pi runtime) const { complete } = await import("@mariozechner/pi-ai"); const response = await complete( model, { systemPrompt: "You are a code analysis assistant. Analyze the provided file and respond with concise, structured information.", messages: [ { role: "user", content: prompt, timestamp: Date.now(), }, ], }, { temperature: 0.1, maxTokens: 256, }, ); if (response.errorMessage) { throw new LLMError(`Pi LLM error: ${response.errorMessage}`); } // Extract text content from AssistantMessage const text = response.content .filter((c: any) => c.type === "text") .map((c: any) => c.text) .join("") .trim(); return text; } catch (err: any) { if (err instanceof LLMError) throw err; if (err.code === "MODULE_NOT_FOUND") { throw new LLMError( "Pi LLM not accessible: @mariozechner/pi-ai is not available. " + "This extension must run inside the Pi runtime.", ); } throw new LLMError( `Pi LLM call failed: ${err.message || String(err)}`, err, ); } } }