import { describe, expect, it } from "vitest"; import { BaseEvent, EventType, ToolCallResultEvent, RunErrorEvent, } from "@ag-ui/client"; import { finalizeRunEvents } from "@copilotkit/shared"; const createTextStart = (messageId: string): BaseEvent => ({ type: EventType.TEXT_MESSAGE_START, messageId, }) as BaseEvent; const createToolStart = (toolCallId: string): BaseEvent => ({ type: EventType.TOOL_CALL_START, toolCallId, }) as BaseEvent; describe("finalizeRunEvents", () => { it("closes streams with a RUN_FINISHED event when a stop was requested", () => { const events: BaseEvent[] = [ createTextStart("msg-1"), createToolStart("tool-1"), ]; const appended = finalizeRunEvents(events, { stopRequested: true }); expect(appended.map((event) => event.type)).toEqual([ EventType.TEXT_MESSAGE_END, EventType.TOOL_CALL_END, EventType.TOOL_CALL_RESULT, EventType.RUN_FINISHED, ]); const resultEvent = appended.find( (event): event is ToolCallResultEvent => event.type === EventType.TOOL_CALL_RESULT, ); expect(JSON.parse(resultEvent?.content ?? "")).toEqual( expect.objectContaining({ status: "stopped", reason: "stop_requested", }), ); expect(events.at(-1)?.type).toBe(EventType.RUN_FINISHED); }); it("emits a RUN_ERROR with meaningful payload when the stream ends abruptly", () => { const events: BaseEvent[] = [ createTextStart("msg-1"), createToolStart("tool-1"), ]; const appended = finalizeRunEvents(events); expect(appended.map((event) => event.type)).toEqual([ EventType.TEXT_MESSAGE_END, EventType.TOOL_CALL_END, EventType.TOOL_CALL_RESULT, EventType.RUN_ERROR, ]); const resultEvent = appended.find( (event): event is ToolCallResultEvent => event.type === EventType.TOOL_CALL_RESULT, ); expect(JSON.parse(resultEvent?.content ?? "")).toEqual( expect.objectContaining({ status: "error", reason: "missing_terminal_event", }), ); const errorEvent = appended.find( (event): event is RunErrorEvent => event.type === EventType.RUN_ERROR, ); expect(errorEvent?.code).toBe("INCOMPLETE_STREAM"); expect(errorEvent?.message).toContain("terminal event"); }); it("only appends structural fixes when a terminal event already exists", () => { const events: BaseEvent[] = [ createTextStart("msg-1"), createToolStart("tool-1"), { type: EventType.RUN_FINISHED } as BaseEvent, ]; const appended = finalizeRunEvents(events); expect(appended.map((event) => event.type)).toEqual([ EventType.TEXT_MESSAGE_END, EventType.TOOL_CALL_END, ]); expect( appended.some((event) => event.type === EventType.TOOL_CALL_RESULT), ).toBe(false); expect(appended.some((event) => event.type === EventType.RUN_ERROR)).toBe( false, ); expect( appended.some((event) => event.type === EventType.RUN_FINISHED), ).toBe(false); }); });