diff --git a/extensions/pixverse/video-generation-provider.test.ts b/extensions/pixverse/video-generation-provider.test.ts index 2525094ab573..0873d4307a39 100644 --- a/extensions/pixverse/video-generation-provider.test.ts +++ b/extensions/pixverse/video-generation-provider.test.ts @@ -182,6 +182,48 @@ describe("pixverse video generation provider", () => { expect(firstPostJsonRequest().body).not.toHaveProperty("seed"); }); + it("drops malformed response seed metadata", async () => { + postJsonRequestMock.mockResolvedValue({ + response: { + json: async () => ({ + ErrCode: 0, + ErrMsg: "success", + Resp: { video_id: 123 }, + }), + }, + release: vi.fn(async () => {}), + }); + fetchWithTimeoutMock.mockResolvedValueOnce({ + json: async () => ({ + ErrCode: 0, + ErrMsg: "success", + Resp: { + id: 123, + status: 1, + url: "https://media.pixverse.ai/out.mp4", + seed: 1.5, + }, + }), + headers: new Headers(), + }); + + const provider = buildPixVerseVideoGenerationProvider(); + const result = await provider.generateVideo({ + provider: "pixverse", + model: "pixverse/v6", + prompt: "a quiet city street at sunrise", + cfg: {}, + }); + + expect(result.metadata).toEqual({ + endpoint: "/video/text/generate", + videoId: 123, + status: 1, + seed: undefined, + size: undefined, + }); + }); + it("uploads local image input before submitting image-to-video", async () => { postMultipartRequestMock.mockResolvedValue({ response: { diff --git a/extensions/pixverse/video-generation-provider.ts b/extensions/pixverse/video-generation-provider.ts index 4352d2af004b..6c03a4dcf4b1 100644 --- a/extensions/pixverse/video-generation-provider.ts +++ b/extensions/pixverse/video-generation-provider.ts @@ -13,7 +13,11 @@ import { sanitizeConfiguredModelProviderRequest, type ProviderOperationDeadline, } from "openclaw/plugin-sdk/provider-http"; -import { asFiniteNumber, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime"; +import { + asFiniteNumber, + asSafeIntegerInRange, + normalizeOptionalString, +} from "openclaw/plugin-sdk/string-coerce-runtime"; import type { GeneratedVideoAsset, VideoGenerationProvider, @@ -34,6 +38,7 @@ const DEFAULT_TIMEOUT_MS = 300_000; const POLL_INTERVAL_MS = 5_000; const MAX_POLL_ATTEMPTS = 180; const MAX_DURATION_SECONDS = 15; +const PIXVERSE_SEED_MAX = 2_147_483_647; const PIXVERSE_VIDEO_MODELS = ["v6", "c1"] as const; const PIXVERSE_TEXT_ASPECT_RATIOS = [ "16:9", @@ -134,12 +139,16 @@ function appendOptionalNumber(body: Record, key: string, value: } function appendOptionalInt32Seed(body: Record, value: unknown): void { - const seed = asFiniteNumber(value); - if (seed != null && Number.isSafeInteger(seed) && seed >= 0 && seed <= 2_147_483_647) { + const seed = asSafeIntegerInRange(value, { min: 0, max: PIXVERSE_SEED_MAX }); + if (seed !== undefined) { body.seed = seed; } } +function readPixVerseSeed(value: unknown): number | undefined { + return asSafeIntegerInRange(value, { min: 0, max: PIXVERSE_SEED_MAX }); +} + function appendOptionalString(body: Record, key: string, value: unknown): void { const stringValue = normalizeOptionalString(value); if (stringValue) { @@ -498,7 +507,7 @@ export function buildPixVerseVideoGenerationProvider(): VideoGenerationProvider endpoint, videoId, status: readPixVerseStatus(completed), - seed: asFiniteNumber(completed.seed), + seed: readPixVerseSeed(completed.seed), size: asFiniteNumber(completed.size), }, };