


























@@ -31,6 +31,13 @@ type FixtureResponse = {
3131};
3232};
333334+type OversizedStreamServer = {
35+baseUrl: string;
36+closed: Promise<void>;
37+getBodyBytesSent: () => number;
38+getPlannedBodyBytes: () => number;
39+};
40+3441const servers: Array<{ close: () => Promise<void> }> = [];
35423643function createOptions(
@@ -163,6 +170,96 @@ async function startHangingErrorEmbeddingServer(): Promise<{
163170};
164171}
165172173+async function startOversizedSuccessEmbeddingServer(): Promise<OversizedStreamServer> {
174+const chunk = Buffer.alloc(64 * 1024, 0x20);
175+const prefix = Buffer.from('{"data":[');
176+const plannedBodyBytes = 64 * 1024 * 1024;
177+const sockets = new Set<Socket>();
178+let bodyBytesSent = 0;
179+let resolveClosed: () => void = () => undefined;
180+const closed = new Promise<void>((resolve) => {
181+resolveClosed = resolve;
182+});
183+const server = createServer((req: IncomingMessage, res: ServerResponse) => {
184+void (async () => {
185+await readJsonBody(req);
186+let closedAlready = false;
187+res.on("close", () => {
188+closedAlready = true;
189+resolveClosed();
190+});
191+res.writeHead(200, { "content-type": "application/json" });
192+const writeChunk = async (buffer: Buffer): Promise<boolean> => {
193+if (closedAlready) {
194+return false;
195+}
196+const accepted = res.write(buffer);
197+bodyBytesSent += buffer.byteLength;
198+if (accepted) {
199+return true;
200+}
201+return await new Promise<boolean>((resolve) => {
202+const cleanup = () => {
203+res.off("drain", onDrain);
204+res.off("close", onClose);
205+};
206+const onDrain = () => {
207+cleanup();
208+resolve(!closedAlready);
209+};
210+const onClose = () => {
211+cleanup();
212+resolve(false);
213+};
214+res.once("drain", onDrain);
215+res.once("close", onClose);
216+});
217+};
218+219+if (!(await writeChunk(prefix))) {
220+return;
221+}
222+const chunksToSend = Math.ceil((plannedBodyBytes - bodyBytesSent) / chunk.byteLength);
223+for (let i = 0; i < chunksToSend; i++) {
224+if (!(await writeChunk(chunk))) {
225+return;
226+}
227+}
228+res.end("]}");
229+})();
230+});
231+server.on("connection", (socket) => {
232+sockets.add(socket);
233+socket.on("close", () => sockets.delete(socket));
234+});
235+236+await new Promise<void>((resolve, reject) => {
237+server.once("error", reject);
238+server.listen(0, "127.0.0.1", () => {
239+server.off("error", reject);
240+resolve();
241+});
242+});
243+244+servers.push({
245+close: () =>
246+new Promise<void>((resolve, reject) => {
247+for (const socket of sockets) {
248+socket.destroy();
249+}
250+server.close((error) => (error ? reject(error) : resolve()));
251+}),
252+});
253+254+const address = server.address() as AddressInfo;
255+return {
256+baseUrl: `http://127.0.0.1:${address.port}/v1`,
257+ closed,
258+getBodyBytesSent: () => bodyBytesSent,
259+getPlannedBodyBytes: () => plannedBodyBytes,
260+};
261+}
262+166263afterEach(async () => {
167264const pending = servers.splice(0);
168265await Promise.all(pending.map((server) => server.close()));
@@ -333,6 +430,29 @@ describe("openai-compatible generic embedding provider", () => {
333430).resolves.toBe("closed");
334431});
335432433+it("bounds and cancels oversized successful embedding JSON bodies", async () => {
434+const server = await startOversizedSuccessEmbeddingServer();
435+const { provider } = await createOpenAICompatibleEmbeddingProvider(
436+createOptions({
437+model: "text-embedding-bge-m3",
438+remote: { baseUrl: server.baseUrl },
439+}),
440+);
441+442+await expect(provider.embed("hello")).rejects.toThrow(
443+"openai-compatible embeddings failed: JSON response exceeds 16777216 bytes",
444+);
445+await expect(
446+Promise.race([
447+server.closed.then(() => "closed" as const),
448+new Promise<"open">((resolve) => {
449+setTimeout(() => resolve("open"), 1_000);
450+}),
451+]),
452+).resolves.toBe("closed");
453+expect(server.getBodyBytesSent()).toBeLessThan(server.getPlannedBodyBytes() / 2);
454+});
455+336456it("resolves env SecretRef API keys on the memory search secret surface", async () => {
337457const token = "env-secret-token";
338458const envVar = "OPENCLAW_TEST_OPENAI_COMPATIBLE_EMBEDDING_API_KEY";
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。