vercel/ai · error · HarnessCapabilityUnsupportedError

pi: only text user-message parts are supported; got '${part.

Error message

pi: only text user-message parts are supported; got '${part.type}'.

What it means

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.

Source

Thrown at packages/harness-pi/src/pi-utils.ts:27

/**
 * Extract a single user text string from a `HarnessV1Prompt`. Pi's
 * `session.prompt(text)` accepts a string; multimodal user content
 * is not supported in this foundational version.
 */
export function extractUserText(prompt: HarnessV1Prompt): string {
  if (typeof prompt === 'string') {
    return prompt;
  }

  const { content } = prompt;
  if (typeof content === 'string') {
    return content;
  }

  const parts: string[] = [];
  for (const part of content) {
    if (part.type !== 'text') {
      throw new HarnessCapabilityUnsupportedError({
        message: `pi: only text user-message parts are supported; got '${part.type}'.`,
        harnessId: HARNESS_ID,
      });
    }
    parts.push(part.text);
  }
  return parts.join('\n\n');
}

/*
 * Frame session instructions and the user's text so the runtime treats the
 * instructions as system-provided operating guidance, not something the user
 * wrote. Without the wrapper the agent can echo the prepended text back as if
 * the user had asked for it, which is confusing since the user never typed it.
 * Applied only to the first user message of a fresh session.
 */
export function frameInstructions(
  instructions: string,

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Filter user message content down to text-only parts before sending to the pi harness.
  2. 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).
  3. Catch HarnessCapabilityUnsupportedError and degrade gracefully (e.g. notify the user attachments are unsupported).

Example fix

// before
await session.text({
  role: 'user',
  content: [{ type: 'text', text: 'look' }, { type: 'image', image }],
});
// after
await session.text({
  role: 'user',
  content: [{ type: 'text', text: 'look at image.png (attached in workspace)' }],
});
Defensive patterns

Strategy: validation

Validate before calling

function assertTextOnly(message) {
  const content = Array.isArray(message.content) ? message.content : [];
  const bad = content.filter((p) => p.type !== 'text');
  if (bad.length > 0) {
    throw new Error(`Non-text parts not supported by pi: ${bad.map((p) => p.type).join(', ')}`);
  }
}
assertTextOnly(userMessage);

Type guard

function isTextPart(p: { type: string }): p is { type: 'text'; text: string } {
  return p.type === 'text';
}

Try / catch

import { HarnessCapabilityUnsupportedError } from '@ai-sdk/harness-pi';
try {
  await session.text({ role: 'user', content });
} catch (err) {
  if (HarnessCapabilityUnsupportedError.isInstance(err)) {
    return textOnlyFallback(content);
  }
  throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30). Data as JSON: /api/errors/52da2fe11a8c09c0. Report an issue: GitHub.