🐛 fix: Only stream provisioning sources with the standard contract

Provisioning called getDownloadStream(req, filepath) for every source, but that
contract is not universal: openai takes (file_id, client) and execute_code takes
(fileIdentifier, identity, req) and returns an Axios response, so files backed by
those sources were mis-invoked rather than provisioned. Streaming now goes
through an allowlist of storage-backed sources, so an unfamiliar source is
skipped with a warning instead of called incorrectly.
This commit is contained in:
Danny Avila 2026-08-31 09:49:03 -04:00
parent 0eb921eb1c
commit bc2e9c868f
2 changed files with 53 additions and 8 deletions

View file

@ -20,6 +20,34 @@ const { getStrategyFunctions } = require('./strategies');
const axios = createAxiosInstance();
/* Sources whose `getDownloadStream` takes `(req, filepath)` and returns a readable.
* Others diverge: `openai` takes `(file_id, client)` and `execute_code` takes
* `(fileIdentifier, identity, req)` returning an Axios response, so calling them
* through this contract fails. An allowlist keeps an unfamiliar source skipped
* rather than mis-invoked. */
const STORAGE_STREAM_SOURCES = new Set([
FileSources.local,
FileSources.s3,
FileSources.cloudfront,
FileSources.azure_blob,
FileSources.firebase,
]);
/** Resolves a storage download stream, or null when the source uses a different contract. */
async function getStorageStream(file, req) {
if (!STORAGE_STREAM_SOURCES.has(file.source)) {
logger.warn(
`[provision] Cannot stream "${file.filename}" (${file.file_id}) from source "${file.source}": unsupported download contract`,
);
return null;
}
const { getDownloadStream } = getStrategyFunctions(file.source);
if (!getDownloadStream) {
return null;
}
return await getDownloadStream(req, file.filepath);
}
/** Composes code-API auth: legacy X-API-Key when configured, plus JWT bearer when enabled. */
async function buildCodeApiHeaders({ apiKey, req }) {
return {
@ -82,16 +110,14 @@ async function loadCodeApiKey(userId) {
* Merged pointers plus the deferred DB update
*/
async function provisionToCodeEnv({ req, file, entity_id }) {
const { getDownloadStream } = getStrategyFunctions(file.source);
if (!getDownloadStream) {
const { handleFileUpload: uploadCodeEnvFile } = getStrategyFunctions(FileSources.execute_code);
const stream = await getStorageStream(file, req);
if (!stream) {
throw new Error(
`Cannot provision file "${file.filename}" to code env: storage source "${file.source}" does not support download streams`,
);
}
const { handleFileUpload: uploadCodeEnvFile } = getStrategyFunctions(FileSources.execute_code);
const stream = await getDownloadStream(req, file.filepath);
const kind = entity_id ? 'agent' : 'user';
const id = entity_id ?? req.user.id;
@ -147,13 +173,12 @@ async function provisionToVectorDB({ req, file, entity_id, existingStream }) {
try {
let stream = existingStream;
if (!stream) {
const { getDownloadStream } = getStrategyFunctions(file.source);
if (!getDownloadStream) {
stream = await getStorageStream(file, req);
if (!stream) {
throw new Error(
`Cannot provision file "${file.filename}" to vector DB: storage source "${file.source}" does not support download streams`,
);
}
stream = await getDownloadStream(req, file.filepath);
}
// uploadVectors expects a file-like object with a `path` property for fs.createReadStream.

View file

@ -157,6 +157,26 @@ describe('provisionToCodeEnv', () => {
expect(result.referenceSet.codeEnvRefs.default.executionProfile).toBe('default');
});
it('refuses sources whose download contract differs instead of mis-invoking them', async () => {
const uploadCodeEnvFile = jest.fn();
setupStrategies(uploadCodeEnvFile);
await expect(
provisionToCodeEnv({
req: { user: { id: 'u1' } },
file: {
file_id: 'f-openai',
filename: 'doc.pdf',
type: 'application/pdf',
source: 'openai',
filepath: '/x/doc.pdf',
metadata: {},
},
}),
).rejects.toThrow(/does not support download streams/);
expect(uploadCodeEnvFile).not.toHaveBeenCalled();
});
it('keeps filenames untouched when the extension already matches or the file is not an image', async () => {
const uploadCodeEnvFile = jest
.fn()