返回 DeepSeek-Reasonix
history-tool-status.test.ts
根目录 / desktop / frontend / src / __tests__ / history-tool-status.test.ts
1 // Run: tsx src/__tests__/history-tool-status.test.ts
2
3 import { historyMessagesToItems } from "../lib/useController";
4 import type { HistoryMessage } from "../lib/types";
5
6 let passed = 0;
7 let failed = 0;
8
9 function eq(a: unknown, b: unknown, label: string) {
10 if (a === b) {
11 process.stdout.write(` PASS ${label}\n`);
12 passed += 1;
13 } else {
14 process.stdout.write(` FAIL ${label}: expected ${JSON.stringify(b)}, got ${JSON.stringify(a)}\n`);
15 failed += 1;
16 }
17 }
18
19 function toolItems(messages: HistoryMessage[]) {
20 return historyMessagesToItems(messages, "h").items.filter((item) => item.kind === "tool");
21 }
22
23 console.log("\nhistory tool status");
24
25 const userTimeItems = historyMessagesToItems([
26 { role: "user", content: "old prompt" },
27 { role: "user", content: "new prompt", createdAt: 1_718_000_000_000 },
28 ] as HistoryMessage[], "u").items.filter((item) => item.kind === "user");
29 eq(userTimeItems[0]?.kind === "user" && userTimeItems[0].createdAt, undefined, "history users without createdAt keep no timestamp");
30 eq(userTimeItems[1]?.kind === "user" && userTimeItems[1].createdAt, 1_718_000_000_000, "history users preserve createdAt when present");
31
32 const checkpointTurnItems = historyMessagesToItems([
33 { role: "user", content: "first", checkpointTurn: 0 },
34 { role: "assistant", content: "ok" },
35 { role: "user", content: "second", checkpointTurn: 3 },
36 ] as HistoryMessage[], "c").items.filter((item) => item.kind === "user");
37 eq(checkpointTurnItems[0]?.kind === "user" && checkpointTurnItems[0].checkpointTurn, 0, "history users preserve checkpoint turn zero");
38 eq(checkpointTurnItems[1]?.kind === "user" && checkpointTurnItems[1].checkpointTurn, 3, "history users preserve non-contiguous backend checkpoint turns");
39
40 const citedAssistant = historyMessagesToItems([
41 {
42 role: "assistant",
43 content: "done",
44 memoryCitations: [{ id: "mem-1", source: "MEMORY.md", lineStart: 116, lineEnd: 123, note: "workflow" }],
45 },
46 ] as HistoryMessage[], "m").items.filter((item) => item.kind === "assistant");
47 eq(citedAssistant[0]?.kind === "assistant" && citedAssistant[0].memoryCitations?.length, 1, "assistant history preserves memory citations");
48 eq(citedAssistant[0]?.kind === "assistant" && citedAssistant[0].memoryCitations?.[0]?.source, "MEMORY.md", "assistant memory citation source is preserved");
49 eq(citedAssistant[0]?.kind === "assistant" && citedAssistant[0].memoryCitations?.[0]?.lineEnd, 123, "assistant memory citation line range is preserved");
50
51 const lowercaseError = toolItems([
52 {
53 role: "assistant",
54 content: "",
55 toolCalls: [{ id: "todo-bad", name: "todo_write", arguments: "{\"todos\":[{\"content\":\"Bad\",\"status\":\"in_progress\"}]}" }],
56 },
57 {
58 role: "tool",
59 content: "error: rejected todo transition",
60 toolCallId: "todo-bad",
61 toolName: "todo_write",
62 },
63 ]);
64 eq(lowercaseError[0]?.kind === "tool" && lowercaseError[0].status, "error", "lowercase error result restores as error");
65 eq(Boolean(lowercaseError[0]?.kind === "tool" && lowercaseError[0].error), true, "lowercase error result carries error text");
66
67 const blocked = toolItems([
68 {
69 role: "assistant",
70 content: "",
71 toolCalls: [{ id: "writer", name: "write_file", arguments: "{}" }],
72 },
73 {
74 role: "tool",
75 content: "blocked: plan mode is read-only",
76 toolCallId: "writer",
77 toolName: "write_file",
78 },
79 ]);
80 eq(blocked[0]?.kind === "tool" && blocked[0].status, "error", "blocked result restores as error");
81
82 const missingResult = toolItems([
83 {
84 role: "assistant",
85 content: "",
86 toolCalls: [{ id: "step-pending", name: "complete_step", arguments: "{\"step\":\"A\"}" }],
87 },
88 ]);
89 eq(missingResult[0]?.kind === "tool" && missingResult[0].status, "stopped", "missing tool result restores as stopped");
90
91 const positionalResult = toolItems([
92 {
93 role: "assistant",
94 content: "",
95 toolCalls: [{ id: "", name: "todo_write", arguments: "{\"todos\":[{\"content\":\"A\",\"status\":\"in_progress\"}]}" }],
96 },
97 {
98 role: "tool",
99 content: "Todos updated",
100 toolCallId: "",
101 toolName: "todo_write",
102 },
103 ]);
104 eq(positionalResult.length, 1, "positional tool result is consumed instead of rendering as an orphan");
105 eq(positionalResult[0]?.kind === "tool" && positionalResult[0].status, "done", "empty-id tool call restores from positional result");
106 eq(positionalResult[0]?.kind === "tool" && positionalResult[0].output, "Todos updated", "empty-id tool call keeps positional output");
107
108 const archivedResult = toolItems([
109 {
110 role: "assistant",
111 content: "",
112 toolCalls: [{
113 id: "archived-bash",
114 name: "bash",
115 arguments: "",
116 argumentsArchived: true,
117 subject: "printf x",
118 summary: "600 lines",
119 }],
120 },
121 {
122 role: "tool",
123 content: "",
124 toolCallId: "archived-bash",
125 toolName: "bash",
126 toolResultArchived: true,
127 },
128 ] as HistoryMessage[]);
129 eq(archivedResult[0]?.kind === "tool" && archivedResult[0].status, "done", "archived successful result restores as done");
130 eq(archivedResult[0]?.kind === "tool" && archivedResult[0].dataArchived, true, "archived successful result is marked dataArchived immediately");
131 eq(archivedResult[0]?.kind === "tool" && archivedResult[0].output, undefined, "archived successful result has no initial output");
132 eq(archivedResult[0]?.kind === "tool" && archivedResult[0].args, "", "archived successful result has no initial args");
133 eq(archivedResult[0]?.kind === "tool" && archivedResult[0].subject, "printf x", "archived successful result keeps precomputed subject");
134 eq(archivedResult[0]?.kind === "tool" && archivedResult[0].summary, "600 lines", "archived successful result keeps precomputed summary");
135
136 const archivedError = toolItems([
137 {
138 role: "assistant",
139 content: "",
140 toolCalls: [{
141 id: "archived-error",
142 name: "bash",
143 arguments: "",
144 argumentsArchived: true,
145 subject: "rm protected",
146 }],
147 },
148 {
149 role: "tool",
150 content: "error: permission denied",
151 toolCallId: "archived-error",
152 toolName: "bash",
153 toolResultArchived: true,
154 toolResultError: "error: permission denied",
155 },
156 ] as HistoryMessage[]);
157 eq(archivedError[0]?.kind === "tool" && archivedError[0].status, "error", "archived failed result restores as error");
158 eq(archivedError[0]?.kind === "tool" && archivedError[0].dataArchived, true, "archived failed result is still loadable on demand");
159 eq(archivedError[0]?.kind === "tool" && archivedError[0].error, "error: permission denied", "archived failed result keeps bounded error preview");
160
161 console.log(`\n${passed} passed, ${failed} failed`);
162 if (failed > 0) process.exit(1);
163
163 lines TYPESCRIPT