assistant-stream
Version:
Streaming utilities for AI assistants
1,712 lines (1,591 loc) • 44.7 kB
text/typescript
import { describe, expect, it } from "vitest";
import { toGenericMessages, type GenericFilePart } from "./toGenericMessages";
import type { ToolModelContentPart } from "../tool/tool-types";
describe("toGenericMessages", () => {
describe("tool model content", () => {
const modelContent: readonly ToolModelContentPart[] = [
{ type: "text", text: "Report contents" },
{
type: "file",
data: "JVBERi0xLjQ=",
mediaType: "application/pdf",
filename: "report.pdf",
},
];
it.each([{ content: modelContent }, { content: [] }])(
"preserves explicit content on a settled result: %j",
({ content }) => {
const result = toGenericMessages([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call-1",
toolName: "report",
result: { documentId: "report-1" },
modelContent: content,
},
],
},
]);
expect(result.at(-1)).toEqual({
role: "tool",
content: [
{
type: "tool-result",
toolCallId: "call-1",
toolName: "report",
result: { documentId: "report-1" },
modelContent: content,
},
],
});
},
);
it.each([{}, { result: "preview", isPreliminary: true }])(
"does not forward content from an unfinished result: %j",
(extra) => {
const result = toGenericMessages([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call-1",
toolName: "report",
modelContent,
...extra,
},
],
},
]);
expect(result.at(-1)).toEqual({
role: "tool",
content: [
{
type: "tool-result",
toolCallId: "call-1",
toolName: "report",
result: { error: "Tool call was not completed" },
isError: true,
},
],
});
},
);
});
describe("system messages", () => {
it("converts text content", () => {
const result = toGenericMessages([
{
role: "system",
content: [{ type: "text", text: "You are a helpful assistant." }],
},
]);
expect(result).toEqual([
{ role: "system", content: "You are a helpful assistant." },
]);
});
it("handles empty text", () => {
const result = toGenericMessages([
{
role: "system",
content: [{ type: "text", text: "" }],
},
]);
expect(result).toEqual([]);
});
it("handles missing text property", () => {
const result = toGenericMessages([
{
role: "system",
content: [{ type: "text" }],
},
]);
expect(result).toEqual([]);
});
it("uses first text part when multiple exist", () => {
const result = toGenericMessages([
{
role: "system",
content: [
{ type: "text", text: "First" },
{ type: "text", text: "Second" },
],
},
]);
expect(result).toEqual([{ role: "system", content: "First" }]);
});
it("skips a null part when finding the text", () => {
const result = toGenericMessages([
{
role: "system",
content: [null, { type: "text", text: "Be brief." }],
},
] as never);
expect(result).toEqual([{ role: "system", content: "Be brief." }]);
});
});
describe("user messages", () => {
describe.each(["mp3", "wav"] as const)("legacy %s audio", (format) => {
it.each([false, true])(
"preserves audio bytes (attachment: %s)",
(attachment) => {
const part = { type: "audio", audio: { data: "UklGRg==", format } };
const message = {
role: "user" as const,
content: attachment ? [] : [part],
attachments: attachment ? [{ content: [part] }] : [],
};
expect(toGenericMessages([message])).toEqual([
{
role: "user",
content: [
{
type: "file",
data: "UklGRg==",
mediaType: `audio/${format}`,
},
],
},
]);
},
);
it.each([
"https://example.com/recording",
"data:audio/FORMAT;base64,UklGRg==",
])("preserves an audio URL: %s", (value) => {
const data = value.replace("FORMAT", format);
expect(
toGenericMessages([
{
role: "user",
content: [{ type: "audio", audio: { data, format } }],
},
]),
).toEqual([
{
role: "user",
content: [
{
type: "file",
data: new URL(data),
mediaType: `audio/${format}`,
},
],
},
]);
});
it.each([
{
name: "a mismatched data URL",
input: "data:audio/mpeg;base64,QUJD",
expected: `data:audio/${format};base64,QUJD`,
isUrl: true,
},
{
name: "a matching data URL",
input: `data:audio/${format};base64,QUJD`,
expected: `data:audio/${format};base64,QUJD`,
isUrl: true,
},
{
name: "raw base64",
input: "QUJD",
expected: "QUJD",
isUrl: false,
},
{
name: "an HTTP URL",
input: "http://example.com/recording",
expected: "http://example.com/recording",
isUrl: true,
},
{
name: "an HTTPS URL",
input: "https://example.com/recording",
expected: "https://example.com/recording",
isUrl: true,
},
])("uses the typed format for $name", ({ input, expected, isUrl }) => {
expect(
toGenericMessages([
{
role: "user",
content: [{ type: "audio", audio: { data: input, format } }],
},
]),
).toEqual([
{
role: "user",
content: [
{
type: "file",
data: isUrl ? new URL(expected) : expected,
mediaType: `audio/${format}`,
},
],
},
]);
});
});
it.each([undefined, {}, { data: "UklGRg==" }, { format: "wav" }])(
"skips an audio part missing its payload or format: %j",
(audio) => {
expect(
toGenericMessages([
{
role: "user",
content: [
{ type: "text", text: "Keep this" },
{ type: "audio", audio },
],
},
] as never),
).toEqual([
{ role: "user", content: [{ type: "text", text: "Keep this" }] },
]);
},
);
it("converts text parts", () => {
const result = toGenericMessages([
{
role: "user",
content: [{ type: "text", text: "Hello!" }],
},
]);
expect(result).toEqual([
{ role: "user", content: [{ type: "text", text: "Hello!" }] },
]);
});
it("converts image parts with media type inference", () => {
const result = toGenericMessages([
{
role: "user",
content: [{ type: "image", image: "https://example.com/photo.jpg" }],
},
]);
expect(result).toEqual([
{
role: "user",
content: [
{
type: "file",
data: new URL("https://example.com/photo.jpg"),
mediaType: "image/jpeg",
},
],
},
]);
});
it("converts file parts", () => {
const result = toGenericMessages([
{
role: "user",
content: [
{
type: "file",
data: "https://example.com/doc.pdf",
mimeType: "application/pdf",
},
],
},
]);
expect(result).toEqual([
{
role: "user",
content: [
{
type: "file",
data: new URL("https://example.com/doc.pdf"),
mediaType: "application/pdf",
},
],
},
]);
});
it("preserves zero-byte file parts", () => {
const result = toGenericMessages([
{
role: "user",
content: [
{
type: "file",
data: "",
mimeType: "text/plain",
filename: "empty.txt",
},
],
},
]);
expect(result).toEqual([
{
role: "user",
content: [
{
type: "file",
data: "",
mediaType: "text/plain",
filename: "empty.txt",
},
],
},
]);
});
it.each([
{ label: "empty", mimeType: "" },
{ label: "missing", mimeType: undefined },
])("falls back when a file MIME type is $label", ({ mimeType }) => {
const result = toGenericMessages([
{
role: "user",
content: [
{
type: "file",
data: "https://cdn.example.com/untyped-file",
...(mimeType !== undefined && { mimeType }),
filename: "untyped-file",
},
],
},
]);
expect(result).toEqual([
{
role: "user",
content: [
{
type: "file",
data: new URL("https://cdn.example.com/untyped-file"),
mediaType: "application/octet-stream",
filename: "untyped-file",
},
],
},
]);
});
it("uses the data URL MIME type when the file MIME type is empty", () => {
const data = "data:text/plain;base64,";
const result = toGenericMessages([
{
role: "user",
content: [{ type: "file", data, mimeType: "" }],
},
]);
expect(result).toEqual([
{
role: "user",
content: [
{
type: "file",
data: new URL(data),
mediaType: "text/plain",
},
],
},
]);
});
it("does not infer a MIME type from a malformed data URL", () => {
const data = "data:text/plain";
const result = toGenericMessages([
{
role: "user",
content: [{ type: "file", data, mimeType: "" }],
},
]);
expect(result).toEqual([
{
role: "user",
content: [
{
type: "file",
data: new URL(data),
mediaType: "application/octet-stream",
},
],
},
]);
});
it("handles attachments", () => {
const result = toGenericMessages([
{
role: "user",
content: [{ type: "text", text: "See attached" }],
attachments: [
{
content: [
{
type: "file",
data: "https://example.com/file.txt",
mimeType: "text/plain",
},
],
},
],
},
]);
expect(result).toEqual([
{
role: "user",
content: [
{ type: "text", text: "See attached" },
{
type: "file",
data: new URL("https://example.com/file.txt"),
mediaType: "text/plain",
},
],
},
]);
});
it("uses an image attachment's media type for extensionless URLs", () => {
const result = toGenericMessages([
{
role: "user",
content: [],
attachments: [
{
contentType: "image/jpeg",
content: [
{ type: "image", image: "https://cdn.example.com/image/123" },
],
},
],
},
]);
expect(result).toEqual([
{
role: "user",
content: [
{
type: "file",
data: new URL("https://cdn.example.com/image/123"),
mediaType: "image/jpeg",
},
],
},
]);
});
it("keeps attachment parts when the message has no content", () => {
const result = toGenericMessages([
{
role: "user",
attachments: [{ content: [{ type: "text", text: "notes" }] }],
},
] as never);
expect(result).toEqual([
{ role: "user", content: [{ type: "text", text: "notes" }] },
]);
});
it("skips attachments without content and null attachments", () => {
const result = toGenericMessages([
{
role: "user",
content: [{ type: "text", text: "See attached" }],
attachments: [
{ id: "uploading" },
null,
{ content: [{ type: "text", text: "notes" }] },
],
},
] as never);
expect(result).toEqual([
{
role: "user",
content: [
{ type: "text", text: "See attached" },
{ type: "text", text: "notes" },
],
},
]);
});
it("lowercases a mixed-case attachment media type", () => {
const result = toGenericMessages([
{
role: "user",
content: [],
attachments: [
{
contentType: "Image/JPEG",
content: [
{ type: "image", image: "https://cdn.example.com/image/123" },
],
},
],
},
]);
expect(result[0]?.content[0]).toMatchObject({ mediaType: "image/jpeg" });
});
it("falls back past a wildcard attachment media type", () => {
const result = toGenericMessages([
{
role: "user",
content: [],
attachments: [
{
contentType: "image/*",
content: [
{ type: "image", image: "https://cdn.example.com/photo.webp" },
],
},
],
},
]);
expect(result[0]?.content[0]).toMatchObject({ mediaType: "image/webp" });
});
it("carries a file part filename through", () => {
const result = toGenericMessages([
{
role: "user",
content: [
{
type: "file",
data: "https://cdn.example.com/a.pdf",
mimeType: "application/pdf",
filename: "invoice.pdf",
},
],
},
] as never);
expect(result).toEqual([
{
role: "user",
content: [
{
type: "file",
data: new URL("https://cdn.example.com/a.pdf"),
mediaType: "application/pdf",
filename: "invoice.pdf",
},
],
},
]);
});
it("omits filename when the part has none", () => {
const result = toGenericMessages([
{
role: "user",
content: [
{
type: "file",
data: "https://cdn.example.com/a.pdf",
mimeType: "application/pdf",
},
],
},
] as never);
const part = (result[0] as { content: unknown[] }).content[0];
expect(part).not.toHaveProperty("filename");
});
it("filters invalid parts", () => {
const result = toGenericMessages([
{
role: "user",
content: [
{ type: "text", text: "Valid" },
{ type: "image" }, // missing image property
{ type: "file", data: null } as never,
{ type: "unknown" },
],
},
]);
expect(result).toEqual([
{ role: "user", content: [{ type: "text", text: "Valid" }] },
]);
});
it("skips null parts in the message and its attachments", () => {
const result = toGenericMessages([
{
role: "user",
content: [null, { type: "text", text: "Hi" }],
attachments: [{ content: [null, { type: "text", text: "notes" }] }],
},
] as never);
expect(result).toEqual([
{
role: "user",
content: [
{ type: "text", text: "Hi" },
{ type: "text", text: "notes" },
],
},
]);
});
it("handles data URL as URL object", () => {
const dataUrl =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==";
const result = toGenericMessages([
{
role: "user",
content: [{ type: "image", image: dataUrl }],
},
]);
expect(result).toHaveLength(1);
expect(result[0]!.role).toBe("user");
const content = (result[0] as { content: unknown[] }).content;
expect(content[0]).toMatchObject({
type: "file",
mediaType: "image/png",
});
expect((content[0] as { data: unknown }).data).toBeInstanceOf(URL);
});
it("infers a lowercase media type from an uppercase-scheme data URL", () => {
const dataUrl =
"DATA:IMAGE/PNG;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==";
const result = toGenericMessages([
{
role: "user",
content: [{ type: "image", image: dataUrl }],
},
]);
const content = (result[0] as { content: unknown[] }).content;
expect(content[0]).toMatchObject({
type: "file",
mediaType: "image/png",
});
});
it("handles relative/invalid URL as string", () => {
const result = toGenericMessages([
{
role: "user",
content: [
{
type: "file",
data: "/relative/path/file.txt",
mimeType: "text/plain",
},
],
},
]);
expect(result).toEqual([
{
role: "user",
content: [
{
type: "file",
data: "/relative/path/file.txt",
mediaType: "text/plain",
},
],
},
]);
});
it("produces empty result when all parts are invalid", () => {
const result = toGenericMessages([
{
role: "user",
content: [{ type: "unknown" }],
},
]);
expect(result).toEqual([]);
});
});
describe("assistant messages", () => {
it("converts text parts", () => {
const result = toGenericMessages([
{
role: "assistant",
content: [{ type: "text", text: "Hello!" }],
},
]);
expect(result).toEqual([
{ role: "assistant", content: [{ type: "text", text: "Hello!" }] },
]);
});
it("converts generated file parts", () => {
const result = toGenericMessages([
{
role: "assistant",
content: [
{
type: "file",
data: "iVBORw0KGgo=",
mimeType: "image/png",
filename: "generated.png",
},
],
},
]);
expect(result).toEqual([
{
role: "assistant",
content: [
{
type: "file",
data: "iVBORw0KGgo=",
mediaType: "image/png",
filename: "generated.png",
},
],
},
]);
});
it("converts generated image parts", () => {
const dataUrl = "data:image/webp;base64,UklGRg==";
const result = toGenericMessages([
{
role: "assistant",
content: [
{ type: "image", image: dataUrl, filename: "generated.webp" },
],
},
]);
expect(result).toHaveLength(1);
expect(result[0]).toMatchObject({
role: "assistant",
content: [
{
type: "file",
mediaType: "image/webp",
filename: "generated.webp",
},
],
});
const content = (result[0] as { content: GenericFilePart[] }).content;
expect(content[0]?.data).toBeInstanceOf(URL);
expect(String(content[0]?.data)).toBe(dataUrl);
});
it("places generated files after settled tool results", () => {
const result = toGenericMessages([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "create_image",
args: {},
result: { created: true },
},
{
type: "file",
data: "iVBORw0KGgo=",
mimeType: "image/png",
filename: "generated.png",
},
],
},
]);
expect(result.map((message) => message.role)).toEqual([
"assistant",
"tool",
"assistant",
]);
expect(result[2]).toEqual({
role: "assistant",
content: [
{
type: "file",
data: "iVBORw0KGgo=",
mediaType: "image/png",
filename: "generated.png",
},
],
});
});
it("skips null parts", () => {
const result = toGenericMessages([
{
role: "assistant",
content: [null, { type: "text", text: "Hello!" }, null],
},
] as never);
expect(result).toEqual([
{ role: "assistant", content: [{ type: "text", text: "Hello!" }] },
]);
});
it("closes out tool calls without results", () => {
const result = toGenericMessages([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "get_weather",
args: { city: "London" },
},
],
},
]);
expect(result).toEqual([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "get_weather",
args: { city: "London" },
},
],
},
{
role: "tool",
content: [
{
type: "tool-result",
toolCallId: "call_123",
toolName: "get_weather",
result: { error: "Tool call was not completed" },
isError: true,
},
],
},
]);
});
it("does not send a preliminary result to the model", () => {
const result = toGenericMessages([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "get_weather",
args: { city: "London" },
state: "call",
result: { temperature: 20 },
isPreliminary: true,
},
],
},
]);
expect(result.at(-1)).toEqual({
role: "tool",
content: [
{
type: "tool-result",
toolCallId: "call_123",
toolName: "get_weather",
result: { error: "Tool call was not completed" },
isError: true,
},
],
});
});
it.each([
["a pending approval", { approval: { id: "ap_1" } }],
[
"an approved call the host has not run yet",
{
approval: { id: "ap_1", approved: true },
},
],
["an interrupted call", { interrupt: { type: "human", payload: {} } }],
])("leaves %s untouched instead of failing it", (_label, extra) => {
const result = toGenericMessages([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "get_weather",
args: { city: "London" },
...extra,
},
],
},
]);
expect(result).toEqual([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "get_weather",
args: { city: "London" },
},
],
},
]);
});
it.each(["running", "requires-action"])(
"leaves a pending approval untouched while its message is %s",
(type) => {
const result = toGenericMessages([
{
role: "assistant",
status: { type },
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "get_weather",
args: { city: "London" },
approval: { id: "ap_1" },
},
],
},
]);
expect(result.map((message) => message.role)).toEqual(["assistant"]);
},
);
const awaitingHost = [
["a pending approval", { approval: { id: "ap_1" } }],
[
"an approved call the host never ran",
{ approval: { id: "ap_1", approved: true } },
],
["an interrupted call", { interrupt: { type: "human", payload: {} } }],
] as const;
const closedOut = [
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "get_weather",
args: { city: "London" },
},
],
},
{
role: "tool",
content: [
{
type: "tool-result",
toolCallId: "call_123",
toolName: "get_weather",
result: { error: "Tool call was not completed" },
isError: true,
},
],
},
];
it.each(awaitingHost)(
"closes out %s once its message has settled",
(_label, extra) => {
const result = toGenericMessages([
{
role: "assistant",
status: { type: "incomplete" },
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "get_weather",
args: { city: "London" },
...extra,
},
],
},
]);
expect(result).toEqual(closedOut);
},
);
it.each(awaitingHost)(
"closes out %s in a message a later message follows",
(_label, extra) => {
const result = toGenericMessages([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "get_weather",
args: { city: "London" },
...extra,
},
],
},
{ role: "user", content: [{ type: "text", text: "never mind" }] },
]);
expect(result).toEqual([
...closedOut,
{ role: "user", content: [{ type: "text", text: "never mind" }] },
]);
},
);
it.each([
["a cancelled approval", { id: "ap_1", resolution: "cancelled" }],
["a denied approval carrying no result", { id: "ap_1", approved: false }],
])("closes out %s", (_label, approval) => {
const result = toGenericMessages([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "get_weather",
args: { city: "London" },
approval,
},
],
},
]);
expect(result).toEqual([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "get_weather",
args: { city: "London" },
},
],
},
{
role: "tool",
content: [
{
type: "tool-result",
toolCallId: "call_123",
toolName: "get_weather",
result: { error: "Tool call was not completed" },
isError: true,
},
],
},
]);
});
it("treats a settled tool call carrying no result as completed", () => {
const result = toGenericMessages([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "void_tool",
args: {},
state: "result",
},
],
},
]);
expect(result).toEqual([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "void_tool",
args: {},
},
],
},
{
role: "tool",
content: [
{
type: "tool-result",
toolCallId: "call_123",
toolName: "void_tool",
result: "<no result>",
},
],
},
]);
});
it("closes out a tool call left unresolved before a later message", () => {
const result = toGenericMessages([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "get_weather",
args: { city: "London" },
},
],
},
{
role: "user",
content: [{ type: "text", text: "never mind" }],
},
]);
expect(result).toEqual([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "get_weather",
args: { city: "London" },
},
],
},
{
role: "tool",
content: [
{
type: "tool-result",
toolCallId: "call_123",
toolName: "get_weather",
result: { error: "Tool call was not completed" },
isError: true,
},
],
},
{
role: "user",
content: [{ type: "text", text: "never mind" }],
},
]);
});
it("converts tool calls with results (creates tool message)", () => {
const result = toGenericMessages([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "get_weather",
args: { city: "London" },
result: { temperature: 20, unit: "celsius" },
},
],
},
]);
expect(result).toEqual([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "get_weather",
args: { city: "London" },
},
],
},
{
role: "tool",
content: [
{
type: "tool-result",
toolCallId: "call_123",
toolName: "get_weather",
result: { temperature: 20, unit: "celsius" },
},
],
},
]);
});
it("includes isError in tool result when present", () => {
const result = toGenericMessages([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "get_weather",
args: { city: "London" },
result: "Error: City not found",
isError: true,
},
],
},
]);
expect(result).toEqual([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "get_weather",
args: { city: "London" },
},
],
},
{
role: "tool",
content: [
{
type: "tool-result",
toolCallId: "call_123",
toolName: "get_weather",
result: "Error: City not found",
isError: true,
},
],
},
]);
});
it("handles text-tool interleaving (flush on text boundary)", () => {
const result = toGenericMessages([
{
role: "assistant",
content: [
{ type: "text", text: "Let me check the weather." },
{
type: "tool-call",
toolCallId: "call_1",
toolName: "get_weather",
args: { city: "London" },
result: { temp: 20 },
},
{ type: "text", text: "The weather is nice." },
],
},
]);
// The flush happens BEFORE adding text when there are pending tool results
// So first assistant message contains both initial text and tool call
expect(result).toEqual([
{
role: "assistant",
content: [
{ type: "text", text: "Let me check the weather." },
{
type: "tool-call",
toolCallId: "call_1",
toolName: "get_weather",
args: { city: "London" },
},
],
},
{
role: "tool",
content: [
{
type: "tool-result",
toolCallId: "call_1",
toolName: "get_weather",
result: { temp: 20 },
},
],
},
{
role: "assistant",
content: [{ type: "text", text: "The weather is nice." }],
},
]);
});
it("handles missing toolCallId", () => {
const result = toGenericMessages([
{
role: "assistant",
content: [
{
type: "tool-call",
toolName: "get_weather",
args: { city: "London" },
},
],
},
]);
// Tool call should be skipped due to missing toolCallId
expect(result).toEqual([]);
});
it("handles missing toolName", () => {
const result = toGenericMessages([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
args: { city: "London" },
},
],
},
]);
// Tool call should be skipped due to missing toolName
expect(result).toEqual([]);
});
it("defaults args to empty object when missing", () => {
const result = toGenericMessages([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "no_args_tool",
},
],
},
]);
expect(result).toEqual([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "no_args_tool",
args: {},
},
],
},
{
role: "tool",
content: [
{
type: "tool-result",
toolCallId: "call_123",
toolName: "no_args_tool",
result: { error: "Tool call was not completed" },
isError: true,
},
],
},
]);
});
it("handles multiple tool calls with mixed results", () => {
const result = toGenericMessages([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_1",
toolName: "tool_a",
args: {},
result: "result_a",
},
{
type: "tool-call",
toolCallId: "call_2",
toolName: "tool_b",
args: {},
// no result
},
{
type: "tool-call",
toolCallId: "call_3",
toolName: "tool_c",
args: {},
result: "result_c",
},
],
},
]);
expect(result).toEqual([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_1",
toolName: "tool_a",
args: {},
},
{
type: "tool-call",
toolCallId: "call_2",
toolName: "tool_b",
args: {},
},
{
type: "tool-call",
toolCallId: "call_3",
toolName: "tool_c",
args: {},
},
],
},
{
role: "tool",
content: [
{
type: "tool-result",
toolCallId: "call_1",
toolName: "tool_a",
result: "result_a",
},
{
type: "tool-result",
toolCallId: "call_2",
toolName: "tool_b",
result: { error: "Tool call was not completed" },
isError: true,
},
{
type: "tool-result",
toolCallId: "call_3",
toolName: "tool_c",
result: "result_c",
},
],
},
]);
});
});
describe("inferImageMediaType", () => {
const testMediaType = (url: string, expectedType: string) => {
const result = toGenericMessages([
{
role: "user",
content: [{ type: "image", image: url }],
},
]);
const content = (result[0] as { content: { mediaType: string }[] })
.content;
expect(content[0]!.mediaType).toBe(expectedType);
};
it("maps known extensions to their media types", () => {
testMediaType("https://example.com/photo.jpg", "image/jpeg");
testMediaType("https://example.com/photo.jpeg", "image/jpeg");
testMediaType("https://example.com/photo.png", "image/png");
testMediaType("https://example.com/photo.gif", "image/gif");
testMediaType("https://example.com/photo.webp", "image/webp");
testMediaType("https://example.com/icon.svg", "image/svg+xml");
testMediaType("https://example.com/photo.avif", "image/avif");
testMediaType("https://example.com/photo.bmp", "image/bmp");
testMediaType("https://example.com/favicon.ico", "image/x-icon");
testMediaType("https://example.com/photo.tiff", "image/tiff");
testMediaType("https://example.com/photo.tif", "image/tiff");
});
it("defaults to image/png for unknown or missing extensions", () => {
testMediaType("https://example.com/photo.unknown", "image/png");
testMediaType("https://example.com/photo", "image/png");
});
it("ignores query params and casing in the extension", () => {
testMediaType(
"https://example.com/photo.jpg?size=large&quality=high",
"image/jpeg",
);
testMediaType("https://example.com/photo.JPG", "image/jpeg");
});
});
describe("toUrlOrString", () => {
it("converts valid URL to URL object", () => {
const result = toGenericMessages([
{
role: "user",
content: [{ type: "image", image: "https://example.com/photo.png" }],
},
]);
const content = (result[0] as { content: { data: unknown }[] }).content;
expect(content[0]!.data).toBeInstanceOf(URL);
expect((content[0]!.data as URL).href).toBe(
"https://example.com/photo.png",
);
});
it("converts data URL to URL object", () => {
const dataUrl = "data:image/png;base64,abc123";
const result = toGenericMessages([
{
role: "user",
content: [{ type: "image", image: dataUrl }],
},
]);
const content = (result[0] as { content: { data: unknown }[] }).content;
expect(content[0]!.data).toBeInstanceOf(URL);
});
it("keeps relative path as string", () => {
const result = toGenericMessages([
{
role: "user",
content: [
{ type: "file", data: "/path/to/file.txt", mimeType: "text/plain" },
],
},
]);
const content = (result[0] as { content: { data: unknown }[] }).content;
expect(content[0]!.data).toBe("/path/to/file.txt");
});
it("keeps invalid URL as string", () => {
const result = toGenericMessages([
{
role: "user",
content: [
{ type: "file", data: "not a valid url", mimeType: "text/plain" },
],
},
]);
const content = (result[0] as { content: { data: unknown }[] }).content;
expect(content[0]!.data).toBe("not a valid url");
});
});
describe("multiple messages", () => {
it("skips null and undefined messages", () => {
const result = toGenericMessages([
null,
{ role: "user", content: [{ type: "text", text: "Hi" }] },
undefined,
] as never);
expect(result).toEqual([
{ role: "user", content: [{ type: "text", text: "Hi" }] },
]);
});
it("keeps a pending approval in the last message when a null entry trails it", () => {
const result = toGenericMessages([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "get_weather",
args: { city: "London" },
approval: { id: "ap_1" },
},
],
},
null,
] as never);
expect(result).toEqual([
{
role: "assistant",
content: [
{
type: "tool-call",
toolCallId: "call_123",
toolName: "get_weather",
args: { city: "London" },
},
],
},
]);
});
it("converts a full conversation", () => {
const result = toGenericMessages([
{
role: "system",
content: [{ type: "text", text: "You are helpful." }],
},
{
role: "user",
content: [{ type: "text", text: "What is 2+2?" }],
},
{
role: "assistant",
content: [{ type: "text", text: "2+2 equals 4." }],
},
{
role: "user",
content: [{ type: "text", text: "Thanks!" }],
},
]);
expect(result).toEqual([
{ role: "system", content: "You are helpful." },
{ role: "user", content: [{ type: "text", text: "What is 2+2?" }] },
{
role: "assistant",
content: [{ type: "text", text: "2+2 equals 4." }],
},
{ role: "user", content: [{ type: "text", text: "Thanks!" }] },
]);
});
it("skips messages without content and keeps the rest", () => {
const result = toGenericMessages([
{ role: "system" },
{ role: "user" },
{ role: "assistant" },
{ role: "user", content: [{ type: "text", text: "Thanks!" }] },
] as never);
expect(result).toEqual([
{ role: "user", content: [{ type: "text", text: "Thanks!" }] },
]);
});
it("handles empty messages array", () => {
const result = toGenericMessages([]);
expect(result).toEqual([]);
});
});
});