






















@@ -6,6 +6,7 @@ import {
66createProviderOperationDeadline,
77createProviderOperationTimeoutResolver,
88fetchProviderDownloadResponse,
9+fetchWithTimeoutGuarded,
910pollProviderOperationJson,
1011postJsonRequest,
1112postMultipartRequest,
@@ -30,6 +31,11 @@ const MAX_POLL_ATTEMPTS = 120;
3031const OPENAI_VIDEO_SECONDS = [4, 8, 12] as const;
3132const OPENAI_VIDEO_SIZES = ["720x1280", "1280x720", "1024x1792", "1792x1024"] as const;
323334+type OpenAIVideoRequestPolicy = {
35+allowPrivateNetwork: boolean;
36+dispatcherPolicy?: Parameters<typeof postJsonRequest>[0]["dispatcherPolicy"];
37+};
38+3339type OpenAIVideoStatus = "queued" | "in_progress" | "completed" | "failed";
34403541type OpenAIVideoResponse = {
@@ -117,13 +123,15 @@ function resolveReferenceAsset(req: VideoGenerationRequest) {
117123return new File([toBlobBytes(asset.buffer)], fileName, { type: mimeType });
118124}
119125120-async function pollOpenAIVideo(params: {
121-videoId: string;
122-headers: Headers;
123-timeoutMs?: number;
124-baseUrl: string;
125-fetchFn: typeof fetch;
126-}): Promise<OpenAIVideoResponse> {
126+async function pollOpenAIVideo(
127+params: {
128+videoId: string;
129+headers: Headers;
130+timeoutMs?: number;
131+baseUrl: string;
132+fetchFn: typeof fetch;
133+} & OpenAIVideoRequestPolicy,
134+): Promise<OpenAIVideoResponse> {
127135const deadline = createProviderOperationDeadline({
128136timeoutMs: params.timeoutMs,
129137label: `OpenAI video generation task ${params.videoId}`,
@@ -138,6 +146,9 @@ async function pollOpenAIVideo(params: {
138146pollIntervalMs: POLL_INTERVAL_MS,
139147requestFailedMessage: "OpenAI video status request failed",
140148timeoutMessage: `OpenAI video generation task ${params.videoId} did not finish in time`,
149+allowPrivateNetwork: params.allowPrivateNetwork,
150+dispatcherPolicy: params.dispatcherPolicy,
151+auditContext: "openai-video-status",
141152isComplete: (payload) => payload.status === "completed",
142153getFailureMessage: (payload) =>
143154payload.status === "failed"
@@ -146,16 +157,63 @@ async function pollOpenAIVideo(params: {
146157});
147158}
148159149-async function downloadOpenAIVideo(params: {
150-videoId: string;
151-headers: Headers;
152-timeoutMs?: ProviderOperationTimeoutMs;
153-baseUrl: string;
154-fetchFn: typeof fetch;
155-}): Promise<GeneratedVideoAsset> {
160+function resolveOpenAIVideoDownloadTimeoutMs(timeoutMs: ProviderOperationTimeoutMs | undefined) {
161+const resolved = typeof timeoutMs === "function" ? timeoutMs() : timeoutMs;
162+return typeof resolved === "number" && Number.isFinite(resolved) && resolved > 0
163+ ? resolved
164+ : DEFAULT_TIMEOUT_MS;
165+}
166+167+async function fetchOpenAIVideoDownload(
168+params: {
169+url: string;
170+init: RequestInit;
171+timeoutMs?: ProviderOperationTimeoutMs;
172+fetchFn: typeof fetch;
173+} & OpenAIVideoRequestPolicy,
174+) {
175+if (!params.allowPrivateNetwork && !params.dispatcherPolicy) {
176+const response = await fetchProviderDownloadResponse({
177+url: params.url,
178+init: params.init,
179+timeoutMs: params.timeoutMs ?? DEFAULT_TIMEOUT_MS,
180+fetchFn: params.fetchFn,
181+provider: "openai",
182+requestFailedMessage: "OpenAI video download failed",
183+});
184+return {
185+ response,
186+release: async () => {},
187+};
188+}
189+190+const result = await fetchWithTimeoutGuarded(
191+params.url,
192+params.init,
193+resolveOpenAIVideoDownloadTimeoutMs(params.timeoutMs),
194+params.fetchFn,
195+{
196+ ...(params.allowPrivateNetwork ? { ssrfPolicy: { allowPrivateNetwork: true } } : {}),
197+ ...(params.dispatcherPolicy ? { dispatcherPolicy: params.dispatcherPolicy } : {}),
198+auditContext: "openai-video-download",
199+},
200+);
201+await assertOkOrThrowHttpError(result.response, "OpenAI video download failed");
202+return result;
203+}
204+205+async function downloadOpenAIVideo(
206+params: {
207+videoId: string;
208+headers: Headers;
209+timeoutMs?: ProviderOperationTimeoutMs;
210+baseUrl: string;
211+fetchFn: typeof fetch;
212+} & OpenAIVideoRequestPolicy,
213+): Promise<GeneratedVideoAsset> {
156214const url = new URL(`${params.baseUrl}/videos/${params.videoId}/content`);
157215url.searchParams.set("variant", "video");
158-const response = await fetchProviderDownloadResponse({
216+const { response, release } = await fetchOpenAIVideoDownload({
159217url: url.toString(),
160218init: {
161219method: "GET",
@@ -164,18 +222,22 @@ async function downloadOpenAIVideo(params: {
164222Accept: "application/binary",
165223}),
166224},
167-timeoutMs: params.timeoutMs ?? DEFAULT_TIMEOUT_MS,
225+timeoutMs: params.timeoutMs,
168226fetchFn: params.fetchFn,
169-provider: "openai",
170-requestFailedMessage: "OpenAI video download failed",
227+allowPrivateNetwork: params.allowPrivateNetwork,
228+dispatcherPolicy: params.dispatcherPolicy,
171229});
172-const mimeType = normalizeOptionalString(response.headers.get("content-type")) ?? "video/mp4";
173-const arrayBuffer = await response.arrayBuffer();
174-return {
175-buffer: Buffer.from(arrayBuffer),
176- mimeType,
177-fileName: `video-1.${extensionForMime(mimeType)?.slice(1) ?? "mp4"}`,
178-};
230+try {
231+const mimeType = normalizeOptionalString(response.headers.get("content-type")) ?? "video/mp4";
232+const arrayBuffer = await response.arrayBuffer();
233+return {
234+buffer: Buffer.from(arrayBuffer),
235+ mimeType,
236+fileName: `video-1.${extensionForMime(mimeType)?.slice(1) ?? "mp4"}`,
237+};
238+} finally {
239+await release();
240+}
179241}
180242181243export function buildOpenAIVideoGenerationProvider(): VideoGenerationProvider {
@@ -351,6 +413,8 @@ export function buildOpenAIVideoGenerationProvider(): VideoGenerationProvider {
351413}),
352414 baseUrl,
353415 fetchFn,
416+ allowPrivateNetwork,
417+ dispatcherPolicy,
354418});
355419const video = await downloadOpenAIVideo({
356420 videoId,
@@ -361,6 +425,8 @@ export function buildOpenAIVideoGenerationProvider(): VideoGenerationProvider {
361425}),
362426 baseUrl,
363427 fetchFn,
428+ allowPrivateNetwork,
429+ dispatcherPolicy,
364430});
365431return {
366432videos: [video],
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。