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 sparse

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Use array content for tool messages: [{ type: 'tool-result', toolCallId, toolName, output }].
  2. If the text is a plain statement, use role 'user' or 'assistant' instead of 'tool'.
  3. 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

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


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