Visitar URL original
toAiMessages | Chat SDK

toAiMessages

Convert an array of Message objects into the { role, content }[] format expected by the AI SDK. The output is structurally compatible with AI SDK's ModelMessage[].

Using TanStack AI? toTanStackMessages in chat/ai/tanstack converts the same history into the ModelMessage[] shape that chat() expects. See TanStack AI.

import { toAiMessages } from "chat/ai";

toAiMessages is also re-exported from the main chat entrypoint for backwards compatibility and marked @deprecated in JSDoc. New code should import it from chat/ai alongside createChatTools and the rest of the AI utilities.

Usage

lib/bot.ts
import { toAiMessages } from "chat/ai";

bot.onSubscribedMessage(async (thread, message) => {
  const result = await thread.adapter.fetchMessages(thread.id, { limit: 20 });
  const history = await toAiMessages(result.messages);
  const response = await agent.stream({ prompt: history });
  await thread.post(response.fullStream);
});

Signature

function toAiMessages(
  messages: Message[],
  options?: ToAiMessagesOptions
): Promise<AiMessage[]>

Parameters

PropTypeDefaultDescription
messages?Message[]
Array of Chat SDK Message objects. Works with FetchResult.messages, thread.recentMessages, or any collected iterable.
options?ToAiMessagesOptions{}
Optional configuration.

Options

PropTypeDefaultDescription
includeNames?booleanfalse
Prefix user messages with [username]: for multi-user context.
transformMessage?(aiMessage: AiMessage, source: Message) => AiMessage | null | Promise<AiMessage | null>
Transform or filter each message after default processing. Return null to skip the message.
onUnsupportedAttachment?(attachment: Attachment, message: Message) => voidconsole.warn
Called when an attachment type is not supported (video, audio).

Returns

Promise<AiMessage[]>: an array of messages with role and content fields, assignable to AI SDK's ModelMessage[].

Behavior

  • Role mapping: messages with author.isMe === true become "assistant", and all others become "user".
  • Filtering: a message with no text is kept when it has links or attachments the converter can include, meaning images and text files with a working fetchData(). Messages with no text whose only attachments are unsupported (video, audio, other file types, or missing fetchData()) are removed.
  • Sorting: messages are sorted oldest first by metadata.dateSent.
  • Links: link metadata (URL, title, description, site name) is appended to the message content. Third-party title, description, and site-name fields are normalized, length-limited, escaped, and enclosed in an explicit untrusted-content fence. Embedded message links are labeled [Embedded message: ...].
  • Attachments: images and text files (JSON, XML, YAML, and similar) are fetched with fetchData() and included as multipart content. When the message has no text, the content array contains only attachment parts, with no leading text part. Video and audio attachments trigger onUnsupportedAttachment.

Return types

type AiMessage = AiUserMessage | AiAssistantMessage;

interface AiUserMessage {
  role: "user";
  content: string | AiMessagePart[];
}

interface AiAssistantMessage {
  role: "assistant";
  content: string;
}

User messages have multipart content when attachments are present:

type AiMessagePart = AiTextPart | AiImagePart | AiFilePart;

interface AiTextPart {
  type: "text";
  text: string;
}

interface AiImagePart {
  type: "image";
  image: DataContent | URL;
  mediaType?: string;
}

interface AiFilePart {
  type: "file";
  data: DataContent | URL;
  filename?: string;
  mediaType: string;
}

Examples

Multi-user context

Prefix each user message with the sender's username so the model can tell speakers apart:

const history = await toAiMessages(result.messages, { includeNames: true });
// [{ role: "user", content: "[alice]: Hello" },
//  { role: "assistant", content: "Hi there!" },
//  { role: "user", content: "[bob]: Thanks" }]

Transforming messages

Replace raw user IDs with readable names:

const history = await toAiMessages(result.messages, {
  transformMessage: (aiMessage) => {
    if (typeof aiMessage.content === "string") {
      return {
        ...aiMessage,
        content: aiMessage.content.replace(/<@U123>/g, "@VercelBot"),
      };
    }
    return aiMessage;
  },
});

Filtering messages

Skip messages from a specific user:

const history = await toAiMessages(result.messages, {
  transformMessage: (aiMessage, source) => {
    if (source.author.userId === "U_NOISY_BOT") return null;
    return aiMessage;
  },
});

Handling unsupported attachments

const history = await toAiMessages(result.messages, {
  onUnsupportedAttachment: (attachment, message) => {
    logger.warn(`Skipped ${attachment.type} attachment in message ${message.id}`);
  },
});

Supported attachment types

TypeMIME typesIncluded as
imageAny image MIME typeFilePart with base64 data
filetext/*, application/json, application/xml, application/javascript, application/typescript, application/yaml, application/tomlFilePart with base64 data
videoAnySkipped (triggers onUnsupportedAttachment)
audioAnySkipped (triggers onUnsupportedAttachment)
fileOther (e.g. application/pdf)Silently skipped

Attachments require fetchData() to be available on the attachment object. Attachments without fetchData() are silently skipped.

Read more