> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/fdarian/ff/llms.txt
> Use this file to discover all available pages before exploring further.

# Messages

> Message types and utility functions for AI conversations

## Overview

The message types in ff-ai extend the Vercel AI SDK's `ModelMessage` with additional metadata for persistence and identification.

## ConversationMessage

The `ConversationMessage` namespace provides types and utilities for working with conversation messages.

### Type Definition

```typescript theme={null}
import type * as Ai from 'ai';
import type * as v from 'valibot';

export namespace ConversationMessage {
  export type Id = string & v.Brand<'ff-ai/ConversationMessageId'>;

  export type Type = Ai.ModelMessage & {
    id: Id;
    createdAt: Date;
  };
}
```

### Message Structure

A `ConversationMessage` includes:

<ParamField path="id" type="ConversationMessage.Id" required>
  UUID v7 identifier for the message. Generated automatically using the `uuid` package.
</ParamField>

<ParamField path="createdAt" type="Date" required>
  Timestamp when the message was created. Used for ordering messages chronologically.
</ParamField>

<ParamField path="role" type="'user' | 'assistant' | 'tool' | 'system'" required>
  The role of the message sender (inherited from AI SDK)
</ParamField>

<ParamField path="content" type="string | ContentPart[]" required>
  The message content. Can be a simple string or an array of content parts (text, images, tool calls, etc.)
</ParamField>

## Creating Messages

Messages are typically created automatically when using `createTurnHandler`. The turn handler's `saveUserMessage` and `onStep` methods handle message creation internally.

If you need to manually create a message (e.g., for custom storage implementations), construct it with the required fields:

```typescript theme={null}
import type { ConversationMessage } from 'ff-ai';
import { v7 as createUuid } from 'uuid';

const message: ConversationMessage = {
  id: createUuid() as any, // UUID v7
  createdAt: new Date(),
  role: 'user',
  content: 'Hello, world!'
};
```

<Note>
  Most users should rely on `createTurnHandler` to manage message creation automatically rather than creating messages manually.
</Note>

## Converting Messages

### convertToUIMessage

Convert a `ConversationMessage` to the AI SDK's `UIMessage` format for rendering in user interfaces:

```typescript theme={null}
import { convertToUIMessage } from 'ff-ai';

const conversationMessage = {
  id: '01940b3e-...',
  createdAt: new Date(),
  role: 'assistant',
  content: 'Hello! How can I help you?'
};

const uiMessage = convertToUIMessage(conversationMessage);

// Result:
// {
//   id: '01940b3e-...',
//   role: 'assistant',
//   parts: [
//     { type: 'text', text: 'Hello! How can I help you?' }
//   ]
// }
```

**Signature:**

```typescript theme={null}
function convertToUIMessage(
  message: ConversationMessage.Type
): Ai.UIMessage
```

**Behavior:**

* Extracts text parts from the message content
* Maps `tool` role to `assistant` (UI convention)
* Preserves message ID for React keys
* Returns an array of `UIMessagePart` objects

## Message Roles

Messages can have different roles based on their source:

<AccordionGroup>
  <Accordion title="user">
    Messages sent by the end user. These are the primary input to the AI model.

    ```typescript theme={null}
    {
      role: 'user',
      content: 'What is the weather today?'
    }
    ```
  </Accordion>

  <Accordion title="assistant">
    Messages generated by the AI model. Can include text responses or tool calls.

    ```typescript theme={null}
    {
      role: 'assistant',
      content: 'The weather today is sunny with a high of 75°F.'
    }
    ```
  </Accordion>

  <Accordion title="tool">
    Results from tool executions. These provide context back to the model.

    ```typescript theme={null}
    {
      role: 'tool',
      content: [
        {
          type: 'tool-result',
          toolCallId: 'call_123',
          toolName: 'getWeather',
          result: { temp: 75, condition: 'sunny' }
        }
      ]
    }
    ```
  </Accordion>

  <Accordion title="system">
    System instructions that guide the model's behavior. Not stored in conversation history by default.

    ```typescript theme={null}
    {
      role: 'system',
      content: 'You are a helpful weather assistant.'
    }
    ```
  </Accordion>
</AccordionGroup>

## Content Types

Messages can contain different types of content:

### Text Content

Simple string content:

```typescript theme={null}
const message = {
  role: 'user',
  content: 'Hello!'
};
```

### Structured Content

Array of content parts for complex messages:

```typescript theme={null}
const message = {
  role: 'user',
  content: [
    {
      type: 'text',
      text: 'What is in this image?'
    },
    {
      type: 'image',
      image: 'data:image/png;base64,...'
    }
  ]
};
```

### Tool Calls

Assistant messages requesting tool execution:

```typescript theme={null}
const message = {
  role: 'assistant',
  content: [
    {
      type: 'tool-call',
      toolCallId: 'call_123',
      toolName: 'getWeather',
      args: { location: 'San Francisco' }
    }
  ]
};
```

## Working with Messages

### Complete Example

```typescript theme={null}
import { ConversationMessage, convertToUIMessage } from 'ff-ai';
import { Effect } from 'effect';

const program = Effect.gen(function* () {
  // Create a user message
  const userMessage = ConversationMessage.fromModelMessage({
    role: 'user',
    content: 'Tell me a joke'
  });

  console.log('Message ID:', userMessage.id);
  console.log('Created at:', userMessage.createdAt);

  // Create an assistant response
  const assistantMessage = ConversationMessage.fromModelMessage({
    role: 'assistant',
    content: 'Why did the chicken cross the road? To get to the other side!'
  });

  // Convert for UI rendering
  const uiMessages = [
    convertToUIMessage(userMessage),
    convertToUIMessage(assistantMessage)
  ];

  return uiMessages;
});
```

### Multi-part Messages

```typescript theme={null}
import { ConversationMessage } from 'ff-ai';

const message = ConversationMessage.fromModelMessage({
  role: 'user',
  content: [
    { type: 'text', text: 'Analyze this data:' },
    { type: 'text', text: 'Sales: $1M, Growth: 15%' }
  ]
});

console.log('Content parts:', message.content.length);
```

### Tool Result Messages

```typescript theme={null}
import { ConversationMessage } from 'ff-ai';

const toolResult = ConversationMessage.fromModelMessage({
  role: 'tool',
  content: [
    {
      type: 'tool-result',
      toolCallId: 'call_abc123',
      toolName: 'getWeather',
      result: {
        location: 'San Francisco',
        temperature: 68,
        condition: 'Partly Cloudy'
      }
    }
  ]
});
```

## Message IDs

Message IDs use UUID v7, which provides:

* **Time-ordered**: IDs are sortable by creation time
* **Unique**: Globally unique across distributed systems
* **Performance**: Efficient for database indexing

```typescript theme={null}
import { v7 as createUuid } from 'uuid';

const id = createUuid();
// '01940b3e-5c5a-7b9c-9c3e-f1a2b3c4d5e6'
```

The first part of the UUID encodes the timestamp, making it naturally ordered.

## Type Safety

The `ConversationMessage.Id` type is branded using Valibot:

```typescript theme={null}
const idSchema = v.pipe(
  v.string(),
  v.uuid(),
  v.brand('ff-ai/ConversationMessageId'),
);
```

This prevents accidental mixing of message IDs with other string types:

```typescript theme={null}
function processMessage(id: ConversationMessage.Id) {
  // TypeScript ensures `id` is a valid message ID
}

// Error: Type 'string' is not assignable to type 'ConversationMessage.Id'
processMessage('not-a-valid-id');

// OK: Created through proper channels
const msg = ConversationMessage.fromModelMessage({ role: 'user', content: 'Hi' });
processMessage(msg.id);
```

## Best Practices

<AccordionGroup>
  <Accordion title="Always use fromModelMessage">
    Don't manually construct `ConversationMessage` objects. Use `fromModelMessage` to ensure IDs and timestamps are properly generated:

    ```typescript theme={null}
    // Good
    const message = ConversationMessage.fromModelMessage({
      role: 'user',
      content: 'Hello'
    });

    // Bad - missing id and createdAt
    const message = {
      role: 'user',
      content: 'Hello'
    };
    ```
  </Accordion>

  <Accordion title="Use convertToUIMessage for rendering">
    When displaying messages in a UI, always convert them first:

    ```typescript theme={null}
    const uiMessages = messages.map(convertToUIMessage);
    return <MessageList messages={uiMessages} />;
    ```
  </Accordion>

  <Accordion title="Preserve message history">
    Store complete messages including metadata. Don't discard `id` and `createdAt` as they're needed for message ordering and deduplication.
  </Accordion>

  <Accordion title="Handle multi-part content">
    Always check if `content` is a string or array:

    ```typescript theme={null}
    if (typeof message.content === 'string') {
      // Handle simple text
    } else {
      // Handle content parts
      for (const part of message.content) {
        if (part.type === 'text') {
          // Handle text part
        }
      }
    }
    ```
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Turn Handler" icon="arrows-rotate" href="/ff-ai/turn-handler">
    Automatically manage message creation and persistence
  </Card>

  <Card title="Conversation Store" icon="database" href="/ff-ai/conversation-store">
    Store and retrieve conversation messages
  </Card>

  <Card title="Examples" icon="code" href="/ff-ai/examples">
    See complete examples with messages
  </Card>
</CardGroup>
