import type { UIMessage } from "ai"; import { describe, expect, it } from "vitest"; import { aiSDKV6FormatAdapter, extractAISDKRunTelemetry, type AISDKMessageLike, } from "./index"; const assistant = ( parts: UIMessage["parts"], metadata?: Record, id = "a1", ): UIMessage => ({ id, role: "assistant", parts, ...(metadata ? { metadata } : {}), }) as UIMessage; describe("aiSDKV6FormatAdapter", () => { it("stores a message without its id and restores it from the row", () => { const message = assistant([{ type: "text", text: "hi" }], { a: 1 }); const content = aiSDKV6FormatAdapter.encode({ parentId: null, message }); expect(content).toEqual({ role: "assistant", parts: [{ type: "text", text: "hi" }], metadata: { a: 1 }, }); expect( aiSDKV6FormatAdapter.decode({ id: "a1", parent_id: "u1", format: "ai-sdk/v6", content, }), ).toEqual({ parentId: "u1", message }); expect(aiSDKV6FormatAdapter.getId(message)).toBe("a1"); }); }); describe("extractAISDKRunTelemetry", () => { it("returns null without an assistant message", () => { expect( extractAISDKRunTelemetry([ { role: "user", parts: [{ type: "text", text: "q" }] }, ]), ).toBeNull(); }); it("reads tool calls, steps and usage from one streamed message", () => { const message = assistant( [ { type: "step-start" }, { type: "tool-weather", toolCallId: "call_1", state: "output-available", input: { city: "Oslo" }, output: { temp: 3 }, } as UIMessage["parts"][number], { type: "step-start" }, { type: "dynamic-tool", toolName: "search", toolCallId: "call_2", state: "output-available", input: { q: "x" }, output: [{ type: "text", text: "found" }], } as UIMessage["parts"][number], { type: "step-start" }, { type: "text", text: "Cold." }, ], { usage: { inputTokens: 10, outputTokens: 5 }, steps: [ { usage: { promptTokens: 4, completionTokens: 1 } }, { usage: { inputTokens: 6, outputTokens: 4 } }, {}, ], samplingCalls: { call_2: [{ duration_ms: 20 }] }, modelId: "gpt-6-luna", }, ); expect(extractAISDKRunTelemetry([message])).toEqual({ assistantMessageId: "a1", status: "completed", toolCalls: [ { tool_name: "weather", tool_call_id: "call_1", tool_args: '{"city":"Oslo"}', tool_result: '{"temp":3}', tool_source: "frontend", }, { tool_name: "search", tool_call_id: "call_2", tool_args: '{"q":"x"}', tool_result: '[{"type":"text","text":"found"}]', tool_source: "mcp", sampling_calls: [{ duration_ms: 20 }], }, ], steps: [ { usage: { promptTokens: 4, completionTokens: 1 }, toolCalls: [expect.objectContaining({ tool_call_id: "call_1" })], finishReason: "tool-calls", }, { usage: { inputTokens: 6, outputTokens: 4 }, toolCalls: [expect.objectContaining({ tool_call_id: "call_2" })], finishReason: "tool-calls", }, {}, ], totalSteps: 3, outputText: "Cold.", usage: { inputTokens: 10, outputTokens: 5 }, modelId: "gpt-6-luna", metadata: message.metadata, }); }); it("sums usage over stored rows without ids and reads legacy tool fields", () => { const rows: AISDKMessageLike[] = [ { role: "assistant", parts: [ { type: "step-start" }, { type: "tool-lookup", toolCallId: "call_1", state: "output-available", args: { id: 1 }, result: "ok", } as unknown as UIMessage["parts"][number], ], metadata: { steps: [{ usage: { inputTokens: 2, outputTokens: 1 } }] }, }, { role: "assistant", parts: [{ type: "step-start" }, { type: "text", text: "done" }], metadata: { steps: [{ usage: { inputTokens: 3, reasoningTokens: 7 } }], }, }, ]; const result = extractAISDKRunTelemetry(rows); expect(result?.assistantMessageId).toBeUndefined(); expect(result?.status).toBe("completed"); expect(result?.totalSteps).toBe(2); expect(result?.usage).toEqual({ inputTokens: 5, outputTokens: 1, reasoningTokens: 7, }); expect(result?.toolCalls).toEqual([ { tool_name: "lookup", tool_call_id: "call_1", tool_args: '{"id":1}', tool_result: '"ok"', tool_source: "frontend", }, ]); }); it("attaches sampling calls from the row that made the tool call", () => { const rows: AISDKMessageLike[] = [ { role: "assistant", parts: [ { type: "dynamic-tool", toolName: "search", toolCallId: "call_1", state: "output-available", input: {}, output: "x", } as UIMessage["parts"][number], ], metadata: { samplingCalls: { call_1: [{ duration_ms: 5 }] } }, }, { role: "assistant", parts: [{ type: "text", text: "done" }], metadata: { samplingCalls: {} }, }, ]; expect(extractAISDKRunTelemetry(rows)?.toolCalls).toEqual([ expect.objectContaining({ tool_call_id: "call_1", tool_source: "mcp", sampling_calls: [{ duration_ms: 5 }], }), ]); }); it("reads a tool only run as completed with its step closed by the calls", () => { const call = (toolCallId: string, state: string, output?: unknown) => ({ type: "tool-search", toolCallId, state, input: {}, ...(output !== undefined ? { output } : {}), }) as UIMessage["parts"][number]; const answered = extractAISDKRunTelemetry([ assistant([{ type: "step-start" }, call("a", "output-available", [])]), ]); expect(answered?.status).toBe("completed"); expect(answered?.steps?.[0]?.finishReason).toBe("tool-calls"); expect(answered?.outputText).toBeUndefined(); const pending = extractAISDKRunTelemetry([ assistant([{ type: "step-start" }, call("b", "input-available")]), ]); expect(pending?.status).toBe("completed"); expect(pending?.steps?.[0]?.finishReason).toBe("tool-calls"); }); it("reads incomplete for a run that produced nothing", () => { const empty = extractAISDKRunTelemetry([ assistant([{ type: "step-start" }]), ]); expect(empty?.status).toBe("incomplete"); expect(empty?.totalSteps).toBe(1); expect(empty?.steps?.[0]?.finishReason).toBeUndefined(); expect(empty?.outputText).toBeUndefined(); }); it("reads no steps without step markers", () => { const result = extractAISDKRunTelemetry([ assistant([{ type: "text", text: "hi" }]), ]); expect(result?.steps).toBeUndefined(); expect(result?.totalSteps).toBeUndefined(); }); it("skips a stored part without a type", () => { const result = extractAISDKRunTelemetry([ { role: "assistant", parts: [ null, { toolCallId: "a" }, { type: "text", text: "hi" }, ] as never, }, ]); expect(result?.outputText).toBe("hi"); expect(result?.toolCalls).toBeUndefined(); }); });