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

  1. Move the text part into a separate 'user' or 'assistant' message.
  2. Keep only { type: 'tool-result' } parts in tool-role messages, embedding any textual output inside the result field.
  3. 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

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


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/d09852ead2c7ea28. Report an issue: GitHub.