115 lines
4.1 KiB
TypeScript
115 lines
4.1 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
|
|
import { createActivityTracker } from "../../src/routes/chatActivityTracker.js";
|
|
import { initialActivity } from "../../src/routes/chatStreamEvents.js";
|
|
|
|
describe("createActivityTracker", () => {
|
|
it("moves real tool work out of preparation when the model omits activity updates", () => {
|
|
const events: Array<{ event: string; data: Record<string, unknown> }> = [];
|
|
const tracker = createActivityTracker({
|
|
clientSessionId: "client-session-1",
|
|
write: (event, data) => events.push({ event, data }),
|
|
});
|
|
const action = (id: string, tool: string, status: string) =>
|
|
tracker.upsertAction(
|
|
{ id, tool, state: { status, input: {} } } as never,
|
|
{},
|
|
);
|
|
|
|
tracker.start(initialActivity.id, initialActivity.title, initialActivity.reason);
|
|
action("skill-1", "skill", "completed");
|
|
action("read-1", "read", "completed");
|
|
action("data-1", "tjwater_cli", "completed");
|
|
action("data-1", "tjwater_cli", "completed");
|
|
action("compute-1", "bash", "completed");
|
|
action("map-1", "show_chart", "completed");
|
|
tracker.finalize("completed");
|
|
|
|
const started = events
|
|
.filter(({ data }) =>
|
|
(data.activity as { actions: unknown[] }).actions.length === 0 &&
|
|
(data.activity as { status: string }).status === "running",
|
|
)
|
|
.map(({ data }) => (data.activity as { title: string }).title);
|
|
expect(started).toEqual([
|
|
initialActivity.title,
|
|
"查找所需资料",
|
|
"获取业务数据",
|
|
"处理分析数据",
|
|
"更新地图与图表",
|
|
]);
|
|
expect(tracker.getActionContext("read-1")?.title).toBe("查找所需资料");
|
|
expect(tracker.getActionContext("data-1")?.title).toBe("获取业务数据");
|
|
expect(tracker.getActionContext("map-1")?.title).toBe("更新地图与图表");
|
|
});
|
|
|
|
it("keeps a model-provided stage across tool categories", () => {
|
|
const events: Array<{ event: string; data: Record<string, unknown> }> = [];
|
|
const tracker = createActivityTracker({
|
|
clientSessionId: "client-session-1",
|
|
write: (event, data) => events.push({ event, data }),
|
|
});
|
|
tracker.start(initialActivity.id, initialActivity.title, initialActivity.reason);
|
|
tracker.start("model-stage", "验证分析结果", "核对本次计算与数据。");
|
|
for (const [id, tool] of [["tool-1", "tjwater_cli"], ["tool-2", "show_chart"]]) {
|
|
tracker.upsertAction(
|
|
{ id, tool, state: { status: "completed", input: {} } } as never,
|
|
{},
|
|
);
|
|
}
|
|
expect(tracker.getActionContext("tool-1")?.title).toBe("验证分析结果");
|
|
expect(tracker.getActionContext("tool-2")?.title).toBe("验证分析结果");
|
|
expect(
|
|
events.filter(({ data }) =>
|
|
(data.activity as { actions: unknown[] }).actions.length === 0 &&
|
|
(data.activity as { status: string }).status === "running",
|
|
),
|
|
).toHaveLength(2);
|
|
});
|
|
|
|
it("groups actions and closes running children with their activity", () => {
|
|
const events: Array<{ event: string; data: Record<string, unknown> }> = [];
|
|
const tracker = createActivityTracker({
|
|
clientSessionId: "client-session-1",
|
|
write: (event, data) => events.push({ event, data }),
|
|
});
|
|
|
|
tracker.start("activity-1", "准备分析数据", "需要先获取分析输入。");
|
|
tracker.upsertAction(
|
|
{
|
|
id: "tool-1",
|
|
tool: "tjwater_cli",
|
|
state: {
|
|
status: "running",
|
|
input: { command: "data list" },
|
|
},
|
|
} as never,
|
|
{ command: "data list" },
|
|
);
|
|
tracker.finalize("cancelled");
|
|
|
|
expect(tracker.getCurrentContext()).toEqual({
|
|
id: "activity-1",
|
|
title: "准备分析数据",
|
|
reason: "需要先获取分析输入。",
|
|
});
|
|
expect(events.at(-1)).toMatchObject({
|
|
event: "activity_update",
|
|
data: {
|
|
session_id: "client-session-1",
|
|
activity: {
|
|
id: "activity-1",
|
|
status: "cancelled",
|
|
actions: [
|
|
expect.objectContaining({
|
|
id: "tool-1",
|
|
status: "completed",
|
|
target: "data list",
|
|
}),
|
|
],
|
|
},
|
|
},
|
|
});
|
|
});
|
|
});
|