mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix: validate byteplus video seeds
This commit is contained in:
@@ -145,6 +145,23 @@ describe("byteplus video generation provider", () => {
|
||||
expect(body.camera_fixed).toBe(false);
|
||||
});
|
||||
|
||||
it("drops malformed seed values before creating videos", async () => {
|
||||
mockSuccessfulBytePlusTask({ model: "seedance-1-0-pro-250528" });
|
||||
|
||||
const provider = buildBytePlusVideoGenerationProvider();
|
||||
await provider.generateVideo({
|
||||
provider: "byteplus",
|
||||
model: "seedance-1-0-pro-250528",
|
||||
prompt: "A cinematic lobster montage",
|
||||
providerOptions: {
|
||||
seed: 1.5,
|
||||
},
|
||||
cfg: {},
|
||||
});
|
||||
|
||||
expect(requireBytePlusPostBody()).not.toHaveProperty("seed");
|
||||
});
|
||||
|
||||
it("reports malformed create JSON with a provider-owned error", async () => {
|
||||
const release = vi.fn(async () => {});
|
||||
postJsonRequestMock.mockResolvedValue({
|
||||
|
||||
@@ -13,7 +13,11 @@ import {
|
||||
waitProviderOperationPollInterval,
|
||||
type ProviderOperationTimeoutMs,
|
||||
} from "openclaw/plugin-sdk/provider-http";
|
||||
import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
asSafeIntegerInRange,
|
||||
isRecord,
|
||||
normalizeOptionalString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type {
|
||||
GeneratedVideoAsset,
|
||||
VideoGenerationProvider,
|
||||
@@ -25,6 +29,7 @@ const DEFAULT_BYTEPLUS_VIDEO_MODEL = "seedance-1-0-lite-t2v-250428";
|
||||
const DEFAULT_TIMEOUT_MS = 120_000;
|
||||
const POLL_INTERVAL_MS = 5_000;
|
||||
const MAX_POLL_ATTEMPTS = 120;
|
||||
const BYTEPLUS_SEED_MAX = 2_147_483_647;
|
||||
|
||||
type BytePlusTaskCreateResponse = {
|
||||
id?: unknown;
|
||||
@@ -116,6 +121,10 @@ function resolveBytePlusImageUrl(req: VideoGenerationRequest): string | undefine
|
||||
return toDataUrl(input.buffer, normalizeOptionalString(input.mimeType) ?? "image/png");
|
||||
}
|
||||
|
||||
function resolveBytePlusSeed(value: unknown): number | undefined {
|
||||
return asSafeIntegerInRange(value, { min: -1, max: BYTEPLUS_SEED_MAX });
|
||||
}
|
||||
|
||||
async function pollBytePlusTask(params: {
|
||||
taskId: string;
|
||||
headers: Headers;
|
||||
@@ -310,7 +319,7 @@ export function buildBytePlusVideoGenerationProvider(): VideoGenerationProvider
|
||||
// Forward declared providerOptions: seed, draft, camerafixed.
|
||||
// draft=true forces 480p resolution for faster generation.
|
||||
const opts = req.providerOptions ?? {};
|
||||
const seed = typeof opts.seed === "number" ? opts.seed : undefined;
|
||||
const seed = resolveBytePlusSeed(opts.seed);
|
||||
const draft = opts.draft === true;
|
||||
// Official JSON body field is camera_fixed (with underscore).
|
||||
const cameraFixed = typeof opts.camera_fixed === "boolean" ? opts.camera_fixed : undefined;
|
||||
|
||||
@@ -18,6 +18,7 @@ export {
|
||||
asFiniteNumberInRange,
|
||||
asFiniteNumber,
|
||||
asPositiveSafeInteger,
|
||||
asSafeIntegerInRange,
|
||||
parseFiniteNumber,
|
||||
} from "../shared/number-coercion.js";
|
||||
export { asBoolean, parseBooleanValue } from "../utils/boolean.js";
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { asFiniteNumber, asFiniteNumberInRange, parseFiniteNumber } from "./number-coercion.js";
|
||||
import {
|
||||
asFiniteNumber,
|
||||
asFiniteNumberInRange,
|
||||
asSafeIntegerInRange,
|
||||
parseFiniteNumber,
|
||||
} from "./number-coercion.js";
|
||||
|
||||
describe("number-coercion", () => {
|
||||
test("asFiniteNumber accepts only finite numbers", () => {
|
||||
@@ -17,6 +22,14 @@ describe("number-coercion", () => {
|
||||
expect(asFiniteNumberInRange("1", { min: 0, max: 2 })).toBeUndefined();
|
||||
});
|
||||
|
||||
test("asSafeIntegerInRange accepts only safe integers inside inclusive bounds", () => {
|
||||
expect(asSafeIntegerInRange(-1, { min: -1, max: 10 })).toBe(-1);
|
||||
expect(asSafeIntegerInRange(10, { min: -1, max: 10 })).toBe(10);
|
||||
expect(asSafeIntegerInRange(1.5, { min: -1, max: 10 })).toBeUndefined();
|
||||
expect(asSafeIntegerInRange(11, { min: -1, max: 10 })).toBeUndefined();
|
||||
expect(asSafeIntegerInRange(Number.NaN, { min: -1, max: 10 })).toBeUndefined();
|
||||
});
|
||||
|
||||
test("parseFiniteNumber accepts finite numbers and numeric strings", () => {
|
||||
expect(parseFiniteNumber(4)).toBe(4);
|
||||
expect(parseFiniteNumber("4.5")).toBe(4.5);
|
||||
|
||||
@@ -28,6 +28,25 @@ export function asFiniteNumberInRange(
|
||||
return number;
|
||||
}
|
||||
|
||||
export function asSafeIntegerInRange(
|
||||
value: unknown,
|
||||
range: {
|
||||
min?: number;
|
||||
max?: number;
|
||||
},
|
||||
): number | undefined {
|
||||
if (typeof value !== "number" || !Number.isSafeInteger(value)) {
|
||||
return undefined;
|
||||
}
|
||||
if (range.min !== undefined && value < range.min) {
|
||||
return undefined;
|
||||
}
|
||||
if (range.max !== undefined && value > range.max) {
|
||||
return undefined;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function parseFiniteNumber(value: unknown): number | undefined {
|
||||
if (typeof value === "number") {
|
||||
return Number.isFinite(value) ? value : undefined;
|
||||
|
||||
Reference in New Issue
Block a user