Files
Jyotisha/frontend/src/lib/agent-activity-trace.ts
T
Jesse_Chen 6b225a288c fix(web): keep later thinking below rectification tool steps
BUG-360: stream thinking and tool activity as an ordered trace so later CoT opens under 正在整理 instead of filling the first 思考 block.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-23 17:02:06 +08:00

108 lines
2.9 KiB
TypeScript

export type AgentActivityTraceKind = "activity" | "think";
export type AgentActivityTraceStatus = "live" | "done";
export type AgentActivityTraceItem = Readonly<{
id: string;
kind: AgentActivityTraceKind;
status: AgentActivityTraceStatus;
label: string;
text?: string;
startedAt?: number;
tool?: string;
}>;
const THINKING_TEXT_LIMIT = 8_000;
export function emptyActivityTrace(): readonly AgentActivityTraceItem[] {
return [];
}
export function appendActivityTraceThinking(
rows: readonly AgentActivityTraceItem[],
text: string,
): readonly AgentActivityTraceItem[] {
if (!text) return rows;
const last = rows.at(-1);
if (last?.kind === "think" && last.status === "live") {
return rows.map((row, index) => (
index === rows.length - 1
? { ...row, text: `${row.text ?? ""}${text}`.slice(0, THINKING_TEXT_LIMIT) }
: row
));
}
const frozen = freezeLiveThink(rows);
const thinkCount = frozen.filter((row) => row.kind === "think").length;
return [
...frozen,
{
id: `think-${thinkCount + 1}`,
kind: "think",
status: "live",
label: "思考",
text: text.slice(0, THINKING_TEXT_LIMIT),
},
];
}
export function startActivityTraceStep(
rows: readonly AgentActivityTraceItem[],
tool: string,
label: string,
startedAt = Date.now(),
): readonly AgentActivityTraceItem[] {
const frozen = freezeLiveThink(rows);
const last = frozen.at(-1);
if (last?.kind === "activity" && last.status === "live" && last.tool === tool) {
return frozen;
}
const closed = frozen.map((row) => (
row.kind === "activity" && row.status === "live" ? { ...row, status: "done" as const } : row
));
const toolCount = closed.filter((row) => row.kind === "activity" && row.tool === tool).length;
return [
...closed,
{
id: `activity-${tool}-${toolCount + 1}`,
kind: "activity",
status: "live",
label,
startedAt,
tool,
},
];
}
export function completeActivityTraceStep(
rows: readonly AgentActivityTraceItem[],
tool: string,
label?: string,
): readonly AgentActivityTraceItem[] {
for (let index = rows.length - 1; index >= 0; index -= 1) {
const row = rows[index];
if (row?.kind === "activity" && row.tool === tool && row.status === "live") {
return rows.map((item, current) => (
current === index
? { ...item, status: "done" as const, label: label ?? item.label }
: item
));
}
}
return rows;
}
export function freezeLiveThink(
rows: readonly AgentActivityTraceItem[],
): readonly AgentActivityTraceItem[] {
return rows.map((row) => (
row.kind === "think" && row.status === "live" ? { ...row, status: "done" as const } : row
));
}
export function completeActivityTrace(
rows: readonly AgentActivityTraceItem[],
): readonly AgentActivityTraceItem[] {
return freezeLiveThink(rows).map((row) => (
row.status === "live" ? { ...row, status: "done" as const } : row
));
}