























@@ -27,27 +27,74 @@ const POLL_INTERVAL_MS = 5_000;
2727const MAX_POLL_ATTEMPTS = 120;
28282929type BytePlusTaskCreateResponse = {
30-id?: string;
30+id?: unknown;
3131};
32323333type BytePlusTaskResponse = {
34-id?: string;
35-model?: string;
36-status?: "running" | "failed" | "queued" | "succeeded" | "cancelled";
37-error?: {
38-code?: string;
39-message?: string;
40-};
41-content?: {
42-video_url?: string;
43-last_frame_url?: string;
44-file_url?: string;
45-};
46-duration?: number;
47-ratio?: string;
48-resolution?: string;
34+id?: unknown;
35+model?: unknown;
36+status?: unknown;
37+error?: unknown;
38+content?: unknown;
39+duration?: unknown;
40+ratio?: unknown;
41+resolution?: unknown;
4942};
504344+type BytePlusTaskStatus = "running" | "failed" | "queued" | "succeeded" | "cancelled";
45+46+function isRecord(value: unknown): value is Record<string, unknown> {
47+return typeof value === "object" && value !== null && !Array.isArray(value);
48+}
49+50+async function readBytePlusJsonResponse<T>(
51+response: Pick<Response, "json">,
52+label: string,
53+): Promise<T> {
54+let payload: unknown;
55+try {
56+payload = await response.json();
57+} catch (cause) {
58+throw new Error(`${label}: malformed JSON response`, { cause });
59+}
60+if (!isRecord(payload)) {
61+throw new Error(`${label}: malformed JSON response`);
62+}
63+return payload as T;
64+}
65+66+function readBytePlusTaskStatus(payload: BytePlusTaskResponse): BytePlusTaskStatus {
67+const status = normalizeOptionalString(payload.status);
68+switch (status) {
69+case "running":
70+case "failed":
71+case "queued":
72+case "succeeded":
73+case "cancelled":
74+return status;
75+case undefined:
76+throw new Error("BytePlus video status response missing task status");
77+default:
78+throw new Error(`BytePlus video status response returned unknown task status: ${status}`);
79+}
80+}
81+82+function readBytePlusErrorMessage(error: unknown): string | undefined {
83+return isRecord(error) ? normalizeOptionalString(error.message) : undefined;
84+}
85+86+function readBytePlusVideoUrl(payload: BytePlusTaskResponse): string {
87+const content = payload.content;
88+if (content !== undefined && !isRecord(content)) {
89+throw new Error("BytePlus video generation completed with malformed content");
90+}
91+const videoUrl = normalizeOptionalString(content?.video_url);
92+if (!videoUrl) {
93+throw new Error("BytePlus video generation completed without a video URL");
94+}
95+return videoUrl;
96+}
97+5198function resolveBytePlusVideoBaseUrl(req: VideoGenerationRequest): string {
5299return (
53100normalizeOptionalString(req.cfg?.models?.providers?.byteplus?.baseUrl) ?? BYTEPLUS_BASE_URL
@@ -100,14 +147,17 @@ async function pollBytePlusTask(params: {
100147provider: "byteplus",
101148requestFailedMessage: "BytePlus video status request failed",
102149});
103-const payload = (await response.json()) as BytePlusTaskResponse;
104-switch (normalizeOptionalString(payload.status)) {
150+const payload = await readBytePlusJsonResponse<BytePlusTaskResponse>(
151+response,
152+"BytePlus video status request failed",
153+);
154+switch (readBytePlusTaskStatus(payload)) {
105155case "succeeded":
106156return payload;
107157case "failed":
108158case "cancelled":
109159throw new Error(
110-normalizeOptionalString(payload.error?.message) || "BytePlus video generation failed",
160+readBytePlusErrorMessage(payload.error) || "BytePlus video generation failed",
111161);
112162case "queued":
113163case "running":
@@ -292,7 +342,10 @@ export function buildBytePlusVideoGenerationProvider(): VideoGenerationProvider
292342});
293343try {
294344await assertOkOrThrowHttpError(response, "BytePlus video generation failed");
295-const submitted = (await response.json()) as BytePlusTaskCreateResponse;
345+const submitted = await readBytePlusJsonResponse<BytePlusTaskCreateResponse>(
346+response,
347+"BytePlus video generation failed",
348+);
296349const taskId = normalizeOptionalString(submitted.id);
297350if (!taskId) {
298351throw new Error("BytePlus video generation response missing task id");
@@ -307,10 +360,7 @@ export function buildBytePlusVideoGenerationProvider(): VideoGenerationProvider
307360 baseUrl,
308361 fetchFn,
309362});
310-const videoUrl = normalizeOptionalString(completed.content?.video_url);
311-if (!videoUrl) {
312-throw new Error("BytePlus video generation completed without a video URL");
313-}
363+const videoUrl = readBytePlusVideoUrl(completed);
314364const video = await downloadBytePlusVideo({
315365url: videoUrl,
316366timeoutMs: createProviderOperationTimeoutResolver({
@@ -321,14 +371,14 @@ export function buildBytePlusVideoGenerationProvider(): VideoGenerationProvider
321371});
322372return {
323373videos: [video],
324-model: completed.model ?? resolvedModel,
374+model: normalizeOptionalString(completed.model) ?? resolvedModel,
325375metadata: {
326376 taskId,
327-status: completed.status,
377+status: normalizeOptionalString(completed.status),
328378 videoUrl,
329-ratio: completed.ratio,
330-resolution: completed.resolution,
331-duration: completed.duration,
379+ratio: normalizeOptionalString(completed.ratio),
380+resolution: normalizeOptionalString(completed.resolution),
381+duration: typeof completed.duration === "number" ? completed.duration : undefined,
332382},
333383};
334384} finally {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。