Skip to content

Commit 2efc462

Browse files
fix(video-generation): add real-environment repro for OpenAI video submit response cap
1 parent 5b2e4bc commit 2efc462

1 file changed

Lines changed: 141 additions & 0 deletions

File tree

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Live repro for PR #96144 — proves the canonical 16 MiB provider-response
4+
* cap on `readProviderJsonResponse` (a) accepts a valid OpenAI video
5+
* submit response under the cap and (b) rejects a hostile oversized
6+
* response before OOM.
7+
*
8+
* Run: pnpm exec tsx scripts/repro/issue-96144-openai-video-cap.mjs
9+
*
10+
* The script drives the production `readProviderJsonResponse` directly
11+
* (no vitest mock) with two streaming bodies:
12+
* 1. Valid: a realistic Sora submit response shape (id/model/status/
13+
* prompt/seconds/size) under the 16 MiB cap → accepted, parsed
14+
* payload returned.
15+
* 2. Hostile: a 64 MiB streaming body → rejected with the canonical
16+
* "JSON response exceeds 16777216 bytes" error before the runtime
17+
* buffers the full body.
18+
*
19+
* A negative control shows the legacy raw `response.json()` on the
20+
* same 64 MiB body buffers the full body and only fails on JSON parse,
21+
* proving the bounded read is the right shape.
22+
*
23+
* This is the same helper that `extensions/openai/video-generation-provider.ts:427`
24+
* now routes the Sora submit response through after the PR — the helper
25+
* is provider-agnostic, so the canonical 16 MiB cap applies to OpenAI
26+
* video the same way it does to OpenAI-compatible image / DashScope
27+
* video / provider-http-errors paths.
28+
*/
29+
import assert from "node:assert/strict";
30+
import { readProviderJsonResponse } from "../../src/agents/provider-http-errors.ts";
31+
32+
const PROVIDER_JSON_RESPONSE_MAX_BYTES = 16 * 1024 * 1024; // 16 MiB
33+
const OPENAI_VIDEO_LABEL = "OpenAI video generation";
34+
35+
function createStreamingJsonResponse({ totalBytes, chunkSize, contentType = "application/json" }) {
36+
let written = 0;
37+
const encoder = new TextEncoder();
38+
const stream = new ReadableStream({
39+
pull(controller) {
40+
if (written >= totalBytes) {
41+
controller.close();
42+
return;
43+
}
44+
const remaining = totalBytes - written;
45+
const slice = Math.min(chunkSize, remaining);
46+
controller.enqueue(encoder.encode("a".repeat(slice)));
47+
written += slice;
48+
},
49+
});
50+
return new Response(stream, { status: 200, headers: { "content-type": contentType } });
51+
}
52+
53+
console.log("=== Reproduction for PR #96144 — OpenAI video submit response cap policy ===");
54+
console.log(`PROVIDER_JSON_RESPONSE_MAX_BYTES = ${PROVIDER_JSON_RESPONSE_MAX_BYTES} bytes`);
55+
console.log(`bounded-reader label = "${OPENAI_VIDEO_LABEL}"`);
56+
57+
// 1. Valid OpenAI video submit response under the cap.
58+
// Shape mirrors `OpenAIVideoResponse` (extensions/openai/video-generation-provider.ts:53):
59+
// { id, model, status, prompt, seconds, size, error }
60+
// A 4 MiB prompt payload is well below the 16 MiB cap and represents
61+
// a realistic long-prompt Sora submit envelope.
62+
const VALID_PAYLOAD_BYTES = 4 * 1024 * 1024;
63+
const validBody = {
64+
id: "video_abc123",
65+
model: "sora-1.0",
66+
status: "queued",
67+
prompt: "p".repeat(VALID_PAYLOAD_BYTES),
68+
seconds: "8",
69+
size: "1280x720",
70+
error: null,
71+
};
72+
const validJson = JSON.stringify(validBody);
73+
console.log(
74+
`Valid envelope: ${validJson.length} bytes (${(validJson.length / 1024 / 1024).toFixed(2)} MiB) ` +
75+
`id=${validBody.id} status=${validBody.status}`,
76+
);
77+
assert.ok(
78+
validJson.length < PROVIDER_JSON_RESPONSE_MAX_BYTES,
79+
`valid envelope must fit under 16 MiB; got ${validJson.length} bytes`,
80+
);
81+
const validResponse = new Response(validJson, {
82+
status: 200,
83+
headers: { "content-type": "application/json" },
84+
});
85+
const validParsed = await readProviderJsonResponse(validResponse, OPENAI_VIDEO_LABEL);
86+
assert.equal(validParsed.id, "video_abc123", "valid submit response must be accepted; id missing");
87+
assert.equal(validParsed.model, "sora-1.0", "valid submit response must preserve model field");
88+
assert.equal(validParsed.status, "queued", "valid submit response must preserve status field");
89+
assert.equal(
90+
validParsed.prompt.length,
91+
VALID_PAYLOAD_BYTES,
92+
"valid submit response must preserve full prompt payload",
93+
);
94+
console.log(`PASS valid OpenAI video submit response: accepted, id=${validParsed.id}`);
95+
96+
// 2. Hostile oversized response: 64 MiB streaming body.
97+
const OVERSIZED_BYTES = 64 * 1024 * 1024;
98+
const oversized = createStreamingJsonResponse({
99+
totalBytes: OVERSIZED_BYTES,
100+
chunkSize: 1024 * 1024,
101+
});
102+
let hostileError = null;
103+
try {
104+
await readProviderJsonResponse(oversized, OPENAI_VIDEO_LABEL);
105+
} catch (err) {
106+
hostileError = err;
107+
}
108+
assert.ok(hostileError, "hostile response must throw");
109+
assert.match(
110+
hostileError.message,
111+
/JSON response exceeds 16777216 bytes/,
112+
`hostile response must surface canonical overflow error; got: ${hostileError.message}`,
113+
);
114+
console.log(`PASS hostile 64 MiB response: rejected with "${hostileError.message}"`);
115+
116+
// 3. Negative control: raw `response.json()` on the same 64 MiB body
117+
// buffers the full body before failing on JSON parse — proves the
118+
// bounded read is the right shape (vs. an inert helper that
119+
// silently truncates and then re-fails). This is the exact code path
120+
// the PR replaces in `extensions/openai/video-generation-provider.ts:427`.
121+
const negativeBody = createStreamingJsonResponse({
122+
totalBytes: OVERSIZED_BYTES,
123+
chunkSize: 1024 * 1024,
124+
});
125+
let negativeError = null;
126+
try {
127+
await negativeBody.json();
128+
} catch (err) {
129+
negativeError = err;
130+
}
131+
assert.ok(negativeError, "raw json() must also throw on 64 MiB non-JSON body");
132+
assert.doesNotMatch(
133+
negativeError.message,
134+
/JSON response exceeds 16777216 bytes/,
135+
"raw json() must NOT surface the bounded-reader error (it doesn't go through the helper)",
136+
);
137+
console.log(
138+
`PASS negative control: raw response.json() on 64 MiB body failed with "${negativeError.constructor.name}" (no bounded-reader wrapping)`,
139+
);
140+
141+
console.log("=== All repro assertions passed ===");

0 commit comments

Comments
 (0)