@assistant-ui/react
Version:
Typescript/React library for AI Chat
150 lines (138 loc) • 4.42 kB
text/typescript
import { LanguageModelV1Message } from "@ai-sdk/provider";
import { CoreMessage, ToolCallContentPart } from "../../../types";
import { Writable } from "stream";
import { ReadonlyJSONObject } from "../../../utils/json/json-value";
type fromLanguageModelMessagesOptions = {
mergeSteps?: boolean;
};
export const fromLanguageModelMessages = (
lm: LanguageModelV1Message[],
{ mergeSteps }: fromLanguageModelMessagesOptions = {},
): CoreMessage[] => {
const messages: CoreMessage[] = [];
for (const lmMessage of lm) {
const role = lmMessage.role;
switch (role) {
case "system": {
messages.push({
role: "system",
content: [
{
type: "text",
text: lmMessage.content,
},
],
});
break;
}
case "user": {
messages.push({
role: "user",
content: lmMessage.content.map((part) => {
const type = part.type;
switch (type) {
case "text": {
return {
type: "text",
text: part.text,
};
}
case "image": {
if (part.image instanceof URL) {
return {
type: "image",
image: part.image.href,
};
}
throw new Error("Only images with URL data are supported");
}
case "file": {
if (part.data instanceof URL) {
return {
type: "file",
data: part.data.href,
mimeType: part.mimeType,
};
}
throw new Error("Only files with URL data are supported");
}
default: {
const unhandledType: never = type;
throw new Error(`Unknown content part type: ${unhandledType}`);
}
}
}),
});
break;
}
case "assistant": {
const newContent = lmMessage.content
.map((part) => {
if (part.type === "tool-call") {
return {
type: "tool-call",
toolCallId: part.toolCallId,
toolName: part.toolName,
argsText: JSON.stringify(part.args),
args: part.args as ReadonlyJSONObject,
} satisfies ToolCallContentPart;
}
// TODO handle these
if (
part.type === "redacted-reasoning" ||
part.type === "file" ||
part.type === "reasoning"
) {
return null;
}
return part;
})
.filter((p) => !!p);
if (mergeSteps) {
const previousMessage = messages[messages.length - 1];
if (previousMessage?.role === "assistant") {
previousMessage.content = [
...previousMessage.content,
...newContent,
];
break;
}
}
messages.push({
role: "assistant",
content: newContent,
});
break;
}
case "tool": {
const previousMessage = messages[messages.length - 1];
if (previousMessage?.role !== "assistant")
throw new Error(
"A tool message must be preceded by an assistant message.",
);
for (const tool of lmMessage.content) {
const toolCall = previousMessage.content.find(
(c): c is ToolCallContentPart =>
c.type === "tool-call" && c.toolCallId === tool.toolCallId,
);
if (!toolCall)
throw new Error("Received tool result for an unknown tool call.");
if (toolCall.toolName !== tool.toolName)
throw new Error("Tool call name mismatch.");
type Writable<T> = { -readonly [P in keyof T]: T[P] };
const writable = toolCall as Writable<ToolCallContentPart>;
writable.result = tool.result;
if (tool.isError) {
writable.isError = true;
}
}
break;
}
default: {
const unhandledRole: never = role;
throw new Error(`Unknown message role: ${unhandledRole}`);
}
}
}
return messages;
};