feat: Pi extension uses Pi's internal complete() for LLM analysis

- PiLLMClient now imports @mariozechner/pi-ai's complete() function
- Respects user's /model selection and /login auth
- No external fetch() — Pi handles transport internally
- Added src/types/pi-ai.d.ts for TypeScript declarations
- Extension reverted to real LLM calls (initProject, patchFile, reinitPath)
- Tests mock @mariozechner/pi-ai for verification
This commit is contained in:
2026-06-10 14:47:47 +02:00
parent 57c0f08210
commit 5c6719817f
4 changed files with 126 additions and 91 deletions
+48 -38
View File
@@ -2,10 +2,11 @@ import { LLMError } from "./llm-error.js";
import type { LLMClient } from "./llm-client.js";
/**
* Pi LLM Client — uses Pi's built-in model when running inside the Pi runtime.
* Pi LLM Client — uses Pi's built-in `complete()` from `@mariozechner/pi-ai`.
*
* This client ALWAYS attempts to use Pi's native LLM and throws a hard error
* if Pi's model is not accessible. It never falls back to external APIs.
* 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) {}
@@ -16,57 +17,66 @@ export class PiLLMClient implements LLMClient {
if (!ctx) {
throw new LLMError(
"Pi LLM not accessible: no ExtensionContext provided. " +
"This extension must run inside Pi.",
"This tool must run inside Pi.",
);
}
// Try to access Pi's model or modelRegistry
// 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 in Pi runtime. " +
"Configure a model (e.g., via /model) before using project-map tools.",
"Pi LLM not accessible: no model configured. " +
"Set a model via /model before using project-map tools.",
);
}
// Attempt to call the model via Pi's provider interface.
// Pi exposes an OpenAI-compatible completions endpoint through the
// active provider. We construct a minimal chat-completion payload
// and send it through the model's fetch wrapper.
try {
const provider = ctx.modelRegistry?.getProvider?.(model.provider);
if (!provider || typeof provider.complete !== "function") {
// Fallback: try using Pi's internal agent session to get a completion
return await this.callViaAgentSession(ctx, prompt);
// 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}`);
}
return await provider.complete(prompt);
// 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)}. ` +
"Ensure Pi has a working model configured.",
`Pi LLM call failed: ${err.message || String(err)}`,
err,
);
}
}
private async callViaAgentSession(ctx: any, prompt: string): Promise<string> {
// Pi's ExtensionContext may expose a session or agent that can
// make model calls. This is the fallback when direct provider
// access is not available.
if (typeof ctx.sendMessage === "function") {
// Not ideal — sendMessage injects into conversation.
// Reserved for future Pi SDK improvement.
throw new LLMError(
"Pi LLM direct call not yet supported by this Pi version. " +
"The extension requires a Pi runtime with exposed model provider API.",
);
}
throw new LLMError(
"Pi LLM not accessible: the Pi runtime does not expose a callable model API. " +
"This extension requires Pi to provide either ctx.modelRegistry.getProvider() " +
"or a direct model completion interface.",
);
}
}
+24
View File
@@ -0,0 +1,24 @@
// Pi AI module — available only inside the Pi runtime
// Declared here so TypeScript doesn't error during `npm run build`
declare module "@mariozechner/pi-ai" {
export function complete(
model: any,
context: {
systemPrompt?: string;
messages: Array<{
role: "user" | "assistant" | "system" | "toolResult";
content: string;
timestamp?: number;
}>;
},
options?: {
temperature?: number;
maxTokens?: number;
},
): Promise<{
role: "assistant";
content: Array<{ type: string; text?: string }>;
errorMessage?: string;
}>;
}