mastra-ai/mastra · error
Saw text content for input ModelMessage, but the role is ${m
Error message
Saw text content for input ModelMessage, but the role is ${modelMessage.role}. This is only allowed for "system", "assistant", and "user" roles. What it means
aiV5ModelMessageToV2PromptMessage (to-prompt.ts:193) is the AI SDK V5 analogue of the V4 converter: string content is only handled for roles 'system', 'assistant', and 'user'. A ModelMessage with string content and any other role (i.e. 'tool') cannot be mapped to the LanguageModelV2 prompt and the converter throws.
Source
Thrown at packages/core/src/agent/message-list/conversion/to-prompt.ts:193
/**
* Convert an AI SDK V5 ModelMessage to a V2 LanguageModel prompt message.
* Used for creating LLM prompt messages without AI SDK streamText/generateText.
*/
export function aiV5ModelMessageToV2PromptMessage(modelMessage: AIV5Type.ModelMessage): AIV5LanguageModelV2Message {
if (modelMessage.role === `system`) {
return modelMessage;
}
if (typeof modelMessage.content === `string` && (modelMessage.role === `assistant` || modelMessage.role === `user`)) {
return {
role: modelMessage.role,
content: [{ type: 'text', text: modelMessage.content }],
providerOptions: modelMessage.providerOptions,
};
}
if (typeof modelMessage.content === `string`) {
throw new Error(
`Saw text content for input ModelMessage, but the role is ${modelMessage.role}. This is only allowed for "system", "assistant", and "user" roles.`,
);
}
const roleContent: {
user: Extract<AIV5LanguageModelV2Message, { role: 'user' }>['content'];
assistant: Extract<AIV5LanguageModelV2Message, { role: 'assistant' }>['content'];
tool: Extract<AIV5LanguageModelV2Message, { role: 'tool' }>['content'];
} = {
user: [],
assistant: [],
tool: [],
};
const role = modelMessage.role;
for (const part of modelMessage.content ?? []) {
// Defensive: upstream rewrites (e.g. observational memory) have produced sparseView on GitHub (pinned to 75dd419e61)
Solutions
- Use array content for tool messages: [{ type: 'tool-result', toolCallId, toolName, output }].
- If the text is a plain statement, use role 'user' or 'assistant' instead of 'tool'.
- Sanitize stored V5 history so tool messages always contain structured tool-result parts.
Example fix
// before
{ role: 'tool', content: 'the result' } // V5 ModelMessage
// after
{ role: 'tool', content: [{ type: 'tool-result', toolCallId: 'call_1', toolName: 't', output: { type: 'text', value: 'the result' } }] } Defensive patterns
Strategy: validation
Validate before calling
function assertV5ToolMessage(m: { role: string; content: unknown }) {
if (m.role === 'tool' && typeof m.content === 'string') {
throw new Error('V5 tool messages require an array of tool-result content parts, not a string');
}
} Type guard
function isV5Convertible(m: { role: string; content: unknown }): boolean {
if (typeof m.content !== 'string') return true;
return m.role === 'system' || m.role === 'assistant' || m.role === 'user';
} Try / catch
try {
const prompt = messageList.toPrompt();
} catch (e) {
if (e instanceof Error && e.message.includes('Saw text content for input ModelMessage')) {
console.error('V5 tool message has string content; wrap in a tool-result part.');
} else throw e;
} Prevention
- Use AI SDK v5 tool-result part shape ({ type: 'tool-result', toolCallId, toolName, output })
- Never assign string content to tool-role ModelMessages
- Validate message history when porting pipelines from AI SDK v4 to v5
When it happens
Trigger: Calling the converter (or MessageList.toPrompt with V5 messages) with a V5 ModelMessage of role 'tool' whose content is a plain string rather than an array of tool-result content parts.
Common situations: Hand-building tool responses in V5; persisted history where tool results were flattened to strings; porting V4 code that tolerated string tool content.
Related errors
- Saw text content for input CoreMessage, but the role is ${co
- Saw incompatible message content part type ${part.type} for
- Encountered unknown role ${role} when converting V4 CoreMess
- Encountered unknown role ${role} when converting V5 ModelMes
- Unhandled content part type: ${(exhaustiveCheck as { type: s
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/8680d725e531f8af.
Report an issue: GitHub.