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; };}
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:
Copy
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!'};
Most users should rely on createTurnHandler to manage message creation automatically rather than creating messages manually.
Convert a ConversationMessage to the AI SDK’s UIMessage format for rendering in user interfaces:
Copy
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:
Copy
function convertToUIMessage( message: ConversationMessage.Type): Ai.UIMessage
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;});
This prevents accidental mixing of message IDs with other string types:
Copy
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 channelsconst msg = ConversationMessage.fromModelMessage({ role: 'user', content: 'Hi' });processMessage(msg.id);
Store complete messages including metadata. Don’t discard id and createdAt as they’re needed for message ordering and deduplication.
Handle multi-part content
Always check if content is a string or array:
Copy
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 } }}