assistant-cloud
Version:
Cloud integration for assistant-ui
259 lines (245 loc) • 7.64 kB
text/typescript
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<string, unknown>,
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();
});
});