{"record":{"id":"190040807f130e7c","repo":"moeru-ai/airi","slug":"chat-completions-does-not-support-file-urls","errorCode":null,"errorMessage":"Chat Completions does not support file URLs","messagePattern":"Chat Completions does not support file URLs","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core-agent/src/messages/chat-completions.ts","lineNumber":82,"sourceCode":"    }\n    if (message.role === 'system' || message.role === 'developer')\n      return { id, role: message.role, segments: typeof message.content === 'string' ? [{ type: 'text', text: message.content }] : message.content.map(part => ({ type: 'text', text: part.text })) }\n    return { id, role: message.role, segments: chatContentToInputSegments(message.content) }\n  })\n}\n\n/** Reads persisted Chat records; the storage identity scopes turn, round, and invocation ids. */\nexport function chatMessagesToTurns(messages: Parameters<typeof chatMessagesToProjectionEntries>[0], idPrefix?: string): Turn[] {\n  return readTurns(chatMessagesToProjectionEntries(messages, idPrefix))\n}\n\nfunction writeContent(segment: MessageSegment): CommonContentPart {\n  switch (segment.type) {\n    case 'image': return { type: 'image_url', image_url: { url: segment.url, detail: segment.detail } }\n    case 'audio': return { type: 'input_audio', input_audio: { data: segment.data, format: segment.format } }\n    case 'file':\n      if (segment.url)\n        throw new Error('Chat Completions does not support file URLs')\n      return { type: 'file', file: { file_data: segment.data, filename: segment.name, file_id: segment.providerFileId } }\n    default: return { type: 'text', text: renderSegmentText(segment) }\n  }\n}\n\nfunction renderEntry(message: ProjectionEntry, supportsContentArray: boolean): ChatMessage[] {\n  const result: ChatMessage[] = []\n  const role = message.role === 'context' || message.role === 'event' || message.role === 'summary' ? 'user' : message.role\n  let parts: CommonContentPart[] = []\n  let assistantParts: Array<{ type: 'text', text: string } | { type: 'refusal', refusal: string }> = []\n  let calls: NonNullable<Extract<ChatMessage, { role: 'assistant' }>['tool_calls']> = []\n  function flush() {\n    if (role === 'assistant') {\n      if (assistantParts.length || calls.length) {\n        const content = !supportsContentArray || assistantParts.every(part => part.type === 'text')\n          ? assistantParts.map(part => part.type === 'text' ? part.text : part.refusal).join('')\n          : assistantParts\n        result.push({ role, content, ...(calls.length ? { tool_calls: calls } : {}) })","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/moeru-ai/airi/blob/438a067dde47aa0bdb46c2323d1fe293dc805218/packages/core-agent/src/messages/chat-completions.ts#L64-L100","documentation":"writeContent renders a portable file segment into a Chat Completions content part. Chat Completions file input only supports inline file_data or a provider file_id; a segment carrying a URL has no Chat Completions representation, so the renderer throws instead of emitting a part the API would reject.","triggerScenarios":"Calling conversationToChatMessages (through renderEntry) with a portable message whose segments include { type: 'file', url: 'https://...' } — i.e. a file referenced by URL rather than inline data or a provider file id.","commonSituations":"Porting a conversation authored for the Responses API (which supports file URLs) to Chat Completions; building file segments from web links or object-storage URLs; switching providers where the previous API accepted URL-based file references.","solutions":["Replace the URL with inline data: fetch the file yourself and set segment.data to its base64/data-URL content","Upload the file to the provider's file API and use segment.providerFileId instead of segment.url","Route this conversation through a protocol/renderer that supports file URLs (e.g. Responses-style rendering) if URL references must be preserved"],"exampleFix":"// before\nsegments: [{ type: 'file', url: 'https://example.com/doc.pdf', name: 'doc.pdf' }]\n// after\nsegments: [{ type: 'file', data: base64OrDataUrl, name: 'doc.pdf' }]","handlingStrategy":"validation","validationCode":"function chatFileSegmentIsRenderable(seg) {\n  return seg.type !== 'file' || !seg.url\n}\nif (!message.segments.every(chatFileSegmentIsRenderable)) throw new Error('File URL segments are not renderable to Chat Completions')","typeGuard":"function hasFileUrl(segments) {\n  return segments.some(s => s.type === 'file' && typeof s.url === 'string')\n}","tryCatchPattern":"try {\n  return conversationToChatMessages(conversation, supportsContentArray)\n} catch (err) {\n  if (err.message === 'Chat Completions does not support file URLs') {\n    // hydrate url segments to inline data or provider ids, then retry\n    return conversationToChatMessages(await inlineFileUrls(conversation), supportsContentArray)\n  }\n  throw err\n}","preventionTips":["Prefer data or providerFileId over url when authoring file segments","Fetch URL-referenced files and inline them before rendering to Chat Completions","Upload files to the provider and store the returned file id instead of a link","If conversations may contain URL file segments, detect them up front and choose a renderer that supports them"],"tags":["chat-completions","file-content","urls","renderer"],"backgroundTag":"unsupported-operation","analyzedSha":"438a067dde47aa0bdb46c2323d1fe293dc805218","analyzedAt":"2026-09-17T01:14:42.644Z","contentChangedAt":"2026-09-17T01:14:42.644Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}