moeru-ai/airi · error · Error
Responses file requires exactly one source
Error message
Responses file requires exactly one source
What it means
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.
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.
Example fix
// before: both sources set
{ type: 'input_file', file_data: 'aGVsbG8=', file_url: 'https://example.com/hello.txt', filename: 'hello.txt' }
// after: exactly one source
{ type: 'input_file', file_url: 'https://example.com/hello.txt', filename: 'hello.txt' } Defensive patterns
Strategy: validation
Validate before calling
function assertFilePartSingleSource(part) {
if (part.type !== 'input_file') return true
const sources = [part.file_data != null, part.file_url != null].filter(Boolean).length
if (sources !== 1) throw new Error('input_file must set exactly one of file_data or file_url')
return true
} Type guard
const isWellFormedFilePart = (part) => part.type !== 'input_file' || ((part.file_data != null) !== (part.file_url != null))
Try / catch
try {
await streamResponses({ config, conversation, scope, onEvent })
} catch (e) {
if (e.message === 'Responses file requires exactly one source') {
// rebuild the tool's file parts keeping only one source, then retry
} else throw e
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Responses image output requires a URL
- Only assistant messages can invoke tools
- Responses continuation must contain an item array
- Tool messages require a correlated tool result
- Unsupported Responses assistant content
AI-assisted analysis of moeru-ai/airi@438a067dde (2026-09-17).
Data as JSON: /api/errors/f6c70346435ac412.
Report an issue: GitHub.
Appendix: source
Thrown at packages/core-agent/src/runtime/responses.ts:107
})
}
function readToolResultContent(content: Extract<ItemParam, { type: 'function_call_output' }>['output']): InputSegment[] {
if (typeof content === 'string')
return [{ type: 'text', text: content }]
return content.map((part) => {
switch (part.type) {
case 'input_text': return { type: 'text', text: part.text }
case 'input_image':
if (!part.image_url)
throw new Error('Responses image output requires a URL')
return { type: 'image', url: part.image_url, detail: part.detail ?? undefined }
case 'input_file':
if (part.file_data != null && part.file_url == null)
return { type: 'file', data: part.file_data, name: part.filename ?? undefined }
if (part.file_url != null && part.file_data == null)
return { type: 'file', url: part.file_url, name: part.filename ?? undefined }
throw new Error('Responses file requires exactly one source')
case 'input_video': throw new Error('Video tool output is not supported by the conversation model')
}
throw new Error('Unsupported Responses tool output')
})
}
type AssistantContent = Exclude<Extract<ItemParam, { role: 'assistant' }>['content'], string>[number]
function readCitations(part: Extract<AssistantContent, { type: 'output_text' }>): Citation[] | undefined {
return part.annotations?.map(entry => ({
url: entry.url,
title: entry.title,
startIndex: entry.start_index,
endIndex: entry.end_index,
}))
}
function readOutput(items: ItemParam[]): ProjectionEntry[] {View on GitHub (pinned to 438a067dde)