mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-08 17:34:51 +00:00
138 lines
4.6 KiB
TypeScript
138 lines
4.6 KiB
TypeScript
import type { AgentTimelineItem, ToolCallTimelineItem } from "@getpaseo/protocol/agent-types";
|
|
import type { AgentToolCallData, PluginTimelineStreamItem, StreamItem } from "@/types/stream";
|
|
import { cloneAndFreeze } from "../freeze";
|
|
import type { TimelineItemTransform } from "./model";
|
|
|
|
type AssistantPhase = "streaming" | "complete";
|
|
|
|
const projectionCache = new WeakMap<
|
|
TimelineItemTransform,
|
|
WeakMap<StreamItem, Partial<Record<AssistantPhase, StreamItem[]>>>
|
|
>();
|
|
|
|
function sourceToolCallTimelineItem(data: AgentToolCallData): ToolCallTimelineItem {
|
|
const { callId, name, status, error, detail, metadata, agentMessage } = data;
|
|
const base = {
|
|
type: "tool_call" as const,
|
|
callId,
|
|
name,
|
|
detail,
|
|
...(metadata ? { metadata } : {}),
|
|
...(agentMessage ? { agentMessage } : {}),
|
|
};
|
|
switch (status) {
|
|
case "running":
|
|
case "completed":
|
|
case "canceled":
|
|
return { ...base, status, error: null };
|
|
case "failed":
|
|
return { ...base, status, error };
|
|
}
|
|
}
|
|
|
|
function sourceTimelineItem(item: StreamItem): AgentTimelineItem | null {
|
|
switch (item.kind) {
|
|
case "user_message":
|
|
return {
|
|
type: "user_message",
|
|
text: item.text,
|
|
...(item.messageId ? { messageId: item.messageId } : {}),
|
|
...(item.clientMessageId ? { clientMessageId: item.clientMessageId } : {}),
|
|
};
|
|
case "assistant_message":
|
|
return {
|
|
type: "assistant_message",
|
|
text: item.text,
|
|
...(item.messageId ? { messageId: item.messageId } : {}),
|
|
};
|
|
case "thought":
|
|
return { type: "reasoning", text: item.text };
|
|
case "tool_call": {
|
|
if (item.payload.source !== "agent") return null;
|
|
return sourceToolCallTimelineItem(item.payload.data);
|
|
}
|
|
case "todo_list":
|
|
return { type: "todo", items: item.items };
|
|
case "notification":
|
|
return item.sourceType === "error"
|
|
? { type: "error", message: item.message }
|
|
: { type: "notification", level: item.level, message: item.message };
|
|
case "compaction":
|
|
return {
|
|
type: "compaction",
|
|
status: item.status,
|
|
...(item.trigger ? { trigger: item.trigger } : {}),
|
|
...(item.preTokens !== undefined ? { preTokens: item.preTokens } : {}),
|
|
};
|
|
case "plugin":
|
|
case "turn_summary":
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function transformSourceItem(
|
|
item: StreamItem,
|
|
transformTimelineItem: TimelineItemTransform,
|
|
assistantPhase: AssistantPhase,
|
|
): StreamItem[] {
|
|
const source = sourceTimelineItem(item);
|
|
if (!source) return [item];
|
|
const isStreamingThought = item.kind === "thought" && item.status === "loading";
|
|
const isStreamingToolCall =
|
|
item.kind === "tool_call" &&
|
|
item.payload.source === "agent" &&
|
|
item.payload.data.status === "running";
|
|
const isStreamingAssistant = item.kind === "assistant_message" && assistantPhase === "streaming";
|
|
const phase =
|
|
isStreamingAssistant || isStreamingThought || isStreamingToolCall ? "streaming" : "complete";
|
|
const transformed = transformTimelineItem({
|
|
item: cloneAndFreeze(source),
|
|
phase,
|
|
sourceId: item.id,
|
|
});
|
|
if (transformed === undefined) return [item];
|
|
return transformed.map((pluginItem) => {
|
|
const projected: PluginTimelineStreamItem = {
|
|
kind: "plugin",
|
|
id: `${pluginItem.pluginId}/${pluginItem.id}`,
|
|
sourceId: item.id,
|
|
sourceKind: item.kind,
|
|
timestamp: item.timestamp,
|
|
pluginId: pluginItem.pluginId,
|
|
pluginItemId: pluginItem.id,
|
|
itemKind: pluginItem.kind,
|
|
version: pluginItem.version,
|
|
data: pluginItem.data,
|
|
};
|
|
if (item.timelineCursor) projected.timelineCursor = item.timelineCursor;
|
|
if (item.turnId) projected.turnId = item.turnId;
|
|
return projected;
|
|
});
|
|
}
|
|
|
|
export function projectPluginTimelineItems(
|
|
items: StreamItem[],
|
|
transformTimelineItem: TimelineItemTransform | undefined,
|
|
assistantPhase: AssistantPhase = "complete",
|
|
): StreamItem[] {
|
|
if (!transformTimelineItem) return items;
|
|
let bySource = projectionCache.get(transformTimelineItem);
|
|
if (!bySource) {
|
|
bySource = new WeakMap();
|
|
projectionCache.set(transformTimelineItem, bySource);
|
|
}
|
|
let changed = false;
|
|
const projected = items.flatMap((item) => {
|
|
const cached = bySource.get(item)?.[assistantPhase];
|
|
if (cached) {
|
|
changed = changed || cached.length !== 1 || cached[0] !== item;
|
|
return cached;
|
|
}
|
|
const output = transformSourceItem(item, transformTimelineItem, assistantPhase);
|
|
bySource.set(item, { ...bySource.get(item), [assistantPhase]: output });
|
|
changed = changed || output.length !== 1 || output[0] !== item;
|
|
return output;
|
|
});
|
|
return changed ? projected : items;
|
|
}
|