Message¶
Message represents individual messages in a conversation between users, AI assistants, tools, and the system. It provides type-safe message handling with discriminated unions for different message roles.
Overview¶
Messages are the building blocks of conversations, categorized by role:
- user: Messages from the end user
- assistant: Responses from the AI agent
- tool: Results from tool executions
- system: System-level instructions and context
Key Features¶
- ✅ Type-safe role discrimination
- ✅ Automatic timestamp tracking
- ✅ Required
tool_call_idfor tool messages - ✅ JSON serialization support
- ✅ Built-in validation
Constructor¶
Creates a new message with role-specific options.
Content block types¶
Messages for user and tool roles accept either plain text or an array of typed content blocks (MessageContent). Each block is one of:
/** Plain text unit shared by every provider. */
interface TextBlock {
type: "text";
text: string;
}
/** Image block — translated to each provider's wire format automatically. */
interface ImageBlock {
type: "image";
source:
| { type: "base64"; media_type: string; data: string }
| { type: "url"; url: string };
}
/** Audio block — supported by OpenAI (gpt-4o-audio); dropped by Claude. */
interface AudioBlock {
type: "audio";
source: { type: "base64"; media_type: string; data: string };
}
/** Document block (PDF, etc.) — supported by Claude; dropped by OpenAI. */
interface DocumentBlock {
type: "document";
source:
| { type: "base64"; media_type: string; data: string }
| { type: "url"; url: string }
| { type: "file"; file_id: string };
}
/** Union of all content block types. */
type ContentBlock = TextBlock | ImageBlock | AudioBlock | DocumentBlock;
/** Message content: plain text string or multimodal block array. */
type MessageContent = string | ContentBlock[];
MessageOptions¶
type MessageOptions =
| { role: "user"; content: MessageContent }
| { role: "assistant"; content: string | null; tool_calls?: ToolCall[];
thinking?: string; thinking_signature?: string }
| { role: "system"; content: string }
| { role: "tool"; content: MessageContent; tool_call_id: string };
TypeScript enforces that: - Tool messages must include tool_call_id - Assistant messages can have null content when they include tool_calls (function calling) - user and tool content accepts string | ContentBlock[] (multimodal) - system and assistant content is string | null (plain text only)
Examples¶
User Message:
import { Message } from '@arcaelas/agent';
const user_message = new Message({
role: "user",
content: "What's the weather like today?"
});
Assistant Message:
const assistant_message = new Message({
role: "assistant",
content: "It's sunny with a temperature of 24°C. Perfect weather for outdoor activities!"
});
Tool Message:
const tool_message = new Message({
role: "tool",
content: JSON.stringify({ temperature: 24, condition: "sunny" }),
tool_call_id: "weather_query_12345"
});
System Message:
const system_message = new Message({
role: "system",
content: "You are a helpful weather assistant. Provide accurate forecasts and suggestions."
});
Multimodal Message (text + image):
const vision_message = new Message({
role: "user",
content: [
{ type: "text", text: "What do you see in this image?" },
{
type: "image",
source: { type: "url", url: "https://example.com/photo.jpg" },
},
],
});
// Base64 image alternative
const base64_vision = new Message({
role: "user",
content: [
{ type: "text", text: "Describe this diagram." },
{
type: "image",
source: { type: "base64", media_type: "image/png", data: "iVBORw0KGgo..." },
},
],
});
Properties¶
role¶
The message role indicating who sent the message.
Type:
Example:
const message = new Message({ role: "user", content: "Hello" });
console.log(message.role); // "user"
content¶
The content of the message. For user and tool roles it can be plain text or an array of content blocks (multimodal). For assistant it is always string | null; null occurs when the assistant decides to call tools instead of generating text. For system it is always string.
Example:
// Plain text content
const message = new Message({
role: "assistant",
content: "I'm here to help you today!"
});
console.log(message.content); // "I'm here to help you today!"
// Assistant message with null content (during tool calling)
const tool_calling_message = new Message({
role: "assistant",
content: null,
tool_calls: [{ id: "call_1", type: "function", function: { name: "search", arguments: "{}" } }]
});
console.log(tool_calling_message.content); // null
// Multimodal content (user/tool roles)
const vision_message = new Message({
role: "user",
content: [
{ type: "text", text: "What is in this image?" },
{ type: "image", source: { type: "url", url: "https://example.com/img.png" } },
],
});
console.log(Array.isArray(vision_message.content)); // true
tool_call_id¶
Unique identifier for tool messages. Only defined when role === "tool".
Example:
const tool_msg = new Message({
role: "tool",
content: "Result data",
tool_call_id: "calculation_001"
});
console.log(tool_msg.tool_call_id); // "calculation_001"
const user_msg = new Message({ role: "user", content: "Hi" });
console.log(user_msg.tool_call_id); // undefined
thinking¶
The internal reasoning text generated by the model before responding (only populated on assistant messages when the provider exposes it, e.g. Claude extended thinking or reasoning models). undefined for all other roles.
Example:
const msg = new Message({
role: "assistant",
content: "The answer is 42.",
thinking: "Let me think step by step...",
thinking_signature: "sigABC123",
});
console.log(msg.thinking); // "Let me think step by step..."
console.log(msg.thinking_signature); // "sigABC123"
thinking_signature¶
Cryptographic signature of the thinking block (Anthropic-specific). Required by the Anthropic API to accept the reasoning block when it is reinjected in subsequent turns. undefined when not applicable.
timestamp¶
Automatic timestamp when the message was created.
Example:
const message = new Message({ role: "user", content: "Hello" });
console.log(message.timestamp); // Date object
console.log(message.timestamp.toISOString()); // "2025-10-12T10:30:45.123Z"
length¶
Returns the character length of the message content.
Example:
const message = new Message({ role: "user", content: "Hello world" });
console.log(message.length); // 11
Methods¶
toJSON()¶
toJSON(): {
role: MessageRole;
content: MessageContent | null;
tool_call_id?: string;
tool_calls?: ToolCall[];
thinking?: string;
thinking_signature?: string;
timestamp: string;
}
Serializes the message to JSON format. The timestamp is converted to ISO 8601 string. Optional fields (tool_call_id, tool_calls, thinking, thinking_signature) are only included when present.
Returns: Serializable representation used by JSON.stringify and by providers.
Example:
const message = new Message({
role: "assistant",
content: "Hello there!"
});
const json = JSON.stringify(message);
console.log(json);
// {
// "role": "assistant",
// "content": "Hello there!",
// "timestamp": "2025-10-12T10:30:45.123Z"
// }
// Tool message with tool_call_id
const tool_msg = new Message({
role: "tool",
content: "Data",
tool_call_id: "calc_123"
});
console.log(tool_msg.toJSON());
// {
// role: "tool",
// content: "Data",
// tool_call_id: "calc_123",
// timestamp: "..."
// }
// Assistant with thinking (Claude extended thinking / reasoning models)
const thinking_msg = new Message({
role: "assistant",
content: "The answer is 42.",
thinking: "I reasoned through this carefully...",
thinking_signature: "sigXYZ789",
});
console.log(thinking_msg.toJSON());
// {
// role: "assistant",
// content: "The answer is 42.",
// thinking: "I reasoned through this carefully...",
// thinking_signature: "sigXYZ789",
// timestamp: "..."
// }
toString()¶
Returns human-readable string representation. Long content is truncated to 50 characters.
Returns: String in format "Message(role) [tool_call_id]: content"
Example:
const message = new Message({
role: "user",
content: "What's the weather?"
});
console.log(message.toString());
// "Message(user): What's the weather?"
const tool_msg = new Message({
role: "tool",
content: "Result data here",
tool_call_id: "weather_001"
});
console.log(tool_msg.toString());
// "Message(tool) [weather_001]: Result data here"
const long_msg = new Message({
role: "assistant",
content: "This is a very long message that will be truncated when displayed as a string representation"
});
console.log(long_msg.toString());
// "Message(assistant): This is a very long message that will be trunca..."
Usage Patterns¶
Pattern: Conversation Building¶
Build conversation history:
const conversation: Message[] = [
new Message({
role: "system",
content: "You are a helpful customer service assistant"
}),
new Message({
role: "user",
content: "I need help with my order"
}),
new Message({
role: "assistant",
content: "I'd be happy to help! What's your order number?"
}),
new Message({
role: "user",
content: "Order #12345"
})
];
// Add to agent
agent.messages = conversation;
Pattern: Assistant Messages with Tool Calls¶
When assistant decides to call a tool, content can be null:
// User asks for information
const user_msg = new Message({
role: "user",
content: "What's the weather in Madrid?"
});
// Assistant decides to call weather tool (content is null during tool calling)
const assistant_tool_call = new Message({
role: "assistant",
content: null // No text response, calling tool instead
});
// Tool returns result
const tool_result = new Message({
role: "tool",
content: JSON.stringify({ temperature: 24, condition: "sunny" }),
tool_call_id: "weather_call_001"
});
// Assistant responds with tool result
const assistant_response = new Message({
role: "assistant",
content: "It's sunny in Madrid with a temperature of 24°C!"
});
Pattern: Tool Result Messages¶
Handle tool execution results:
const user_query = new Message({
role: "user",
content: "Search for customers named John"
});
// ... agent calls search_customers tool ...
const tool_result = new Message({
role: "tool",
content: JSON.stringify([
{ id: 1, name: "John Smith", email: "john@example.com" },
{ id: 2, name: "John Doe", email: "jdoe@example.com" }
]),
tool_call_id: "search_customers_001"
});
const assistant_response = new Message({
role: "assistant",
content: "I found 2 customers named John..."
});
Pattern: System Instructions¶
Set agent behavior with system messages:
const system_instructions = new Message({
role: "system",
content: `You are a sales assistant for an e-commerce platform.
Guidelines:
- Always be enthusiastic about products
- Provide detailed product information
- Suggest complementary items
- Never discuss pricing discounts (that's for managers only)`
});
const agent = new Agent({
name: "Sales_Assistant",
description: "E-commerce sales specialist",
messages: [system_instructions],
tools: [product_search, inventory_check]
});
Pattern: Message Filtering¶
Filter messages by role:
// Get only user messages
const user_messages = conversation.filter(m => m.role === "user");
// Get only assistant responses
const assistant_responses = conversation.filter(m => m.role === "assistant");
// Get all tool results
const tool_results = conversation.filter(m => m.role === "tool");
// Count messages by role
const message_counts = conversation.reduce((acc, msg) => {
acc[msg.role] = (acc[msg.role] || 0) + 1;
return acc;
}, {} as Record<MessageRole, number>);
console.log(message_counts);
// { user: 3, assistant: 2, tool: 1, system: 1 }
Pattern: Conversation Analysis¶
Analyze conversation history:
function analyzeConversation(messages: Message[]) {
const total_length = messages.reduce((sum, msg) => sum + msg.length, 0);
const avg_length = total_length / messages.length;
const last_user_msg = messages
.filter(m => m.role === "user")
.pop();
const tool_calls = messages.filter(m => m.role === "tool").length;
return {
total_messages: messages.length,
average_length: avg_length,
last_user_message: last_user_msg?.content,
tool_executions: tool_calls,
duration: last_msg.timestamp.getTime() - first_msg.timestamp.getTime()
};
}
Validation¶
Messages are validated during construction:
// ✅ Valid messages
new Message({ role: "user", content: "Hello" });
new Message({ role: "tool", content: "Data", tool_call_id: "123" });
new Message({ role: "assistant", content: null, tool_calls: [/* ... */] });
// ❌ Invalid - missing tool_call_id for tool message (runtime error)
try {
new Message({ role: "tool", content: "Data" } as any);
} catch (error) {
console.error(error); // "Los mensajes de herramienta requieren un tool_call_id"
}
// ❌ Invalid - assistant null content without tool_calls (runtime error)
try {
new Message({ role: "assistant", content: null } as any);
} catch (error) {
console.error(error); // "Los mensajes assistant con content null requieren tool_calls"
}
// ❌ Invalid - missing role (runtime error)
try {
new Message({ content: "Hello" } as any);
} catch (error) {
console.error(error); // "El rol del mensaje es requerido"
}
Best Practices¶
1. Use Appropriate Roles¶
Choose the correct role for each message:
// ✅ Good: Correct role usage
new Message({ role: "user", content: "User question" });
new Message({ role: "assistant", content: "AI response" });
new Message({ role: "system", content: "Behavior instructions" });
new Message({ role: "tool", content: "Tool output", tool_call_id: "tool_1" });
// ❌ Bad: Using wrong roles
new Message({ role: "assistant", content: "This is from the user" }); // Wrong
2. Include Tool IDs¶
Always provide tool_call_id for tool messages:
// ✅ Good: Tool message with ID
new Message({
role: "tool",
content: JSON.stringify({ result: "data" }),
tool_call_id: "search_customers_12345"
});
// TypeScript prevents this at compile time:
// new Message({ role: "tool", content: "data" }); // ❌ Type error
3. Structure Tool Content¶
Use JSON for structured tool results:
// ✅ Good: Structured JSON content
new Message({
role: "tool",
content: JSON.stringify({
status: "success",
results: [{ id: 1, name: "Item" }],
count: 1
}),
tool_call_id: "search_001"
});
// ❌ Bad: Unstructured string
new Message({
role: "tool",
content: "Found 1 item: Item",
tool_call_id: "search_001"
});
4. Preserve Message Order¶
Maintain chronological order:
// ✅ Good: Chronological conversation
const conversation = [
system_message, // First: setup
user_message_1, // User starts
assistant_response_1, // AI responds
user_message_2, // User continues
tool_result_1, // Tool executed
assistant_response_2 // AI uses tool result
];
// ❌ Bad: Out of order
const bad_conversation = [
assistant_response,
user_message, // Response before question
system_message // Setup after conversation started
];
Type Safety¶
Message provides full TypeScript type safety:
import { Message, MessageOptions, MessageRole } from '@arcaelas/agent';
// Type-safe message creation
const message: Message = new Message({
role: "user",
content: "Hello"
});
// Role is strictly typed
const role: MessageRole = message.role; // ✅ "user" | "assistant" | "tool" | "system"
// Type checking enforces tool_call_id for tool messages
const tool_msg: Message = new Message({
role: "tool",
content: "data",
tool_call_id: "required" // ✅ TypeScript enforces this
});
// Discriminated union allows type narrowing
function handleMessage(msg: Message) {
if (msg.role === "tool") {
console.log(msg.tool_call_id); // ✅ TypeScript knows tool_call_id exists here
}
}
Related¶
- Context - Manages message history
- Agent - Creates and processes messages
- Tool - Generates tool messages
- Core Concepts - Architecture overview
Next: Learn about Providers →