6b225a288c
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>
108 lines
2.9 KiB
TypeScript
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
|
|
));
|
|
}
|