refactor: consolidate requests and finalize agent tool states

Share project JSON transport, interaction replies and report selectors.
Settle tool progress consistently for direct streams, reconnects and history, preserving failure reasons and stopped state.
This commit is contained in:
2026-11-09 17:55:00 +08:00
parent ad1f7ca7a9
commit 692bbf609c
38 changed files with 605 additions and 280 deletions
@@ -125,3 +125,76 @@ it("keeps a malformed persisted data payload from crashing the conversation", ()
expect(toAgentChatMessages(messages, {}, {})[0].content).toBe("这段结构化内容无法显示。");
}
});
it("restores an aborted run's terminal state from canonical history", () => {
const messages = toAgentUiMessages([{ id: "answer", role: "assistant", parts: [
{ type: "text", text: "已核查的部分结果" },
{ type: "data-progress", data: { id: "query", title: "查询", status: "running" } },
{ type: "data-run_status", data: { status: "aborted", message: "请求已中断" } }
] }]);
expect(toAgentChatMessages(messages, {}, {})[0]).toMatchObject({ content: "已核查的部分结果", progress: [{ status: "cancelled" }] });
});
describe("terminal tool progress", () => {
const parts = [
{ type: "text", text: "已核查的部分结果" },
{ type: "data-progress", data: { id: "tool", phase: "tool", title: "正在处理", status: "running", tool: { name: "query", input: { id: 12 } } } },
{ type: "data-todo_update", data: { session_id: "s", todos: [{ id: "todo", content: "核查", status: "in_progress" }] } },
{ type: "data-permission_request", data: { request_id: "permission", session_id: "s", permission: "read", patterns: [] } },
{ type: "data-question_request", data: { request_id: "question", session_id: "s", questions: [{ question: "继续吗?", options: [] }] } }
];
const initial = () => toAgentUiMessages([{ id: "answer", role: "assistant", parts }]);
it.each(["done", "error", "aborted"] as const)("settles running tool rows after resumed %s", (type) => {
const messages = applySessionStreamEvent(initial(), { type, data: { message: "模型请求失败" } });
const answer = toAgentChatMessages(messages, {}, {})[0];
expect(answer.progress?.[0]).toMatchObject({ status: type === "done" ? "completed" : type === "aborted" ? "cancelled" : "error", tool: { name: "query", input: { id: 12 } } });
expect(answer.todos?.todos[0].status).toBe("cancelled");
expect(answer.content).toBe("已核查的部分结果");
if (type === "error") expect(answer.runError).toBe("模型请求失败");
});
it("preserves the reason when reopening a failed answer with partial structured output", () => {
const messages = toAgentUiMessages([{ id: "answer", role: "assistant", parts: [...parts,
{ type: "data-final_answer", data: { content: "已核查的部分结果" } },
{ type: "data-run_status", data: { status: "error", message: "模型请求失败" } }
] }]);
const answer = toAgentChatMessages(messages, {}, {})[0];
expect(answer).toMatchObject({ content: "已核查的部分结果", runError: "模型请求失败", progress: [{ status: "error", detail: "模型请求失败" }] });
});
it("does not let an old reply override revive stopped cards", () => {
const messages = applySessionStreamEvent(initial(), { type: "run_status", data: { status: "aborted" } });
const answer = toAgentChatMessages(messages, { permission: { status: "error", error: "late HTTP response" } },
{ question: { status: "submitting" } })[0];
expect(answer.permissions?.[0].status).toBe("aborted");
expect(answer.questions?.[0].status).toBe("rejected");
const stopped = applySessionStreamEvent(messages, { type: "error", data: { message: "请求已中断" } });
expect(toAgentChatMessages(stopped, {}, {})[0].runError).toBeUndefined();
expect(toAgentChatMessages(stopped, {}, {})[0].progress?.[0].status).toBe("cancelled");
});
it("settles legacy history using the session status while leaving active tools running", () => {
for (const status of ["completed", "aborted", "error", "waiting_input", "running"] as const) {
const messages = toAgentUiMessages(initial(), { runStatus: status, isStreaming: status === "running" });
expect(toAgentChatMessages(messages, {}, {})[0].progress?.[0].status)
.toBe(status === "running" ? "running" : status === "error" ? "error" : status === "aborted" ? "cancelled" : "completed");
}
const state = applySessionStreamEvent(initial(), { type: "state", sessionId: "s", isStreaming: false, messages: initial() });
expect(toAgentChatMessages(state, {}, {})[0].progress?.[0].status).toBe("completed");
});
it("keeps a saved failure authoritative when a legacy session defaults to completed", () => {
const saved = applySessionStreamEvent(initial(), { type: "error", data: { message: "历史错误" } });
const answer = toAgentChatMessages(toAgentUiMessages(saved, { runStatus: "completed", isStreaming: false }), {}, {})[0];
expect(answer.runError).toBe("历史错误");
});
it("routes historical question replies to the owning assistant without changing the current turn", () => {
const messages = [...initial(), ...toAgentUiMessages([{ id: "current", role: "assistant", parts: [{ type: "text", text: "新一轮" }] }])];
const updated = applySessionStreamEvent(messages, { type: "question_response", data: { request_id: "question", rejected: true } });
const answers = toAgentChatMessages(updated, {}, {});
expect(answers[0].questions?.[0].status).toBe("rejected");
expect(updated[1]).toEqual(messages[1]);
});
});