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
- Inspect the content part's type at the call site and drop or convert unsupported parts before ingress
- Map the unsupported part to a supported one (e.g. convert to text) if it carries equivalent information
- 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
- Filter content arrays against the supported part-type set before calling ingress functions
- Keep @xsai/shared-chat updated and check for new CommonContentPart variants after SDK upgrades
- Log and drop unknown part types from compatible servers instead of passing them through
- Type content arrays as CommonContentPart so typos fail at compile time
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
- Chat file requires exactly one source
- Chat Completions does not support file URLs
- [llm] Auto-disabling content-part arrays for
- Tool messages require a correlated tool result
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)