---
title: toAiMessages
description: Convert Chat SDK messages to AI SDK conversation format.
docs_index: /llms.txt
lastUpdated: 2026-11-12
type: reference
related:
  - /docs/ai
  - /docs/ai/ai-sdk-tools
  - /docs/ai/types
  - /docs/streaming
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Convert an array of [`Message`](/docs/api/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](/docs/ai/tanstack-ai).

```typescript
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`](/docs/ai) alongside
> [`createChatTools`](/docs/ai/ai-sdk-tools) and the rest of the AI
> utilities.

## Usage

```typescript title="lib/bot.ts" lineNumbers
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

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

### Parameters

| Property   | Type                  | Description                                                                                                           |
| ---------- | --------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `messages` | `Message[]`           | Array of Chat SDK Message objects. Works with FetchResult.messages, thread.recentMessages, or any collected iterable. |
| `options`  | `ToAiMessagesOptions` | Optional configuration.                                                                                               |

### Options

| Property                  | Type                                                                                         | Description                                                                                 |
| ------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `includeNames`            | `boolean`                                                                                    | 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) => void`                                         | 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

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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:

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

### Handling unsupported attachments

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

## Supported attachment types

| Type    | MIME types                                                                                                                                  | Included as                                  |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
| `image` | Any image MIME type                                                                                                                         | `FilePart` with base64 data                  |
| `file`  | `text/*`, `application/json`, `application/xml`, `application/javascript`, `application/typescript`, `application/yaml`, `application/toml` | `FilePart` with base64 data                  |
| `video` | Any                                                                                                                                         | Skipped (triggers `onUnsupportedAttachment`) |
| `audio` | Any                                                                                                                                         | Skipped (triggers `onUnsupportedAttachment`) |
| `file`  | Other (e.g. `application/pdf`)                                                                                                              | Silently skipped                             |

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

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)