{"record":{"id":"8680d725e531f8af","repo":"mastra-ai/mastra","slug":"saw-text-content-for-input-modelmessage-but-the-r","errorCode":null,"errorMessage":"Saw text content for input ModelMessage, but the role is ${modelMessage.role}. This is only allowed for \"system\", \"assistant\", and \"user\" roles.","messagePattern":"Saw text content for input ModelMessage, but the role is (.+?)\\. This is only allowed for \"system\", \"assistant\", and \"user\" roles\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/agent/message-list/conversion/to-prompt.ts","lineNumber":193,"sourceCode":"/**\n * Convert an AI SDK V5 ModelMessage to a V2 LanguageModel prompt message.\n * Used for creating LLM prompt messages without AI SDK streamText/generateText.\n */\nexport function aiV5ModelMessageToV2PromptMessage(modelMessage: AIV5Type.ModelMessage): AIV5LanguageModelV2Message {\n  if (modelMessage.role === `system`) {\n    return modelMessage;\n  }\n\n  if (typeof modelMessage.content === `string` && (modelMessage.role === `assistant` || modelMessage.role === `user`)) {\n    return {\n      role: modelMessage.role,\n      content: [{ type: 'text', text: modelMessage.content }],\n      providerOptions: modelMessage.providerOptions,\n    };\n  }\n\n  if (typeof modelMessage.content === `string`) {\n    throw new Error(\n      `Saw text content for input ModelMessage, but the role is ${modelMessage.role}. This is only allowed for \"system\", \"assistant\", and \"user\" roles.`,\n    );\n  }\n\n  const roleContent: {\n    user: Extract<AIV5LanguageModelV2Message, { role: 'user' }>['content'];\n    assistant: Extract<AIV5LanguageModelV2Message, { role: 'assistant' }>['content'];\n    tool: Extract<AIV5LanguageModelV2Message, { role: 'tool' }>['content'];\n  } = {\n    user: [],\n    assistant: [],\n    tool: [],\n  };\n\n  const role = modelMessage.role;\n\n  for (const part of modelMessage.content ?? []) {\n    // Defensive: upstream rewrites (e.g. observational memory) have produced sparse","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/agent/message-list/conversion/to-prompt.ts#L175-L211","documentation":"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.","triggerScenarios":"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.","commonSituations":"Hand-building tool responses in V5; persisted history where tool results were flattened to strings; porting V4 code that tolerated string tool content.","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."],"exampleFix":"// before\n{ role: 'tool', content: 'the result' } // V5 ModelMessage\n// after\n{ role: 'tool', content: [{ type: 'tool-result', toolCallId: 'call_1', toolName: 't', output: { type: 'text', value: 'the result' } }] }","handlingStrategy":"validation","validationCode":"function assertV5ToolMessage(m: { role: string; content: unknown }) {\n  if (m.role === 'tool' && typeof m.content === 'string') {\n    throw new Error('V5 tool messages require an array of tool-result content parts, not a string');\n  }\n}","typeGuard":"function isV5Convertible(m: { role: string; content: unknown }): boolean {\n  if (typeof m.content !== 'string') return true;\n  return m.role === 'system' || m.role === 'assistant' || m.role === 'user';\n}","tryCatchPattern":"try {\n  const prompt = messageList.toPrompt();\n} catch (e) {\n  if (e instanceof Error && e.message.includes('Saw text content for input ModelMessage')) {\n    console.error('V5 tool message has string content; wrap in a tool-result part.');\n  } else throw e;\n}","preventionTips":["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"],"tags":["message-conversion","ai-sdk-v5","tool-messages","prompt"],"backgroundTag":"invalid-message-role-content","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}