{"record":{"id":"d09852ead2c7ea28","repo":"mastra-ai/mastra","slug":"saw-incompatible-message-content-part-type-part","errorCode":null,"errorMessage":"Saw incompatible message content part type ${part.type} for message role ${role}","messagePattern":"Saw incompatible message content part type (.+?) for message role (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/agent/message-list/conversion/to-prompt.ts","lineNumber":52,"sourceCode":"  const roleContent: {\n    user: Exclude<Extract<LanguageModelV1Message, { role: 'user' }>['content'], string>;\n    assistant: Exclude<Extract<LanguageModelV1Message, { role: 'assistant' }>['content'], string>;\n    tool: Exclude<Extract<LanguageModelV1Message, { role: 'tool' }>['content'], string>;\n  } = {\n    user: [],\n    assistant: [],\n    tool: [],\n  };\n\n  const role = coreMessage.role;\n\n  for (const part of coreMessage.content) {\n    const incompatibleMessage = `Saw incompatible message content part type ${part.type} for message role ${role}`;\n\n    switch (part.type) {\n      case 'text': {\n        if (role === `tool`) {\n          throw new Error(incompatibleMessage);\n        }\n        roleContent[role].push(part);\n        break;\n      }\n\n      case 'redacted-reasoning':\n      case 'reasoning': {\n        if (role !== `assistant`) {\n          throw new Error(incompatibleMessage);\n        }\n        roleContent[role].push(part);\n        break;\n      }\n\n      case 'tool-call': {\n        if (role === `tool` || role === `user`) {\n          throw new Error(incompatibleMessage);\n        }","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/core/src/agent/message-list/conversion/to-prompt.ts#L34-L70","documentation":"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.","triggerScenarios":"A CoreMessage with role 'tool' whose content array includes { type: 'text', ... } parts, passed to prompt conversion (directly or via MessageList.toPrompt).","commonSituations":"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'.","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."],"exampleFix":"// before\n{ role: 'tool', content: [{ type: 'tool-result', toolCallId: 'c1', toolName: 't', result: 1 }, { type: 'text', text: 'done' }] }\n// after\n{ role: 'tool', content: [{ type: 'tool-result', toolCallId: 'c1', toolName: 't', result: 1 }] },\n{ role: 'user', content: [{ type: 'text', text: 'done' }] }","handlingStrategy":"validation","validationCode":"const ROLE_PARTS: Record<string, string[]> = {\n  system: ['text'], user: ['text', 'image', 'file'],\n  assistant: ['text', 'reasoning', 'redacted-reasoning', 'tool-call', 'file'],\n  tool: ['tool-result'],\n};\nfunction validateRoleParts(m: { role: string; content: unknown[] }) {\n  const allowed = ROLE_PARTS[m.role];\n  if (!allowed) throw new Error(`unknown role ${m.role}`);\n  for (const p of m.content) {\n    if (!allowed.includes((p as any).type)) throw new Error(`part ${(p as any).type} not allowed for role ${m.role}`);\n  }\n}","typeGuard":"function isTextPartAllowedForRole(role: string, part: { type: string }): boolean {\n  return !(part.type === 'text' && role === 'tool');\n}","tryCatchPattern":"try {\n  const prompt = messageList.toPrompt();\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Saw incompatible message content part type text for message role tool')) {\n    // filter out text parts from tool messages and retry\n  } else throw e;\n}","preventionTips":["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"],"tags":["message-conversion","ai-sdk-v4","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"}