{"record":{"id":"f6c70346435ac412","repo":"moeru-ai/airi","slug":"responses-file-requires-exactly-one-source","errorCode":null,"errorMessage":"Responses file requires exactly one source","messagePattern":"Responses file requires exactly one source","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core-agent/src/runtime/responses.ts","lineNumber":107,"sourceCode":"  })\n}\n\nfunction readToolResultContent(content: Extract<ItemParam, { type: 'function_call_output' }>['output']): InputSegment[] {\n  if (typeof content === 'string')\n    return [{ type: 'text', text: content }]\n  return content.map((part) => {\n    switch (part.type) {\n      case 'input_text': return { type: 'text', text: part.text }\n      case 'input_image':\n        if (!part.image_url)\n          throw new Error('Responses image output requires a URL')\n        return { type: 'image', url: part.image_url, detail: part.detail ?? undefined }\n      case 'input_file':\n        if (part.file_data != null && part.file_url == null)\n          return { type: 'file', data: part.file_data, name: part.filename ?? undefined }\n        if (part.file_url != null && part.file_data == null)\n          return { type: 'file', url: part.file_url, name: part.filename ?? undefined }\n        throw new Error('Responses file requires exactly one source')\n      case 'input_video': throw new Error('Video tool output is not supported by the conversation model')\n    }\n    throw new Error('Unsupported Responses tool output')\n  })\n}\n\ntype AssistantContent = Exclude<Extract<ItemParam, { role: 'assistant' }>['content'], string>[number]\n\nfunction readCitations(part: Extract<AssistantContent, { type: 'output_text' }>): Citation[] | undefined {\n  return part.annotations?.map(entry => ({\n    url: entry.url,\n    title: entry.title,\n    startIndex: entry.start_index,\n    endIndex: entry.end_index,\n  }))\n}\n\nfunction readOutput(items: ItemParam[]): ProjectionEntry[] {","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/moeru-ai/airi/blob/438a067dde47aa0bdb46c2323d1fe293dc805218/packages/core-agent/src/runtime/responses.ts#L89-L125","documentation":"While reading a `function_call_output` back into the portable model, an `input_file` part must provide exactly one of `file_data` (inline base64 data) or `file_url` (a remote URL); providing both, or neither, is ambiguous and rejected. The Responses API treats these as mutually exclusive file sources.","triggerScenarios":"A tool emits `{ type: 'input_file' }` with both file_data and file_url set, or with both null/undefined. Happens when a tool builder fills both fields 'to be safe', or when a part is constructed from defaults leaving both unset.","commonSituations":"Custom tool output builders populating input_file from a file descriptor where both data and URL happen to exist; refactors that renamed fields and left the other one set; constructing parts from a template without clearing unused source fields.","solutions":["Set exactly one of file_data or file_url on each input_file part; null out the other field.","Prefer file_url when the file is remotely reachable, and file_data (base64) when it must be inlined.","Add a guard in the tool's output builder that asserts exactly one source is present before returning the part.","Inspect the raw function_call_output JSON to see which builder set both/neither and fix at the source."],"exampleFix":"// before: both sources set\n{ type: 'input_file', file_data: 'aGVsbG8=', file_url: 'https://example.com/hello.txt', filename: 'hello.txt' }\n\n// after: exactly one source\n{ type: 'input_file', file_url: 'https://example.com/hello.txt', filename: 'hello.txt' }","handlingStrategy":"validation","validationCode":"function assertFilePartSingleSource(part) {\n  if (part.type !== 'input_file') return true\n  const sources = [part.file_data != null, part.file_url != null].filter(Boolean).length\n  if (sources !== 1) throw new Error('input_file must set exactly one of file_data or file_url')\n  return true\n}","typeGuard":"const isWellFormedFilePart = (part) => part.type !== 'input_file' || ((part.file_data != null) !== (part.file_url != null))","tryCatchPattern":"try {\n  await streamResponses({ config, conversation, scope, onEvent })\n} catch (e) {\n  if (e.message === 'Responses file requires exactly one source') {\n    // rebuild the tool's file parts keeping only one source, then retry\n  } else throw e\n}","preventionTips":["Build input_file parts through a single helper that takes either data or url, never both.","Null out the unused field when copying file descriptors into tool output parts.","Add a builder-level assertion (XOR of file_data/file_url) in every tool that emits files."],"tags":["responses-api","tool-output","files","mutually-exclusive-options"],"backgroundTag":"mutually-exclusive-options","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"}