mastra-ai/mastra · error · Error
Saw incompatible message content part type ${part.type} for
Error message
Saw incompatible message content part type ${part.type} for message role ${role} What it means
In aiV4CoreMessageToV1PromptMessage (to-prompt.ts:52), a 'text' content part is rejected when the message role is 'tool'. Tool messages may only carry tool-result parts in the LanguageModelV1 prompt format; a text part there is incompatible, so the converter throws.
Source
Thrown at packages/core/src/agent/message-list/conversion/to-prompt.ts:52
const roleContent: {
user: Exclude<Extract<LanguageModelV1Message, { role: 'user' }>['content'], string>;
assistant: Exclude<Extract<LanguageModelV1Message, { role: 'assistant' }>['content'], string>;
tool: Exclude<Extract<LanguageModelV1Message, { role: 'tool' }>['content'], string>;
} = {
user: [],
assistant: [],
tool: [],
};
const role = coreMessage.role;
for (const part of coreMessage.content) {
const incompatibleMessage = `Saw incompatible message content part type ${part.type} for message role ${role}`;
switch (part.type) {
case 'text': {
if (role === `tool`) {
throw new Error(incompatibleMessage);
}
roleContent[role].push(part);
break;
}
case 'redacted-reasoning':
case 'reasoning': {
if (role !== `assistant`) {
throw new Error(incompatibleMessage);
}
roleContent[role].push(part);
break;
}
case 'tool-call': {
if (role === `tool` || role === `user`) {
throw new Error(incompatibleMessage);
}View on GitHub (pinned to 75dd419e61)
Solutions
- Move the text part into a separate 'user' or 'assistant' message.
- Keep only { type: 'tool-result' } parts in tool-role messages, embedding any textual output inside the result field.
- Sanitize stored history to strip/relocate text parts from tool messages before prompt conversion.
Example fix
// before
{ role: 'tool', content: [{ type: 'tool-result', toolCallId: 'c1', toolName: 't', result: 1 }, { type: 'text', text: 'done' }] }
// after
{ role: 'tool', content: [{ type: 'tool-result', toolCallId: 'c1', toolName: 't', result: 1 }] },
{ role: 'user', content: [{ type: 'text', text: 'done' }] } Defensive patterns
Strategy: validation
Validate before calling
const ROLE_PARTS: Record<string, string[]> = {
system: ['text'], user: ['text', 'image', 'file'],
assistant: ['text', 'reasoning', 'redacted-reasoning', 'tool-call', 'file'],
tool: ['tool-result'],
};
function validateRoleParts(m: { role: string; content: unknown[] }) {
const allowed = ROLE_PARTS[m.role];
if (!allowed) throw new Error(`unknown role ${m.role}`);
for (const p of m.content) {
if (!allowed.includes((p as any).type)) throw new Error(`part ${(p as any).type} not allowed for role ${m.role}`);
}
} Type guard
function isTextPartAllowedForRole(role: string, part: { type: string }): boolean {
return !(part.type === 'text' && role === 'tool');
} Try / catch
try {
const prompt = messageList.toPrompt();
} catch (e) {
if (e instanceof Error && e.message.startsWith('Saw incompatible message content part type text for message role tool')) {
// filter out text parts from tool messages and retry
} else throw e;
} Prevention
- Keep a role->allowed-part-types map and validate before conversion
- Move conversational text out of tool messages into user/assistant turns
- Sanitize legacy stored histories once during migration
When it happens
Trigger: A CoreMessage with role 'tool' whose content array includes { type: 'text', ... } parts, passed to prompt conversion (directly or via MessageList.toPrompt).
Common situations: Appending human-readable notes alongside tool results in a tool message; copying AI SDK examples from other versions; storage that mixed text and tool-result parts under role 'tool'.
Related errors
- Saw text content for input CoreMessage, but the role is ${co
- Encountered unknown role ${role} when converting V4 CoreMess
- Saw text content for input ModelMessage, but the role is ${m
- Unhandled content part type: ${(exhaustiveCheck as { type: s
- Cancelled
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/d09852ead2c7ea28.
Report an issue: GitHub.