84 lines
4.6 KiB
TypeScript
84 lines
4.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { InteractionRequest } from "../api/interaction-requests";
|
|
import type { AgentChatMessage } from "../types";
|
|
import { groupMessageProcesses } from "./message-process-groups";
|
|
|
|
const assistant = (id: string, content = ""): AgentChatMessage => ({
|
|
id, role: "assistant", content,
|
|
progress: [{ id: "query", phase: "tool", status: "completed", title: id }]
|
|
});
|
|
const user = (id: string): AgentChatMessage => ({ id, role: "user", content: id });
|
|
const request = (source: string, reply: string, extra: Partial<InteractionRequest> = {}): InteractionRequest => ({
|
|
version: "agent-interaction-request@1", interaction_id: `question-${source}`, message_id: source,
|
|
turn_id: `turn-${source}`, call_id: `call-${source}`, status: "answered", revision: 1, draft_revision: 0,
|
|
questions: [{ question_id: "scope", title: "范围", body: "核查范围?", mode: "single", required: true, allow_custom: true, options: [] }],
|
|
draft: [], created_at: "2026-10-28T10:00:00Z", delivery: { native_message_id: reply, revision: 1, status: "delivered" },
|
|
...extra
|
|
});
|
|
|
|
describe("process groups across clarification replies", () => {
|
|
const source = assistant("question");
|
|
const reply = user("reply");
|
|
const result = assistant("answer_reply", "结果正文");
|
|
const messages = [user("original"), source, reply, result];
|
|
|
|
it("moves the source process to the related continuation without editing stored messages or progress ids", () => {
|
|
const original = structuredClone(messages);
|
|
const groups = groupMessageProcesses(messages, [request(source.id, reply.id)]);
|
|
expect(groups.get(source.id)).toEqual([]);
|
|
expect(groups.get(result.id)).toEqual([source, result]);
|
|
expect(groups.get(result.id)?.map((message) => message.progress?.[0].id)).toEqual(["query", "query"]);
|
|
expect(messages).toEqual(original);
|
|
});
|
|
|
|
it("groups an existing result even when delivery is blocked and there is no final_answer", () => {
|
|
const record = request(source.id, reply.id, { delivery: { native_message_id: reply.id, revision: 2, status: "blocked", error_code: "CONTINUATION_UNCONFIRMED" } });
|
|
expect(groupMessageProcesses(messages, [record]).get(result.id)).toEqual([source, result]);
|
|
expect(record.delivery?.status).toBe("blocked");
|
|
});
|
|
|
|
it("keeps processes in place while waiting, cancelled, missing a source page or missing a continuation", () => {
|
|
for (const extra of [{ status: "pending" }, { status: "cancelled" }, { delivery: undefined },
|
|
{ delivery: { native_message_id: reply.id, revision: 2, status: "cancelled" } }] satisfies Partial<InteractionRequest>[]) {
|
|
expect(groupMessageProcesses(messages, [request(source.id, reply.id, extra)]).get(source.id)).toEqual([source]);
|
|
}
|
|
expect(groupMessageProcesses(messages.slice(0, -1), [request(source.id, reply.id)]).get(source.id)).toEqual([source]);
|
|
expect(groupMessageProcesses(messages.slice(2), [request(source.id, reply.id)]).get(result.id)).toEqual([result]);
|
|
});
|
|
|
|
it("does not merge across an unrelated user request on either side of the saved reply", () => {
|
|
for (const list of [
|
|
[source, user("other-request"), reply, result],
|
|
[source, reply, user("other-request"), result],
|
|
[reply, source, result]
|
|
]) {
|
|
const groups = groupMessageProcesses(list, [request(source.id, reply.id)]);
|
|
expect(groups.get(source.id)).toEqual([source]);
|
|
expect(groups.get(result.id)).toEqual([result]);
|
|
}
|
|
});
|
|
|
|
it("combines successive clarifications in order and ignores duplicate records", () => {
|
|
const reply2 = user("reply2");
|
|
const final = assistant("answer_reply2", "最终结果");
|
|
const first = request(source.id, reply.id);
|
|
const second = request(result.id, reply2.id);
|
|
const groups = groupMessageProcesses([...messages, reply2, final], [second, first, first]);
|
|
expect(groups.get(source.id)).toEqual([]);
|
|
expect(groups.get(result.id)).toEqual([]);
|
|
expect(groups.get(final.id)).toEqual([source, result, final]);
|
|
});
|
|
|
|
it("retains actionable questions and permissions at their original message", () => {
|
|
const pendingQuestion: AgentChatMessage = { ...source, questions: [{
|
|
requestId: "pending", sessionId: "s", questions: [], createdAt: 0, status: "checking"
|
|
}] };
|
|
const pendingPermission: AgentChatMessage = { ...source, permissions: [{
|
|
requestId: "pending", sessionId: "s", permission: "simulate", patterns: [], always: [], createdAt: 0, status: "pending"
|
|
}] };
|
|
for (const pending of [pendingQuestion, pendingPermission]) {
|
|
expect(groupMessageProcesses([pending, reply, result], [request(source.id, reply.id)]).get(source.id)).toEqual([pending]);
|
|
}
|
|
});
|
|
});
|