Files
next-tjwater-frontend/src/features/agent/execution-report.ts
T

50 lines
3.1 KiB
TypeScript

export type AgentExecutionReport = {
responses: { id: string; text: string }[];
activities: { id: string; title: string; detail?: string; status: "running" | "completed" | "error" }[];
};
const record = (value: unknown): Record<string, unknown> | null =>
value !== null && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : 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) };
}