moeru-ai/airi · error · Error

Unsupported Chat content part

Error message

Unsupported Chat content part

What it means

chatContentToInputSegments maps known Chat content part types (text, image_url, input_audio, file) to portable input segments. Any part whose type is outside that set has no portable representation, so the library throws rather than silently dropping or mis-typing the content.

Solutions

  1. Inspect the content part's type at the call site and drop or convert unsupported parts before ingress
  2. Map the unsupported part to a supported one (e.g. convert to text) if it carries equivalent information
  3. Check the @xsai/shared-chat CommonContentPart type for the recognized set and update the library/dependency if a new part type must be supported

Example fix

// before
parts.filter(p => p.type === 'reasoning') // later passed as-is into chatMessagesToProjectionEntries
// after
parts.filter(p => ['text','image_url','input_audio','file'].includes(p.type))
Defensive patterns

Strategy: type-guard

Validate before calling

const SUPPORTED = new Set(['text', 'image_url', 'input_audio', 'file'])
if (!contentParts.every(p => SUPPORTED.has(p.type))) throw new Error('Unsupported content part type before ingress')

Type guard

function isSupportedChatPart(part) {
  return ['text', 'image_url', 'input_audio', 'file'].includes(part.type)
}

Try / catch

try {
  const segments = chatContentToInputSegments(content)
} catch (err) {
  if (err.message === 'Unsupported Chat content part') {
    return content.filter(isSupportedChatPart).map(convertSafely)
  }
  throw err
}

Prevention

When it happens

Trigger: Calling chatContentToInputSegments (via chatMessagesToProjectionEntries or replaceToolCallResult) with a content array containing a part.type that is not 'text', 'image_url', 'input_audio', or 'file' — e.g. a newer OpenAI part type like custom tool parts or an unrecognized/misspelled type from a compatible server.

Common situations: Provider SDK updated and introduced new content part types this ingress does not know; a proxy/compatible server returns nonstandard part types; a typo when hand-writing parts (e.g. type: 'imageUrl'); replaying messages captured from a different API surface (Responses vs Chat).

Related errors


AI-assisted analysis of moeru-ai/airi@438a067dde (2026-09-17). Data as JSON: /api/errors/cc1982125d57d957. Report an issue: GitHub.

Appendix: source

Thrown at packages/core-agent/src/messages/chat-completions.ts:33

 */
export function chatContentToInputSegments(content: string | CommonContentPart[] | undefined): InputSegment[] {
  if (content == null)
    return []
  if (typeof content === 'string')
    return [{ type: 'text', text: content }]
  return content.map((part) => {
    switch (part.type) {
      case 'text': return { type: 'text', text: part.text }
      case 'image_url': return { type: 'image', url: part.image_url.url, detail: part.image_url.detail }
      case 'input_audio': return { type: 'audio', ...part.input_audio }
      case 'file':
        if (part.file.file_data !== undefined && part.file.file_id === undefined)
          return { type: 'file', data: part.file.file_data, name: part.file.filename }
        if (part.file.file_id !== undefined && part.file.file_data === undefined)
          return { type: 'file', providerFileId: part.file.file_id, name: part.file.filename }
        throw new Error('Chat file requires exactly one source')
    }
    throw new Error('Unsupported Chat content part')
  })
}

/**
 * Reads Chat-shaped storage or SDK output into portable message semantics.
 * This is an ingress boundary; Responses never calls the Chat request renderer.
 *
 * @example
 * chatMessagesToProjectionEntries([{ role: 'user', content: 'Hello' }])[0].segments
 * // => [{ type: 'text', text: 'Hello' }]
 */
export function chatMessagesToProjectionEntries(messages: (ChatMessage | { role: 'error', content: string })[], idPrefix = 'message'): ProjectionEntry[] {
  return messages.map((message, index) => {
    const id = `${idPrefix}-${index}`
    if (message.role === 'error')
      return { id, role: 'user', segments: [{ type: 'text', text: `User encountered error: ${message.content}` }] }
    if (message.role === 'tool')
      return { id, role: 'tool', segments: [{ type: 'tool-result', callId: message.tool_call_id, content: chatContentToInputSegments(message.content) }] }

View on GitHub (pinned to 438a067dde)