Skip to content

Commit c975e5c

Browse files
committed
fix: bound remote image streaming
1 parent 78577cd commit c975e5c

1 file changed

Lines changed: 64 additions & 6 deletions

File tree

  • packages/ingestion/src/ingestion

packages/ingestion/src/ingestion/image.ts

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,69 @@ const describeImageWithMistral = async (input: {
8181
}
8282
};
8383

84+
interface ImageByteResponse {
85+
body: {
86+
getReader: () => {
87+
cancel: () => Promise<void>;
88+
read: () => Promise<{ done: boolean; value?: Uint8Array }>;
89+
releaseLock: () => void;
90+
};
91+
} | null;
92+
headers: {
93+
get: (name: string) => string | null;
94+
};
95+
}
96+
97+
const readBoundedImageBytes = async (
98+
response: ImageByteResponse,
99+
maxBytes: number
100+
): Promise<Uint8Array> => {
101+
const contentLength = response.headers.get("content-length");
102+
if (contentLength) {
103+
const declaredBytes = Number(contentLength);
104+
if (Number.isFinite(declaredBytes) && declaredBytes >= 0) {
105+
assertMaxSize("remote image payload", declaredBytes, maxBytes);
106+
}
107+
}
108+
109+
if (!response.body) {
110+
return new Uint8Array();
111+
}
112+
113+
const reader = response.body.getReader();
114+
const chunks: Uint8Array[] = [];
115+
let totalBytes = 0;
116+
117+
try {
118+
while (true) {
119+
const { done, value } = await reader.read();
120+
if (done) {
121+
break;
122+
}
123+
if (!value) {
124+
continue;
125+
}
126+
127+
totalBytes += value.byteLength;
128+
assertMaxSize("remote image payload", totalBytes, maxBytes);
129+
chunks.push(value);
130+
}
131+
} catch (error) {
132+
await reader.cancel().catch(() => undefined);
133+
throw error;
134+
} finally {
135+
reader.releaseLock();
136+
}
137+
138+
const bytes = new Uint8Array(totalBytes);
139+
let offset = 0;
140+
for (const chunk of chunks) {
141+
bytes.set(chunk, offset);
142+
offset += chunk.byteLength;
143+
}
144+
return bytes;
145+
};
146+
84147
export const ingestImage = async (input: {
85148
url?: string;
86149
base64?: string;
@@ -111,12 +174,7 @@ export const ingestImage = async (input: {
111174
if (!mimeType.startsWith("image/")) {
112175
throw new Error(`Unexpected image content type: ${mimeType}`);
113176
}
114-
const bytes = new Uint8Array(await response.arrayBuffer());
115-
assertMaxSize(
116-
"remote image payload",
117-
bytes.byteLength,
118-
config.maxInlineBytes
119-
);
177+
const bytes = await readBoundedImageBytes(response, config.maxInlineBytes);
120178
imagePart = {
121179
type: "image_base64",
122180
image_base64: Buffer.from(bytes).toString("base64"),

0 commit comments

Comments
 (0)