import type { ObserverEvent, PromptSection } from "./agentSessionTypes"; import { findBuzzToolName, isGenericToolTitle, normalizeToolName, } from "./agentSessionToolCatalog"; import { asRecord, asString, titleCase } from "./agentSessionUtils"; export function extractPromptText(payload: Record): string { const params = asRecord(payload.params); const prompt = params.prompt; if (!Array.isArray(prompt)) return ""; return prompt.map(extractBlockText).filter(Boolean).join("\n"); } export function parsePromptText(text: string): { sections: PromptSection[]; userText: string; userTitle: string; userPubkey: string | null; } { const sections = parsePromptSections(text).filter( (s) => s.body.trim().length > 0, ); if (sections.length === 0) { return { sections: [], userText: text.trim(), userTitle: "Prompt", userPubkey: null, }; } const eventSection = sections.find((section) => { const title = section.title.toLowerCase(); return title.startsWith("buzz event") || title.startsWith("buzz event"); }); const eventContent = eventSection ? extractEventContent(eventSection.body) : ""; const eventAuthorPubkey = eventSection ? extractEventAuthorPubkey(eventSection.body) : null; const eventKind = eventSection?.title.split(":").slice(1).join(":").trim(); return { sections, userText: eventContent, userTitle: eventKind ? titleCase(eventKind) : "Buzz event", userPubkey: eventAuthorPubkey, }; } /** * Split the framed `session/new` `systemPrompt` into its `Base`/`System` * sub-sections deterministically. * * The harness frames the value as `[Base]\n{base}\n\n[System]\n{persona}`, with * either prompt omitted when absent: base-only is `[Base]\n{base}`, persona-only * is `[System]\n{persona}`. We partition on the FIRST `\n[System]\n` boundary and * read each labeled body literally. Unlike the generic `parsePromptSections`, * embedded `[...]` lines inside a body never start a new section — so a persona * containing a bracketed line, or a mid-string-elided header on an oversize * prompt, can never drop a label or inflate the section count. */ export function parseSystemPromptSections( systemPrompt: string, ): PromptSection[] { const sections: PromptSection[] = []; // Persona-only frame: no [Base], starts directly with [System]. if (systemPrompt.startsWith("[System]\n")) { const body = systemPrompt.slice("[System]\n".length).trim(); if (body) sections.push({ title: "System", body }); return sections; } // Otherwise the head (up to the first [System] boundary, or the whole string) // is the [Base] body. const marker = "\n[System]\n"; const at = systemPrompt.indexOf(marker); const head = at === -1 ? systemPrompt : systemPrompt.slice(0, at); const baseBody = head.replace(/^\[Base]\n/, "").trim(); if (baseBody) sections.push({ title: "Base", body: baseBody }); if (at !== -1) { const systemBody = systemPrompt.slice(at + marker.length).trim(); sections.push({ title: "System", body: systemBody }); } return sections; } function parsePromptSections(text: string): PromptSection[] { const sections: PromptSection[] = []; let current: PromptSection | null = null; const preamble: string[] = []; for (const line of text.split(/\r?\n/)) { const header = line.match(/^\[([^\]]+)]\s*$/); if (header) { if (current) { sections.push({ title: current.title, body: current.body.trim(), }); } else if (preamble.join("\n").trim()) { sections.push({ title: "Prompt", body: preamble.join("\n").trim() }); } current = { title: header[1], body: "" }; continue; } if (current) { current.body += current.body ? `\n${line}` : line; } else { preamble.push(line); } } if (current) { sections.push({ title: current.title, body: current.body.trim() }); } else if (preamble.join("\n").trim()) { sections.push({ title: "Prompt", body: preamble.join("\n").trim() }); } return sections; } function extractEventContent(body: string): string { const contentMatch = body.match(/^Content:\s*(.*)$/m); return contentMatch?.[1]?.trim() ?? ""; } function extractEventAuthorPubkey(body: string): string | null { const fromMatch = body.match(/^From:.*\bhex:\s*([0-9a-fA-F]{64})/m); return fromMatch?.[1]?.toLowerCase() ?? null; } export function extractContentText(value: unknown): string { if (typeof value === "string") return value; if (Array.isArray(value)) return value.map(extractBlockText).join("\n"); return extractBlockText(value); } export function extractBlockText(value: unknown): string { if (typeof value === "string") return value; if (Array.isArray(value)) return value.map(extractBlockText).join("\n"); const record = asRecord(value); const nestedContent = record.content; const rawOutput = record.rawOutput; const nestedText = nestedContent && typeof nestedContent === "object" ? extractBlockText(nestedContent) : ""; const rawOutputText = rawOutput === undefined || rawOutput === null ? "" : typeof rawOutput === "string" ? rawOutput : JSON.stringify(rawOutput, null, 2); const directText = asString(record.text) ?? asString(record.content); return directText || nestedText || rawOutputText || ""; } export function extractToolArgs( update: Record, ): Record { const candidates = [ update.args, update.arguments, update.input, update.rawInput, ]; for (const candidate of candidates) { if ( candidate && typeof candidate === "object" && !Array.isArray(candidate) ) { return candidate as Record; } } return {}; } export function extractToolIdentity(update: Record): { title: string; toolName: string; buzzToolName: string | null; } { const candidates = collectToolNameCandidates(update); const knownName = candidates .map((candidate) => findBuzzToolName(candidate, true)) .find((candidate): candidate is string => Boolean(candidate)); const firstSpecific = candidates.find( (candidate) => !isGenericToolTitle(candidate), ); const title = asString(update.title) ?? knownName ?? firstSpecific ?? "Tool call"; return { title, toolName: knownName ?? normalizeToolName(firstSpecific ?? title), buzzToolName: knownName ?? null, }; } function collectToolNameCandidates(update: Record): string[] { const args = extractToolArgs(update); const tool = asRecord(update.tool); const input = asRecord(update.input); const rawInput = asRecord(update.rawInput); const candidates = [ update.toolName, update.tool_name, update.name, update.title, update.kind, tool.name, tool.toolName, args.toolName, args.tool_name, args.name, args.method, input.toolName, input.tool_name, input.name, rawInput.toolName, rawInput.tool_name, rawInput.name, ]; return candidates.flatMap((candidate) => { const value = asString(candidate); return value ? [value] : []; }); } export function extractToolResult(update: Record): string { const contentText = extractContentText(update.content); if (contentText) return contentText; return extractBlockText(update.rawOutput); } export function describeTurnStarted(payload: unknown): string { const record = asRecord(payload); const ids = Array.isArray(record.triggeringEventIds) ? record.triggeringEventIds.filter( (id): id is string => typeof id === "string", ) : []; return ids.length > 0 ? `Triggered by ${ids.length === 1 ? "1 event" : `${ids.length} events`}.` : ""; } export function describeSessionResolved(payload: unknown): string { const record = asRecord(payload); const isNewSession = record.isNewSession === true; return isNewSession ? "New session created." : ""; } export function describeRawEvent(event: ObserverEvent): string { const payload = asRecord(event.payload); const method = asString(payload.method); if (method === "session/update") { const update = asRecord(asRecord(payload.params).update); return asString(update.sessionUpdate) ?? method; } return method ?? event.kind; }