mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-21 10:16:44 +00:00
fix(google-media): bound JSON response reads (#96920)
* fix(google-media): bound JSON response reads * test(google): relax media response cap assertion --------- Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
co-authored by
Vincent Koc
parent
c5d34c8376
commit
a7bfc06f45
@@ -11,6 +11,7 @@ import {
|
||||
import {
|
||||
assertOkOrThrowProviderError,
|
||||
postJsonRequest,
|
||||
readProviderJsonResponse,
|
||||
type ProviderRequestTransportOverrides,
|
||||
} from "openclaw/plugin-sdk/provider-http";
|
||||
import {
|
||||
@@ -97,11 +98,11 @@ async function generateGeminiInlineDataText(params: {
|
||||
try {
|
||||
await assertOkOrThrowProviderError(res, params.httpErrorLabel);
|
||||
|
||||
const payload = (await res.json()) as {
|
||||
const payload = await readProviderJsonResponse<{
|
||||
candidates?: Array<{
|
||||
content?: { parts?: Array<{ text?: string }> };
|
||||
}>;
|
||||
};
|
||||
}>(res, params.httpErrorLabel);
|
||||
const parts = payload.candidates?.[0]?.content?.parts ?? [];
|
||||
const text = parts
|
||||
.map((part) => part?.text?.trim())
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Google tests cover media understanding provider.video plugin behavior.
|
||||
import { createServer, type Server } from "node:http";
|
||||
import {
|
||||
createRequestCaptureJsonFetch,
|
||||
installPinnedHostnameTestHooks,
|
||||
@@ -10,6 +11,49 @@ import { resolveGoogleGenerativeAiHttpRequestConfig } from "./runtime-api.js";
|
||||
|
||||
installPinnedHostnameTestHooks();
|
||||
|
||||
const LOOPBACK_RESPONSE_BYTES = 18 * 1024 * 1024;
|
||||
|
||||
async function listenLoopbackServer(server: Server): Promise<number> {
|
||||
return await new Promise((resolve, reject) => {
|
||||
server.once("error", reject);
|
||||
server.listen(0, "127.0.0.1", () => {
|
||||
server.off("error", reject);
|
||||
const address = server.address();
|
||||
if (!address || typeof address === "string") {
|
||||
reject(new Error("expected loopback TCP address"));
|
||||
return;
|
||||
}
|
||||
resolve(address.port);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createOversizedJsonServer(): { server: Server; closed: Promise<number> } {
|
||||
let resolveClosed: (sentBytes: number) => void = () => {};
|
||||
const closed = new Promise<number>((resolve) => {
|
||||
resolveClosed = resolve;
|
||||
});
|
||||
const server = createServer((_req, res) => {
|
||||
let sentBytes = 0;
|
||||
const chunk = Buffer.alloc(64 * 1024, 0x20);
|
||||
res.writeHead(200, { "content-type": "application/json" });
|
||||
const timer = setInterval(() => {
|
||||
if (sentBytes >= LOOPBACK_RESPONSE_BYTES) {
|
||||
clearInterval(timer);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
sentBytes += chunk.length;
|
||||
res.write(chunk);
|
||||
}, 1);
|
||||
res.on("close", () => {
|
||||
clearInterval(timer);
|
||||
resolveClosed(sentBytes);
|
||||
});
|
||||
});
|
||||
return { server, closed };
|
||||
}
|
||||
|
||||
describe("describeGeminiVideo", () => {
|
||||
it("respects case-insensitive x-goog-api-key overrides", async () => {
|
||||
let seenKey: string | null = null;
|
||||
@@ -114,6 +158,29 @@ describe("describeGeminiVideo", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("bounds oversized video JSON responses and closes the stream early", async () => {
|
||||
const { server, closed } = createOversizedJsonServer();
|
||||
const port = await listenLoopbackServer(server);
|
||||
const fetchFn = withFetchPreconnect(async () =>
|
||||
fetch(`http://127.0.0.1:${port}/google-video-json`),
|
||||
);
|
||||
|
||||
try {
|
||||
await expect(
|
||||
describeGeminiVideo({
|
||||
buffer: Buffer.from("video-bytes"),
|
||||
fileName: "clip.mp4",
|
||||
apiKey: "test-key",
|
||||
timeoutMs: 1500,
|
||||
fetchFn,
|
||||
}),
|
||||
).rejects.toThrow(/JSON response exceeds 16777216 bytes/u);
|
||||
await expect(closed).resolves.toBeLessThan(LOOPBACK_RESPONSE_BYTES);
|
||||
} finally {
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects non-Google video base URLs before sending authenticated requests", async () => {
|
||||
await expect(
|
||||
describeGeminiVideo({
|
||||
|
||||
Reference in New Issue
Block a user