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
+30 -12
View File
@@ -15,14 +15,17 @@ vi.mock("typebox", () => ({
},
}));
// Mock Pi's AI module so PiLLMClient works in tests
vi.mock("@mariozechner/pi-ai", () => ({
complete: vi.fn(async (_model: any, _context: any) => ({
role: "assistant" as const,
content: [{ type: "text", text: "PURPOSE: Test file\nDEPS: none\nCONCEPTS: testing" }],
})),
}));
import extension from "../pi-extension.js";
const CACHE_FILE = join(
homedir(),
".cache",
"pi-project-map",
"llm-cache.json",
);
const CACHE_FILE = join(homedir(), ".cache", "pi-project-map", "llm-cache.json");
function clearCache() {
if (existsSync(CACHE_FILE)) unlinkSync(CACHE_FILE);
}
@@ -40,7 +43,10 @@ describe("pi-extension", () => {
mockNotify = vi.fn();
mockCtx = {
cwd: "/home/project",
modelRegistry: {},
model: { provider: "openai", id: "gpt-4o-mini", api: "openai-completions" },
modelRegistry: {
getApiKeyAndHeaders: vi.fn(async () => ({ apiKey: "test-key", headers: {} })),
},
ui: { notify: mockNotify },
};
@@ -72,7 +78,7 @@ describe("pi-extension", () => {
});
describe("project_map_init tool", () => {
it("generates heuristic maps successfully", async () => {
it("generates maps with mocked Pi LLM", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, "test.ts"), `export const x = ${Date.now()};`);
mockCtx.cwd = dir;
@@ -80,12 +86,24 @@ describe("pi-extension", () => {
const tool = registeredTools.project_map_init;
const result = await tool.execute("tool-1", {}, null, null, mockCtx);
expect(result.details.success).toBe(true);
expect(result.content[0].text).toContain("Generated heuristic");
expect(result.content[0].text).toContain("Generated");
});
it("returns error when no Pi model configured", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, "test.ts"), `export const x = ${Date.now()};`);
mockCtx.cwd = dir;
mockCtx.model = null;
const tool = registeredTools.project_map_init;
const result = await tool.execute("tool-1", {}, null, null, mockCtx);
expect(result.details.success).toBe(false);
expect(result.content[0].text).toContain("no model configured");
});
});
describe("project_map_patch tool", () => {
it("patches map successfully using heuristics", async () => {
it("patches map with mocked Pi LLM", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
const file = join(dir, "test.ts");
writeFileSync(file, `export const x = ${Date.now()};`);
@@ -132,7 +150,7 @@ describe("pi-extension", () => {
});
describe("project_map_reinit tool", () => {
it("regenerates heuristic maps successfully", async () => {
it("regenerates maps with mocked Pi LLM", async () => {
const dir = mkdtempSync(join(tmpdir(), "pi-ext-test-"));
writeFileSync(join(dir, "test.ts"), `export const x = ${Date.now()};`);
mockCtx.cwd = dir;
@@ -140,7 +158,7 @@ describe("pi-extension", () => {
const tool = registeredTools.project_map_reinit;
const result = await tool.execute("tool-1", {}, null, null, mockCtx);
expect(result.details.success).toBe(true);
expect(result.content[0].text).toContain("Regenerated heuristic");
expect(result.content[0].text).toContain("Regenerated");
});
});