Something went wrong. Try again.
fork of prime agent with changes i want but are not upstreamable probqbly (and some fixes)
Something went wrong. Try again.
5.2 kB · 206 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207import { complete, getModel } from "@earendil-works/pi-ai";import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";import { DynamicBorder, getMarkdownTheme } from "@earendil-works/pi-coding-agent";import { Container, Markdown, matchesKey, Text } from "@earendil-works/pi-tui";
type ContentBlock = { type?: string; text?: string; name?: string; arguments?: Record<string, unknown>;};
type SessionEntry = { type: string; message?: { role?: string; content?: unknown; };};
const extractTextParts = (content: unknown): string[] => { if (typeof content === "string") { return [content]; }
if (!Array.isArray(content)) { return []; }
const textParts: string[] = []; for (const part of content) { if (!part || typeof part !== "object") { continue; }
const block = part as ContentBlock; if (block.type === "text" && typeof block.text === "string") { textParts.push(block.text); } }
return textParts;};
const extractToolCallLines = (content: unknown): string[] => { if (!Array.isArray(content)) { return []; }
const toolCalls: string[] = []; for (const part of content) { if (!part || typeof part !== "object") { continue; }
const block = part as ContentBlock; if (block.type !== "toolCall" || typeof block.name !== "string") { continue; }
const args = block.arguments ?? {}; toolCalls.push(`Tool ${block.name} was called with args ${JSON.stringify(args)}`); }
return toolCalls;};
const buildConversationText = (entries: SessionEntry[]): string => { const sections: string[] = [];
for (const entry of entries) { if (entry.type !== "message" || !entry.message?.role) { continue; }
const role = entry.message.role; const isUser = role === "user"; const isAssistant = role === "assistant";
if (!isUser && !isAssistant) { continue; }
const entryLines: string[] = []; const textParts = extractTextParts(entry.message.content); if (textParts.length > 0) { const roleLabel = isUser ? "User" : "Assistant"; const messageText = textParts.join("\n").trim(); if (messageText.length > 0) { entryLines.push(`${roleLabel}: ${messageText}`); } }
if (isAssistant) { entryLines.push(...extractToolCallLines(entry.message.content)); }
if (entryLines.length > 0) { sections.push(entryLines.join("\n")); } }
return sections.join("\n\n");};
const buildSummaryPrompt = (conversationText: string): string => [ "Summarize this conversation so I can resume it later.", "Include goals, key decisions, progress, open questions, and next steps.", "Keep it concise and structured with headings.", "", "<conversation>", conversationText, "</conversation>", ].join("\n");
const showSummaryUi = async (summary: string, ctx: ExtensionCommandContext) => { if (!ctx.hasUI) { return; }
await ctx.ui.custom((_tui, theme, _kb, done) => { const container = new Container(); const border = new DynamicBorder((s: string) => theme.fg("accent", s)); const mdTheme = getMarkdownTheme();
container.addChild(border); container.addChild(new Text(theme.fg("accent", theme.bold("Conversation Summary")), 1, 0)); container.addChild(new Markdown(summary, 1, 1, mdTheme)); container.addChild(new Text(theme.fg("dim", "Press Enter or Esc to close"), 1, 0)); container.addChild(border);
return { render: (width: number) => container.render(width), invalidate: () => container.invalidate(), handleInput: (data: string) => { if (matchesKey(data, "enter") || matchesKey(data, "escape")) { done(undefined); } }, }; });};
export default function (pi: ExtensionAPI) { pi.registerCommand("summarize", { description: "Summarize the current conversation in a custom UI", handler: async (_args, ctx) => { const branch = ctx.sessionManager.getBranch(); const conversationText = buildConversationText(branch);
if (!conversationText.trim()) { if (ctx.hasUI) { ctx.ui.notify("No conversation text found", "warning"); } return; }
if (ctx.hasUI) { ctx.ui.notify("Preparing summary...", "info"); }
const model = getModel("openai", "gpt-5.2"); if (!model && ctx.hasUI) { ctx.ui.notify("Model openai/gpt-5.2 not found", "warning"); }
const auth = model ? await ctx.modelRegistry.getApiKeyAndHeaders(model) : undefined; if (auth && !auth.ok && ctx.hasUI) { ctx.ui.notify(auth.error, "warning"); } if (auth?.ok && !auth.apiKey && ctx.hasUI) { ctx.ui.notify("No API key for openai/gpt-5.2", "warning"); }
if (!model || !auth?.ok || !auth.apiKey) { return; }
const summaryMessages = [ { role: "user" as const, content: [{ type: "text" as const, text: buildSummaryPrompt(conversationText) }], timestamp: Date.now(), }, ];
const response = await complete( model, { messages: summaryMessages }, { apiKey: auth.apiKey, headers: auth.headers, reasoningEffort: "high", }, );
const summary = response.content .filter((c): c is { type: "text"; text: string } => c.type === "text") .map((c) => c.text) .join("\n");
await showSummaryUi(summary, ctx); }, });}