mirror of
https://github.com/getpaseo/paseo.git
synced 2026-08-14 12:23:16 +00:00
Create activity curator that consolidates hundreds of tiny message chunks into clean, human-readable text. Saves tokens and improves readability dramatically. Changes: - Add activity-curator.ts with curateAgentActivity() function - Consolidate agent_message_chunk fragments into complete messages - Format tool calls with status, input/output in readable structure - Display plans with status icons (✅ completed, 🔄 in_progress, ⏳ pending) - Add priority indicators (🔴 high, 🟡 medium, 🟢 low) - Update get_agent_activity to support format='curated' (default) or 'raw' - Curated format returns clean markdown text instead of JSON array Example: 363 raw updates consolidating to single clean message instead of 363 JSON objects with fragments like " it.", " fix", "ify what". 🤖 Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
141 lines
4.0 KiB
TypeScript
141 lines
4.0 KiB
TypeScript
import type { AgentUpdate } from "./types.js";
|
|
|
|
/**
|
|
* Curate agent activity updates into a human-readable, token-efficient format
|
|
* Consolidates message chunks, formats tool calls, and structures output cleanly
|
|
*/
|
|
export function curateAgentActivity(updates: AgentUpdate[]): string {
|
|
const sections: string[] = [];
|
|
|
|
// Group updates by type and consolidate
|
|
let currentMessage = "";
|
|
let currentThought = "";
|
|
const toolCalls: Array<{ timestamp: Date; data: any }> = [];
|
|
const plans: Array<{ timestamp: Date; data: any }> = [];
|
|
|
|
for (const update of updates) {
|
|
const updateType = update.notification.update.sessionUpdate;
|
|
const content = (update.notification.update as any).content;
|
|
|
|
switch (updateType) {
|
|
case "agent_message_chunk":
|
|
if (content?.type === "text" && content?.text) {
|
|
currentMessage += content.text;
|
|
}
|
|
break;
|
|
|
|
case "agent_thought_chunk":
|
|
if (content?.type === "text" && content?.text) {
|
|
currentThought += content.text;
|
|
}
|
|
break;
|
|
|
|
case "tool_call":
|
|
case "tool_call_update":
|
|
toolCalls.push({
|
|
timestamp: update.timestamp,
|
|
data: update.notification.update,
|
|
});
|
|
break;
|
|
|
|
case "plan":
|
|
plans.push({
|
|
timestamp: update.timestamp,
|
|
data: (update.notification.update as any).entries,
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Format consolidated message
|
|
if (currentMessage.trim()) {
|
|
sections.push("## Agent Response\n\n" + currentMessage.trim());
|
|
}
|
|
|
|
// Format consolidated thoughts
|
|
if (currentThought.trim()) {
|
|
sections.push(
|
|
"## Agent Thoughts\n\n```\n" + currentThought.trim() + "\n```"
|
|
);
|
|
}
|
|
|
|
// Format tool calls
|
|
if (toolCalls.length > 0) {
|
|
const toolSection = ["## Tool Calls\n"];
|
|
|
|
// Group tool calls by toolCallId to show lifecycle
|
|
const toolGroups = new Map<string, typeof toolCalls>();
|
|
for (const call of toolCalls) {
|
|
const toolCallId = call.data.toolCallId || "unknown";
|
|
if (!toolGroups.has(toolCallId)) {
|
|
toolGroups.set(toolCallId, []);
|
|
}
|
|
toolGroups.get(toolCallId)!.push(call);
|
|
}
|
|
|
|
for (const [toolCallId, calls] of toolGroups) {
|
|
const latestCall = calls[calls.length - 1];
|
|
const data = latestCall.data;
|
|
|
|
toolSection.push(`\n### ${data.title || data.kind || "Tool"}`);
|
|
toolSection.push(`- **Status**: ${data.status || "unknown"}`);
|
|
toolSection.push(`- **Call ID**: ${toolCallId.slice(0, 8)}...`);
|
|
|
|
if (data.rawInput && Object.keys(data.rawInput).length > 0) {
|
|
toolSection.push(`- **Input**: ${JSON.stringify(data.rawInput, null, 2)}`);
|
|
}
|
|
|
|
if (data.rawOutput && Object.keys(data.rawOutput).length > 0) {
|
|
toolSection.push(`- **Output**: ${JSON.stringify(data.rawOutput, null, 2)}`);
|
|
}
|
|
|
|
if (data.content && data.content.length > 0) {
|
|
toolSection.push(`- **Content**: ${formatToolContent(data.content)}`);
|
|
}
|
|
}
|
|
|
|
sections.push(toolSection.join("\n"));
|
|
}
|
|
|
|
// Format plans
|
|
if (plans.length > 0) {
|
|
const latestPlan = plans[plans.length - 1];
|
|
const planSection = ["## Plan\n"];
|
|
|
|
for (const entry of latestPlan.data) {
|
|
const icon =
|
|
entry.status === "completed" ? "✅" :
|
|
entry.status === "in_progress" ? "🔄" :
|
|
"⏳";
|
|
|
|
const priority =
|
|
entry.priority === "high" ? "🔴" :
|
|
entry.priority === "medium" ? "🟡" :
|
|
"🟢";
|
|
|
|
planSection.push(`${icon} ${priority} ${entry.content}`);
|
|
}
|
|
|
|
sections.push(planSection.join("\n"));
|
|
}
|
|
|
|
return sections.join("\n\n---\n\n") || "No activity to display.";
|
|
}
|
|
|
|
/**
|
|
* Format tool call content into readable text
|
|
*/
|
|
function formatToolContent(content: any[]): string {
|
|
return content
|
|
.map((item) => {
|
|
if (item.type === "text") {
|
|
return item.text;
|
|
}
|
|
if (item.type === "image") {
|
|
return `[Image: ${item.source || "embedded"}]`;
|
|
}
|
|
return JSON.stringify(item);
|
|
})
|
|
.join("\n");
|
|
}
|