// Summarize — turn-end checkpoint into a Summary. import type { ExtensionContext } from "@earendil-works/pi-coding-agent"; import type { AgentMessage } from "@earendil-works/pi-agent-core"; import { complete } from "@earendil-works/pi-ai"; import { insertSummary } from "../store/db.js"; import { buildSummaryPrompt } from "./prompts.js"; import { extractText, parseSummaryXml } from "./parser.js"; import type { Summary } from "../types.js"; export interface SummarizeInput { sessionId: string; project: string; messages: AgentMessage[]; } export async function summarizeTurn(ctx: ExtensionContext, input: SummarizeInput): Promise { if (input.messages.length === 0) return; const model = ctx.model; if (!model) { console.warn("[pi-mem-cc] no current model for summarization"); return; } const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model); if (!auth.ok) { console.warn("[pi-mem-cc] model auth unavailable for summary:", auth.error); return; } if (!auth.apiKey) { console.warn("[pi-mem-cc] model returned no apiKey for summary"); return; } // Build a compact representation: last assistant text + tool call inventory. let lastAssistantText = ""; const toolInventory: Array<{ name: string; isError?: boolean }> = []; for (const msg of input.messages) { if (msg.role !== "assistant") continue; const text = extractText(msg.content); if (text) lastAssistantText = text; if (Array.isArray(msg.content)) { for (const part of msg.content) { if (part && typeof part === "object") { const p = part as { type?: string; name?: string }; if (p.type === "toolCall" && typeof p.name === "string") { toolInventory.push({ name: p.name }); } } } } } const toolSummary = toolInventory.length ? toolInventory.map((t) => `- ${t.name}`).join("\n") : "(no tool calls)"; const userPrompt = buildSummaryPrompt({ sessionId: input.sessionId, finalAssistantText: lastAssistantText, toolCallSummary: toolSummary, }); let responseText: string; try { const response = await complete( model, { messages: [ { role: "user" as const, content: [{ type: "text" as const, text: userPrompt }], timestamp: Date.now(), }, ], }, { apiKey: auth.apiKey, headers: auth.headers, }, ); responseText = response.content .filter((c): c is { type: "text"; text: string } => c.type === "text") .map((c) => c.text) .join(""); } catch (err) { console.warn("[pi-mem-cc] summarization failed:", err); return; } const parsed = parseSummaryXml(responseText); if (!parsed.valid) { console.debug("[pi-mem-cc] summary did not parse:", parsed.reason); return; } const summary: Summary = { sessionId: input.sessionId, project: input.project, request: parsed.value.request, investigated: parsed.value.investigated, learned: parsed.value.learned, completed: parsed.value.completed, nextSteps: parsed.value.nextSteps, notes: parsed.value.notes, createdAt: Date.now(), }; try { insertSummary(summary); } catch (err) { console.warn("[pi-mem-cc] failed to persist summary:", err); } }