{"record":{"id":"52da2fe11a8c09c0","repo":"vercel/ai","slug":"pi-only-text-user-message-parts-are-supported-go","errorCode":null,"errorMessage":"pi: only text user-message parts are supported; got '${part.type}'.","messagePattern":"pi: only text user-message parts are supported; got '(.+?)'\\.","errorType":"exception","errorClass":"HarnessCapabilityUnsupportedError","httpStatus":null,"severity":"error","filePath":"packages/harness-pi/src/pi-utils.ts","lineNumber":27,"sourceCode":"/**\n * Extract a single user text string from a `HarnessV1Prompt`. Pi's\n * `session.prompt(text)` accepts a string; multimodal user content\n * is not supported in this foundational version.\n */\nexport function extractUserText(prompt: HarnessV1Prompt): string {\n  if (typeof prompt === 'string') {\n    return prompt;\n  }\n\n  const { content } = prompt;\n  if (typeof content === 'string') {\n    return content;\n  }\n\n  const parts: string[] = [];\n  for (const part of content) {\n    if (part.type !== 'text') {\n      throw new HarnessCapabilityUnsupportedError({\n        message: `pi: only text user-message parts are supported; got '${part.type}'.`,\n        harnessId: HARNESS_ID,\n      });\n    }\n    parts.push(part.text);\n  }\n  return parts.join('\\n\\n');\n}\n\n/*\n * Frame session instructions and the user's text so the runtime treats the\n * instructions as system-provided operating guidance, not something the user\n * wrote. Without the wrapper the agent can echo the prepended text back as if\n * the user had asked for it, which is confusing since the user never typed it.\n * Applied only to the first user message of a fresh session.\n */\nexport function frameInstructions(\n  instructions: string,","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/harness-pi/src/pi-utils.ts#L9-L45","documentation":"The Pi harness only supports plain text parts inside user messages. When converting a HarnessV1 user message into a Pi message, extractUserText iterates the content parts and throws HarnessCapabilityUnsupportedError if any part has a type other than 'text' (e.g. image or file). This signals a genuine harness capability limitation, not a bug in the caller's code shape.","triggerScenarios":"Calling session/text (via sessionImpl) with a user message whose content array contains a non-text part — for example `{ type: 'image', image: ... }` or `{ type: 'file', ... }`. Any multimodal user input routed to the pi harness.","commonSituations":"Building a chat UI that attaches screenshots or file uploads and sending the same message array to all harnesses; migrating an app from a vision-capable provider to the Pi harness without filtering parts; tests reusing multimodal fixtures.","solutions":["Filter user message content down to text-only parts before sending to the pi harness.","For image/file inputs, either switch to a harness that supports multimodal input or handle the attachment through a different channel (e.g. workspace file).","Catch HarnessCapabilityUnsupportedError and degrade gracefully (e.g. notify the user attachments are unsupported)."],"exampleFix":"// before\nawait session.text({\n  role: 'user',\n  content: [{ type: 'text', text: 'look' }, { type: 'image', image }],\n});\n// after\nawait session.text({\n  role: 'user',\n  content: [{ type: 'text', text: 'look at image.png (attached in workspace)' }],\n});","handlingStrategy":"validation","validationCode":"function assertTextOnly(message) {\n  const content = Array.isArray(message.content) ? message.content : [];\n  const bad = content.filter((p) => p.type !== 'text');\n  if (bad.length > 0) {\n    throw new Error(`Non-text parts not supported by pi: ${bad.map((p) => p.type).join(', ')}`);\n  }\n}\nassertTextOnly(userMessage);","typeGuard":"function isTextPart(p: { type: string }): p is { type: 'text'; text: string } {\n  return p.type === 'text';\n}","tryCatchPattern":"import { HarnessCapabilityUnsupportedError } from '@ai-sdk/harness-pi';\ntry {\n  await session.text({ role: 'user', content });\n} catch (err) {\n  if (HarnessCapabilityUnsupportedError.isInstance(err)) {\n    return textOnlyFallback(content);\n  }\n  throw err;\n}","preventionTips":["Filter message content to text parts when targeting the pi harness","Route multimodal input to harnesses that advertise vision/file support","Keep attachments in the workspace instead of inline message parts"],"tags":["unsupported-feature","multimodal","input-validation"],"backgroundTag":"capability-unsupported","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}