export type AgentExecutionReport = { responses: { id: string; text: string }[]; activities: { id: string; title: string; detail?: string; status: "running" | "completed" | "error" }[]; }; const record = (value: unknown): Record | null => value !== null && typeof value === "object" && !Array.isArray(value) ? value as Record : null; export function updateExecutionReportProgress(report: AgentExecutionReport, value: unknown): AgentExecutionReport { const progress = record(value); // The report contains public execution facts, not the model's reasoning stream. if (!progress || !["tool", "start", "execution_plan"].includes(String(progress.phase)) || typeof progress.id !== "string" || typeof progress.title !== "string" || !["running", "completed", "error"].includes(String(progress.status))) return report; const activity = { id: progress.id, title: progress.title, detail: typeof progress.detail === "string" ? progress.detail : undefined, status: progress.status as "running" | "completed" | "error" }; const existing = report.activities.findIndex((item) => item.id === activity.id); return { ...report, activities: existing < 0 ? [...report.activities, activity].slice(-8) : report.activities.map((item, index) => index === existing ? activity : item) }; } export function readExecutionReport(messages: unknown[]): AgentExecutionReport { let report: AgentExecutionReport = { responses: [], activities: [] }; messages.forEach((value, index) => { const message = record(value); if (!message || message.role !== "assistant") return; const parts = Array.isArray(message.parts) ? message.parts.map(record).filter((part) => part !== null) : []; const text = parts.filter((part) => part.type === "text" && typeof part.text === "string").map((part) => part.text).join("") || (typeof message.content === "string" ? message.content : ""); report.responses.push({ id: typeof message.id === "string" ? message.id : `response-${index}`, text }); for (const progress of Array.isArray(message.progress) ? message.progress : []) report = updateExecutionReportProgress(report, progress); for (const part of parts) if (part.type === "data-progress") report = updateExecutionReportProgress(report, part.data); }); return { ...report, responses: report.responses.slice(-3) }; } export function appendExecutionReportText(report: AgentExecutionReport, value: unknown): AgentExecutionReport { const data = record(value); if (!data || typeof data.content !== "string") return report; const id = typeof data.message_id === "string" ? data.message_id : "current-response"; const matching = report.responses.findIndex((response) => response.id === id); // Active-run snapshots and UI stream chunks currently use different assistant IDs. const existing = matching < 0 ? report.responses.length - 1 : matching; return { ...report, responses: existing < 0 ? [...report.responses, { id, text: data.content }].slice(-3) : report.responses.map((response, index) => index === existing ? { ...response, id, text: response.text + data.content } : response) }; }