Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled

Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11),
free to diverge. Tree copied sans upstream .git; upstream remote added for
future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19.
Preserves docs/ARCHITECTURE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
2026-07-05 09:36:54 +00:00
parent 3216769225
commit bedb527145
21108 changed files with 6010766 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
# @openclaw/pixverse-provider
Official PixVerse video generation provider plugin for OpenClaw.
This plugin registers PixVerse as a `video_generate` provider for text-to-video and image-to-video workflows.
## Install
```bash
openclaw plugins install @openclaw/pixverse-provider
```
Restart the Gateway after installing or updating the plugin.
## Configure
Store your PixVerse API key in OpenClaw config or expose the supported environment variable to the Gateway. Then select PixVerse as a video generation provider.
Full setup and model/provider examples:
- https://docs.openclaw.ai/providers/pixverse
## Package
- Plugin id: `pixverse`
- Package: `@openclaw/pixverse-provider`
- Minimum OpenClaw host: `2026.5.26`

View File

@@ -0,0 +1,13 @@
// Pixverse plugin module implements constants behavior.
export const PIXVERSE_PROVIDER_ID = "pixverse";
export const PIXVERSE_BASE_URL_BY_REGION = {
international: "https://app-api.pixverse.ai/openapi/v2",
cn: "https://app-api.pixverseai.cn/openapi/v2",
} as const;
export type PixVerseApiRegion = keyof typeof PIXVERSE_BASE_URL_BY_REGION;
export const DEFAULT_PIXVERSE_REGION = "international" satisfies PixVerseApiRegion;
export const DEFAULT_PIXVERSE_MODEL_ID = "v6";
export const PIXVERSE_DEFAULT_VIDEO_MODEL_REF = `${PIXVERSE_PROVIDER_ID}/${DEFAULT_PIXVERSE_MODEL_ID}`;

View File

@@ -0,0 +1,197 @@
// Pixverse tests cover index plugin behavior.
import { capturePluginRegistration } from "openclaw/plugin-sdk/plugin-test-runtime";
import { describe, expect, it, vi } from "vitest";
import {
PIXVERSE_BASE_URL_BY_REGION,
PIXVERSE_DEFAULT_VIDEO_MODEL_REF,
PIXVERSE_PROVIDER_ID,
} from "./constants.js";
import plugin from "./index.js";
import { applyPixVerseConfig, applyPixVerseProviderConfig } from "./onboard.js";
function registerPixVerseProvider() {
const captured = capturePluginRegistration(plugin);
expect(captured.videoGenerationProviders.map((provider) => provider.id)).toEqual([
PIXVERSE_PROVIDER_ID,
]);
const provider = captured.providers[0];
if (!provider) {
throw new Error("expected PixVerse setup provider");
}
expect(provider.id).toBe(PIXVERSE_PROVIDER_ID);
return provider;
}
function createRuntimeContext(region: "international" | "cn") {
const select = vi.fn(async (params: { message: string }) => {
expect(params.message).toBe("Select PixVerse API region");
return region;
});
const ctx = {
config: {
models: {
providers: {
pixverse: {
baseUrl: "https://proxy.example/openapi/v2",
models: [],
params: { quality: "720p" },
},
},
},
},
env: {},
prompter: {
intro: vi.fn(),
outro: vi.fn(),
note: vi.fn(),
select,
multiselect: vi.fn(),
text: vi.fn(async () => "pixverse-test-key"),
confirm: vi.fn(),
progress: vi.fn(() => ({
update: vi.fn(),
stop: vi.fn(),
})),
},
runtime: {
error: vi.fn(),
exit: vi.fn(),
log: vi.fn(),
},
secretInputMode: "plaintext",
isRemote: false,
openUrl: vi.fn(),
oauth: {
createVpsAwareHandlers: vi.fn(),
},
} as never;
return { ctx, select };
}
describe("pixverse plugin", () => {
it("registers provider auth for the setup wizard", () => {
const provider = registerPixVerseProvider();
const auth = provider?.auth?.[0];
expect(provider).toMatchObject({
id: PIXVERSE_PROVIDER_ID,
label: "PixVerse",
docsPath: "/providers/pixverse",
envVars: ["PIXVERSE_API_KEY"],
});
expect(auth).toMatchObject({
id: "api-key",
label: "PixVerse API key",
kind: "api_key",
wizard: {
choiceId: "pixverse-api-key",
choiceLabel: "PixVerse API key",
choiceHint: "Prompts for International or CN endpoint",
groupId: "pixverse",
groupLabel: "PixVerse",
groupHint: "Video generation",
onboardingScopes: ["image-generation"],
},
});
});
it("prompts for the PixVerse region and writes provider config", async () => {
const provider = registerPixVerseProvider();
const auth = provider?.auth?.[0];
if (!auth) {
throw new Error("expected PixVerse auth method");
}
const { ctx, select } = createRuntimeContext("cn");
const result = await auth.run(ctx);
const regionSelect = select.mock.calls[0]?.[0];
expect(regionSelect).toEqual({
message: "Select PixVerse API region",
initialValue: "international",
options: [
{
value: "international",
label: "International",
hint: PIXVERSE_BASE_URL_BY_REGION.international,
},
{
value: "cn",
label: "CN",
hint: PIXVERSE_BASE_URL_BY_REGION.cn,
},
],
});
expect(result.profiles).toEqual([
{
profileId: "pixverse:default",
credential: {
type: "api_key",
provider: PIXVERSE_PROVIDER_ID,
key: "pixverse-test-key",
},
},
]);
expect(result.configPatch?.models?.providers?.pixverse).toEqual({
baseUrl: PIXVERSE_BASE_URL_BY_REGION.cn,
models: [],
params: { quality: "720p" },
region: "cn",
});
expect(result.defaultModel).toBeUndefined();
expect(result.configPatch?.agents?.defaults?.videoGenerationModel).toEqual({
primary: PIXVERSE_DEFAULT_VIDEO_MODEL_REF,
});
expect(result.notes).toEqual([`PixVerse endpoint: CN (${PIXVERSE_BASE_URL_BY_REGION.cn})`]);
});
it("only resets custom baseUrl when a region is explicitly selected", () => {
const config = {
models: {
providers: {
pixverse: {
baseUrl: "https://proxy.example/openapi/v2",
models: [],
params: { quality: "720p" },
},
},
},
};
expect(
applyPixVerseProviderConfig(config, "international").models?.providers?.pixverse,
).toEqual({
baseUrl: "https://proxy.example/openapi/v2",
models: [],
params: { quality: "720p" },
region: "international",
});
expect(
applyPixVerseProviderConfig(config, "cn", { resetBaseUrl: true }).models?.providers?.pixverse,
).toEqual({
baseUrl: PIXVERSE_BASE_URL_BY_REGION.cn,
models: [],
params: { quality: "720p" },
region: "cn",
});
});
it("preserves an existing video generation default", () => {
const result = applyPixVerseConfig(
{
agents: {
defaults: {
videoGenerationModel: {
primary: "openai/sora-2",
},
},
},
},
"international",
);
expect(result.agents?.defaults?.videoGenerationModel).toEqual({
primary: "openai/sora-2",
});
});
});

View File

@@ -0,0 +1,21 @@
// Pixverse plugin entrypoint registers its OpenClaw integration.
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { PIXVERSE_PROVIDER_ID } from "./constants.js";
import { buildPixVerseApiKeyAuthMethod } from "./onboard.js";
import { buildPixVerseVideoGenerationProvider } from "./video-generation-provider.js";
export default definePluginEntry({
id: PIXVERSE_PROVIDER_ID,
name: "PixVerse Provider",
description: "Official external PixVerse video provider plugin",
register(api) {
api.registerProvider({
id: PIXVERSE_PROVIDER_ID,
label: "PixVerse",
docsPath: "/providers/pixverse",
envVars: ["PIXVERSE_API_KEY"],
auth: [buildPixVerseApiKeyAuthMethod()],
});
api.registerVideoGenerationProvider(buildPixVerseVideoGenerationProvider());
},
});

20
extensions/pixverse/npm-shrinkwrap.json generated Normal file
View File

@@ -0,0 +1,20 @@
{
"name": "@openclaw/pixverse-provider",
"version": "2026.6.11",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/pixverse-provider",
"version": "2026.6.11",
"peerDependencies": {
"openclaw": ">=2026.6.11"
},
"peerDependenciesMeta": {
"openclaw": {
"optional": true
}
}
}
}
}

View File

@@ -0,0 +1,253 @@
// Pixverse setup module handles plugin onboarding behavior.
import type {
ProviderAuthContext,
ProviderAuthMethod,
ProviderAuthMethodNonInteractiveContext,
} from "openclaw/plugin-sdk/plugin-entry";
import {
applyAuthProfileConfig,
buildApiKeyCredential,
ensureApiKeyFromOptionEnvOrPrompt,
normalizeApiKeyInput,
normalizeOptionalSecretInput,
type OpenClawConfig,
type SecretInput,
upsertAuthProfileWithLock,
validateApiKeyInput,
} from "openclaw/plugin-sdk/provider-auth-api-key";
import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-onboard";
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
import {
DEFAULT_PIXVERSE_REGION,
PIXVERSE_BASE_URL_BY_REGION,
PIXVERSE_DEFAULT_VIDEO_MODEL_REF,
PIXVERSE_PROVIDER_ID,
type PixVerseApiRegion,
} from "./constants.js";
const PROFILE_ID = `${PIXVERSE_PROVIDER_ID}:default`;
type PixVerseAuthResult = {
profiles: Array<{ profileId: string; credential: ReturnType<typeof buildApiKeyCredential> }>;
configPatch: OpenClawConfig;
notes: string[];
};
type UpsertAuthProfileParams = Parameters<typeof upsertAuthProfileWithLock>[0];
async function upsertAuthProfileWithLockOrThrow(params: UpsertAuthProfileParams): Promise<void> {
const updated = await upsertAuthProfileWithLock(params);
if (!updated) {
throw new Error(
"Failed to update auth profile store; the auth store lock may be busy. Wait a moment and retry.",
);
}
}
function normalizePixVerseRegion(value: unknown): PixVerseApiRegion | undefined {
const region = normalizeOptionalString(value)?.toLowerCase();
switch (region) {
case "cn":
case "china":
case "mainland":
case "pai":
return "cn";
case "global":
case "intl":
case "international":
return "international";
default:
return undefined;
}
}
function pixVerseRegionNote(region: PixVerseApiRegion): string {
const label = region === "cn" ? "CN" : "International";
return `PixVerse endpoint: ${label} (${PIXVERSE_BASE_URL_BY_REGION[region]})`;
}
export function applyPixVerseProviderConfig(
cfg: OpenClawConfig,
region: PixVerseApiRegion,
options?: { resetBaseUrl?: boolean },
): OpenClawConfig {
const existingProvider: Partial<ModelProviderConfig> =
cfg.models?.providers?.[PIXVERSE_PROVIDER_ID] ?? {};
const selectedBaseUrl = PIXVERSE_BASE_URL_BY_REGION[region];
const baseUrl = options?.resetBaseUrl
? selectedBaseUrl
: (normalizeOptionalString(existingProvider.baseUrl) ?? selectedBaseUrl);
return {
...cfg,
models: {
...cfg.models,
providers: {
...cfg.models?.providers,
[PIXVERSE_PROVIDER_ID]: {
...existingProvider,
baseUrl,
models: existingProvider.models ?? [],
region,
},
},
},
};
}
export function applyPixVerseConfig(
cfg: OpenClawConfig,
region: PixVerseApiRegion,
options?: { resetBaseUrl?: boolean },
): OpenClawConfig {
const next = applyPixVerseProviderConfig(cfg, region, options);
if (next.agents?.defaults?.videoGenerationModel) {
return next;
}
return {
...next,
agents: {
...next.agents,
defaults: {
...next.agents?.defaults,
videoGenerationModel: {
primary: PIXVERSE_DEFAULT_VIDEO_MODEL_REF,
},
},
},
};
}
async function promptForPixVerseRegion(ctx: ProviderAuthContext): Promise<PixVerseApiRegion> {
return await ctx.prompter.select<PixVerseApiRegion>({
message: "Select PixVerse API region",
initialValue: DEFAULT_PIXVERSE_REGION,
options: [
{
value: "international",
label: "International",
hint: PIXVERSE_BASE_URL_BY_REGION.international,
},
{
value: "cn",
label: "CN",
hint: PIXVERSE_BASE_URL_BY_REGION.cn,
},
],
});
}
async function runPixVerseApiKeyAuth(ctx: ProviderAuthContext): Promise<PixVerseAuthResult> {
let capturedSecretInput: SecretInput | undefined;
let capturedCredential = false;
let capturedMode: "plaintext" | "ref" | undefined;
await ensureApiKeyFromOptionEnvOrPrompt({
token:
normalizeOptionalSecretInput(ctx.opts?.pixverseApiKey) ??
normalizeOptionalSecretInput(ctx.opts?.token),
tokenProvider: normalizeOptionalSecretInput(ctx.opts?.pixverseApiKey)
? PIXVERSE_PROVIDER_ID
: normalizeOptionalSecretInput(ctx.opts?.tokenProvider),
secretInputMode:
ctx.allowSecretRefPrompt === false
? (ctx.secretInputMode ?? "plaintext")
: ctx.secretInputMode,
config: ctx.config,
env: ctx.env,
expectedProviders: [PIXVERSE_PROVIDER_ID],
provider: PIXVERSE_PROVIDER_ID,
envLabel: "PIXVERSE_API_KEY",
promptMessage: "Enter PixVerse API key",
normalize: normalizeApiKeyInput,
validate: validateApiKeyInput,
prompter: ctx.prompter,
setCredential: async (apiKey, mode) => {
capturedSecretInput = apiKey;
capturedCredential = true;
capturedMode = mode;
},
});
if (!capturedCredential) {
throw new Error("Missing PixVerse API key.");
}
const region = await promptForPixVerseRegion(ctx);
return {
profiles: [
{
profileId: PROFILE_ID,
credential: buildApiKeyCredential(
PIXVERSE_PROVIDER_ID,
capturedSecretInput ?? "",
undefined,
capturedMode
? {
secretInputMode: capturedMode,
config: ctx.config,
}
: undefined,
),
},
],
configPatch: applyPixVerseConfig(ctx.config, region, { resetBaseUrl: true }),
notes: [pixVerseRegionNote(region)],
};
}
async function runPixVerseApiKeyAuthNonInteractive(ctx: ProviderAuthMethodNonInteractiveContext) {
const resolved = await ctx.resolveApiKey({
provider: PIXVERSE_PROVIDER_ID,
flagValue: normalizeOptionalSecretInput(ctx.opts.pixverseApiKey),
flagName: "--pixverse-api-key",
envVar: "PIXVERSE_API_KEY",
});
if (!resolved) {
return null;
}
if (resolved.source !== "profile") {
const credential = ctx.toApiKeyCredential({
provider: PIXVERSE_PROVIDER_ID,
resolved,
});
if (!credential) {
return null;
}
await upsertAuthProfileWithLockOrThrow({
profileId: PROFILE_ID,
credential,
agentDir: ctx.agentDir,
});
}
const next = applyAuthProfileConfig(ctx.config, {
profileId: PROFILE_ID,
provider: PIXVERSE_PROVIDER_ID,
mode: "api_key",
});
const explicitRegion = normalizePixVerseRegion(ctx.opts.pixverseRegion);
return applyPixVerseConfig(next, explicitRegion ?? DEFAULT_PIXVERSE_REGION, {
resetBaseUrl: explicitRegion !== undefined,
});
}
export function buildPixVerseApiKeyAuthMethod(): ProviderAuthMethod {
return {
id: "api-key",
label: "PixVerse API key",
hint: "Video generation API key",
kind: "api_key",
wizard: {
choiceId: "pixverse-api-key",
choiceLabel: "PixVerse API key",
choiceHint: "Prompts for International or CN endpoint",
groupId: "pixverse",
groupLabel: "PixVerse",
groupHint: "Video generation",
onboardingScopes: ["image-generation"],
},
run: runPixVerseApiKeyAuth,
runNonInteractive: runPixVerseApiKeyAuthNonInteractive,
};
}

View File

@@ -0,0 +1,37 @@
{
"id": "pixverse",
"name": "PixVerse",
"description": "OpenClaw PixVerse video generation provider plugin.",
"activation": {
"onStartup": false
},
"enabledByDefault": true,
"providerAuthEnvVars": {
"pixverse": ["PIXVERSE_API_KEY"]
},
"providerAuthChoices": [
{
"provider": "pixverse",
"method": "api-key",
"choiceId": "pixverse-api-key",
"choiceLabel": "PixVerse API key",
"choiceHint": "Wizard prompts for International or CN endpoint.",
"groupId": "pixverse",
"groupLabel": "PixVerse",
"groupHint": "Video generation",
"onboardingScopes": ["image-generation"],
"optionKey": "pixverseApiKey",
"cliFlag": "--pixverse-api-key",
"cliOption": "--pixverse-api-key <key>",
"cliDescription": "PixVerse API key"
}
],
"contracts": {
"videoGenerationProviders": ["pixverse"]
},
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {}
}
}

View File

@@ -0,0 +1,44 @@
{
"name": "@openclaw/pixverse-provider",
"version": "2026.6.11",
"description": "OpenClaw PixVerse video generation provider plugin.",
"repository": {
"type": "git",
"url": "https://github.com/openclaw/openclaw"
},
"type": "module",
"devDependencies": {
"@openclaw/plugin-sdk": "workspace:*",
"openclaw": "workspace:*"
},
"peerDependencies": {
"openclaw": ">=2026.6.11"
},
"peerDependenciesMeta": {
"openclaw": {
"optional": true
}
},
"openclaw": {
"extensions": [
"./index.ts"
],
"install": {
"clawhubSpec": "clawhub:@openclaw/pixverse-provider",
"npmSpec": "@openclaw/pixverse-provider",
"defaultChoice": "npm",
"minHostVersion": ">=2026.5.26"
},
"compat": {
"pluginApi": ">=2026.6.11"
},
"build": {
"openclawVersion": "2026.6.11",
"bundledDist": false
},
"release": {
"publishToClawHub": true,
"publishToNpm": true
}
}
}

View File

@@ -0,0 +1,16 @@
{
"extends": "../tsconfig.package-boundary.base.json",
"compilerOptions": {
"rootDir": "."
},
"include": ["./*.ts", "./src/**/*.ts"],
"exclude": [
"./**/*.test.ts",
"./dist/**",
"./node_modules/**",
"./src/test-support/**",
"./src/**/*test-helpers.ts",
"./src/**/*test-harness.ts",
"./src/**/*test-support.ts"
]
}

View File

@@ -0,0 +1,735 @@
// Pixverse tests cover video generation provider plugin behavior.
import {
getProviderHttpMocks,
installProviderHttpMockCleanup,
} from "openclaw/plugin-sdk/provider-http-test-mocks";
import { expectExplicitVideoGenerationCapabilities } from "openclaw/plugin-sdk/provider-test-contracts";
import { beforeAll, describe, expect, it, vi } from "vitest";
const {
postJsonRequestMock,
postMultipartRequestMock,
fetchWithTimeoutMock,
pollProviderOperationJsonMock,
resolveProviderHttpRequestConfigMock,
sanitizeConfiguredModelProviderRequestMock,
} = getProviderHttpMocks();
let buildPixVerseVideoGenerationProvider: typeof import("./video-generation-provider.js").buildPixVerseVideoGenerationProvider;
beforeAll(async () => {
({ buildPixVerseVideoGenerationProvider } = await import("./video-generation-provider.js"));
});
installProviderHttpMockCleanup();
function firstPostJsonRequest() {
const [call] = postJsonRequestMock.mock.calls;
if (!call) {
throw new Error("expected PixVerse create request");
}
return call[0] as { url?: string; body?: Record<string, unknown>; headers?: Headers };
}
function firstMultipartRequest() {
const [call] = postMultipartRequestMock.mock.calls;
if (!call) {
throw new Error("expected PixVerse image upload request");
}
return call[0] as { url?: string; body?: FormData; headers?: Headers };
}
function firstPollRequest() {
const [call] = pollProviderOperationJsonMock.mock.calls;
if (!call) {
throw new Error("expected PixVerse status poll request");
}
return call[0] as {
url?: string;
allowPrivateNetwork?: boolean;
dispatcherPolicy?: unknown;
};
}
function pollFetchHeaders(callIndex: number): Headers | undefined {
const [, init] = fetchWithTimeoutMock.mock.calls[callIndex] ?? [];
return (init as { headers?: Headers } | undefined)?.headers;
}
function streamedJsonResponse(payload: unknown): Response {
return new Response(
new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode(JSON.stringify(payload)));
controller.close();
},
}),
{ headers: { "content-type": "application/json" } },
);
}
// Drives an unbounded JSON body (>16 MiB, no Content-Length) so the bounded
// reader has to cancel the stream instead of buffering it all. A hard ceiling
// guards the test from hanging if the reader ever fails to cancel.
function oversizedJsonResponse(): {
response: Response;
state: { canceled: boolean; enqueuedBytes: number };
} {
const state = { canceled: false, enqueuedBytes: 0 };
const chunk = 1024 * 1024;
const maxChunks = 64; // 64 MiB ceiling, 4x the 16 MiB cap.
let emitted = 0;
const response = new Response(
new ReadableStream({
pull(controller) {
if (emitted >= maxChunks) {
controller.close();
return;
}
emitted += 1;
state.enqueuedBytes += chunk;
controller.enqueue(new Uint8Array(chunk));
},
cancel() {
state.canceled = true;
},
}),
{ headers: { "content-type": "application/json" } },
);
return { response, state };
}
describe("pixverse video generation provider", () => {
it("declares explicit mode capabilities", () => {
expectExplicitVideoGenerationCapabilities(buildPixVerseVideoGenerationProvider());
});
it("submits text-to-video, polls status, and returns the output URL", async () => {
postJsonRequestMock.mockResolvedValue({
response: streamedJsonResponse({
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: 42,
outputWidth: 960,
outputHeight: 540,
},
}),
headers: new Headers(),
});
const provider = buildPixVerseVideoGenerationProvider();
const result = await provider.generateVideo({
provider: "pixverse",
model: "pixverse/v6",
prompt: "a tiny lobster DJ under neon lights",
cfg: {},
durationSeconds: 4,
aspectRatio: "21:9",
resolution: "720P",
audio: true,
providerOptions: {
seed: 42,
negativePrompt: "blur",
cameraMovement: "zoom_in",
},
});
expect(postJsonRequestMock).toHaveBeenCalledTimes(1);
const createRequest = firstPostJsonRequest();
expect(createRequest.url).toBe("https://app-api.pixverse.ai/openapi/v2/video/text/generate");
expect(createRequest.body).toEqual({
duration: 4,
model: "v6",
prompt: "a tiny lobster DJ under neon lights",
quality: "720p",
aspect_ratio: "21:9",
negative_prompt: "blur",
camera_movement: "zoom_in",
seed: 42,
generate_audio_switch: true,
});
expect(createRequest.headers?.get("API-KEY")).toBe("provider-key");
expect(createRequest.headers?.get("Ai-trace-id")).toMatch(/^[0-9a-f-]{36}$/u);
expect(fetchWithTimeoutMock).toHaveBeenCalledTimes(1);
expect(fetchWithTimeoutMock.mock.calls[0]?.[0]).toBe(
"https://app-api.pixverse.ai/openapi/v2/video/result/123",
);
expect(result).toEqual({
videos: [
{
url: "https://media.pixverse.ai/out.mp4",
mimeType: "video/mp4",
fileName: "video-1.mp4",
metadata: {
sourceUrl: "https://media.pixverse.ai/out.mp4",
outputWidth: 960,
outputHeight: 540,
},
},
],
model: "v6",
metadata: {
endpoint: "/video/text/generate",
videoId: 123,
status: 1,
seed: 42,
size: undefined,
},
});
});
it("drops malformed seed values before creating videos", async () => {
postJsonRequestMock.mockResolvedValue({
response: streamedJsonResponse({
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" },
}),
headers: new Headers(),
});
const provider = buildPixVerseVideoGenerationProvider();
await provider.generateVideo({
provider: "pixverse",
model: "pixverse/v6",
prompt: "a quiet city street at sunrise",
cfg: {},
providerOptions: {
seed: 1.5,
},
});
expect(firstPostJsonRequest().body).not.toHaveProperty("seed");
});
it("drops malformed response seed metadata", async () => {
postJsonRequestMock.mockResolvedValue({
response: streamedJsonResponse({
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("rejects fractional video ids before polling", async () => {
postJsonRequestMock.mockResolvedValue({
response: streamedJsonResponse({
ErrCode: 0,
ErrMsg: "success",
Resp: { video_id: 123.5 },
}),
release: vi.fn(async () => {}),
});
const provider = buildPixVerseVideoGenerationProvider();
await expect(
provider.generateVideo({
provider: "pixverse",
model: "pixverse/v6",
prompt: "a quiet city street at sunrise",
cfg: {},
}),
).rejects.toThrow("PixVerse video generation response missing video_id");
expect(fetchWithTimeoutMock).not.toHaveBeenCalled();
});
it("bounds an unbounded successful PixVerse create JSON body and cancels the stream", async () => {
const oversized = oversizedJsonResponse();
postJsonRequestMock.mockResolvedValue({
response: oversized.response,
release: vi.fn(async () => {}),
});
const provider = buildPixVerseVideoGenerationProvider();
await expect(
provider.generateVideo({
provider: "pixverse",
model: "pixverse/v6",
prompt: "oversized create body",
cfg: {},
}),
).rejects.toThrow("PixVerse video generation failed: JSON response exceeds 16777216 bytes");
// The bounded reader cancelled the stream rather than buffering the whole
// body, and stopped reading well before the 64 MiB ceiling.
expect(oversized.state.canceled).toBe(true);
expect(oversized.state.enqueuedBytes).toBeLessThan(64 * 1024 * 1024);
expect(fetchWithTimeoutMock).not.toHaveBeenCalled();
});
it("bounds an unbounded successful PixVerse image upload JSON body and cancels the stream", async () => {
const oversized = oversizedJsonResponse();
postMultipartRequestMock.mockResolvedValue({
response: oversized.response,
release: vi.fn(async () => {}),
});
const provider = buildPixVerseVideoGenerationProvider();
await expect(
provider.generateVideo({
provider: "pixverse",
model: "c1",
prompt: "oversized upload body",
cfg: {},
inputImages: [{ buffer: Buffer.from("png-bytes"), mimeType: "image/png" }],
}),
).rejects.toThrow("PixVerse image upload failed: JSON response exceeds 16777216 bytes");
expect(oversized.state.canceled).toBe(true);
expect(oversized.state.enqueuedBytes).toBeLessThan(64 * 1024 * 1024);
expect(postJsonRequestMock).not.toHaveBeenCalled();
});
it("uploads local image input before submitting image-to-video", async () => {
postMultipartRequestMock.mockResolvedValue({
response: streamedJsonResponse({
ErrCode: 0,
ErrMsg: "success",
Resp: { img_id: 456, img_url: "https://media.pixverse.ai/image.png" },
}),
release: vi.fn(async () => {}),
});
postJsonRequestMock.mockResolvedValue({
response: streamedJsonResponse({
ErrCode: 0,
ErrMsg: "success",
Resp: { video_id: 789 },
}),
release: vi.fn(async () => {}),
});
fetchWithTimeoutMock.mockResolvedValueOnce({
json: async () => ({
ErrCode: 0,
ErrMsg: "success",
Resp: { id: 789, status: 1, url: "https://media.pixverse.ai/i2v.mp4" },
}),
headers: new Headers(),
});
const provider = buildPixVerseVideoGenerationProvider();
await provider.generateVideo({
provider: "pixverse",
model: "c1",
prompt: "animate the product",
cfg: {},
inputImages: [{ buffer: Buffer.from("png-bytes"), mimeType: "image/png" }],
durationSeconds: 99,
providerOptions: {
motionMode: "fast",
templateId: 302325299692608,
},
});
expect(postMultipartRequestMock).toHaveBeenCalledOnce();
const uploadRequest = firstMultipartRequest();
expect(uploadRequest.url).toBe("https://app-api.pixverse.ai/openapi/v2/image/upload");
expect(uploadRequest.headers?.get("Content-Type")).toBeNull();
expect(uploadRequest.body?.get("image")).toBeInstanceOf(File);
expect(postJsonRequestMock).toHaveBeenCalledOnce();
const createRequest = firstPostJsonRequest();
expect(createRequest.url).toBe("https://app-api.pixverse.ai/openapi/v2/video/img/generate");
expect(createRequest.body).toEqual({
duration: 15,
model: "c1",
prompt: "animate the product",
quality: "540p",
img_id: 456,
motion_mode: "fast",
template_id: 302325299692608,
});
});
it("uploads remote image URLs through PixVerse image upload", async () => {
postMultipartRequestMock.mockResolvedValue({
response: streamedJsonResponse({
ErrCode: 0,
ErrMsg: "success",
Resp: { img_id: 111 },
}),
release: vi.fn(async () => {}),
});
postJsonRequestMock.mockResolvedValue({
response: streamedJsonResponse({
ErrCode: 0,
ErrMsg: "success",
Resp: { video_id: 222 },
}),
release: vi.fn(async () => {}),
});
fetchWithTimeoutMock.mockResolvedValueOnce({
json: async () => ({
ErrCode: 0,
ErrMsg: "success",
Resp: { id: 222, status: 1, url: "https://media.pixverse.ai/remote.mp4" },
}),
headers: new Headers(),
});
const provider = buildPixVerseVideoGenerationProvider();
await provider.generateVideo({
provider: "pixverse",
model: "v6",
prompt: "animate the remote image",
cfg: {},
inputImages: [{ url: "https://example.com/input.png" }],
});
const uploadRequest = firstMultipartRequest();
expect(uploadRequest.body?.get("image_url")).toBe("https://example.com/input.png");
const createRequest = firstPostJsonRequest();
expect(createRequest.body?.img_id).toBe(111);
});
it("rejects PixVerse API errors before polling", async () => {
postJsonRequestMock.mockResolvedValue({
response: streamedJsonResponse({
ErrCode: 400017,
ErrMsg: "Invalid parameter",
}),
release: vi.fn(async () => {}),
});
const provider = buildPixVerseVideoGenerationProvider();
await expect(
provider.generateVideo({
provider: "pixverse",
model: "v6",
prompt: "bad request",
cfg: {},
}),
).rejects.toThrow("PixVerse video generation failed: Invalid parameter");
expect(fetchWithTimeoutMock).not.toHaveBeenCalled();
});
it("reports PixVerse moderation failures from status polling", async () => {
postJsonRequestMock.mockResolvedValue({
response: streamedJsonResponse({
ErrCode: 0,
ErrMsg: "success",
Resp: { video_id: 333 },
}),
release: vi.fn(async () => {}),
});
fetchWithTimeoutMock.mockResolvedValueOnce({
json: async () => ({
ErrCode: 0,
ErrMsg: "success",
Resp: { id: 333, status: 7 },
}),
headers: new Headers(),
});
const provider = buildPixVerseVideoGenerationProvider();
await expect(
provider.generateVideo({
provider: "pixverse",
model: "v6",
prompt: "moderated request",
cfg: {},
}),
).rejects.toThrow("PixVerse video generation failed content moderation");
});
it("uses configured baseUrl", async () => {
postJsonRequestMock.mockResolvedValue({
response: streamedJsonResponse({
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" },
}),
headers: new Headers(),
});
const provider = buildPixVerseVideoGenerationProvider();
await provider.generateVideo({
provider: "pixverse",
model: "v6",
prompt: "custom base",
cfg: {
models: {
providers: {
pixverse: {
baseUrl: "https://proxy.example/openapi/v2",
},
},
},
} as never,
});
expect(firstPostJsonRequest().url).toBe("https://proxy.example/openapi/v2/video/text/generate");
expect(fetchWithTimeoutMock.mock.calls[0]?.[0]).toBe(
"https://proxy.example/openapi/v2/video/result/123",
);
});
it("uses the guarded provider transport for status polling", async () => {
const dispatcherPolicy = { mode: "direct" };
resolveProviderHttpRequestConfigMock.mockReturnValueOnce({
baseUrl: "https://proxy.example/openapi/v2",
allowPrivateNetwork: true,
headers: new Headers({ "API-KEY": "provider-key", "X-Proxy": "enabled" }),
dispatcherPolicy,
} as never);
postJsonRequestMock.mockResolvedValue({
response: streamedJsonResponse({
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" },
}),
headers: new Headers(),
});
const provider = buildPixVerseVideoGenerationProvider();
await provider.generateVideo({
provider: "pixverse",
model: "v6",
prompt: "custom base",
cfg: {},
});
expect(firstPostJsonRequest().url).toBe("https://proxy.example/openapi/v2/video/text/generate");
expect(firstPostJsonRequest().headers?.get("X-Proxy")).toBe("enabled");
expect(firstPollRequest()).toMatchObject({
url: "https://proxy.example/openapi/v2/video/result/123",
allowPrivateNetwork: true,
dispatcherPolicy,
});
const pollHeaders = pollFetchHeaders(0);
expect(pollHeaders?.get("X-Proxy")).toBe("enabled");
});
it("passes configured provider request overrides into the HTTP resolver", async () => {
const request = {
allowPrivateNetwork: true,
headers: { "X-Proxy": "enabled" },
};
postJsonRequestMock.mockResolvedValue({
response: streamedJsonResponse({
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" },
}),
headers: new Headers(),
});
const provider = buildPixVerseVideoGenerationProvider();
await provider.generateVideo({
provider: "pixverse",
model: "v6",
prompt: "custom request config",
cfg: {
models: {
providers: {
pixverse: {
request,
},
},
},
} as never,
});
expect(sanitizeConfiguredModelProviderRequestMock).toHaveBeenCalledWith(request);
expect(resolveProviderHttpRequestConfigMock).toHaveBeenCalledWith(
expect.objectContaining({ request }),
);
});
it("uses a fresh trace id for each status poll", async () => {
postJsonRequestMock.mockResolvedValue({
response: streamedJsonResponse({
ErrCode: 0,
ErrMsg: "success",
Resp: { video_id: 123 },
}),
release: vi.fn(async () => {}),
});
fetchWithTimeoutMock
.mockResolvedValueOnce({
json: async () => ({
ErrCode: 0,
ErrMsg: "success",
Resp: { id: 123, status: 5 },
}),
headers: new Headers(),
})
.mockResolvedValueOnce({
json: async () => ({
ErrCode: 0,
ErrMsg: "success",
Resp: { id: 123, status: 1, url: "https://media.pixverse.ai/out.mp4" },
}),
headers: new Headers(),
});
const provider = buildPixVerseVideoGenerationProvider();
await provider.generateVideo({
provider: "pixverse",
model: "v6",
prompt: "fresh trace ids",
cfg: {},
});
const firstHeaders = pollFetchHeaders(0);
const secondHeaders = pollFetchHeaders(1);
expect(firstHeaders?.get("Ai-trace-id")).toMatch(/^[0-9a-f-]{36}$/u);
expect(secondHeaders?.get("Ai-trace-id")).toMatch(/^[0-9a-f-]{36}$/u);
expect(secondHeaders?.get("Ai-trace-id")).not.toBe(firstHeaders?.get("Ai-trace-id"));
});
it("uses the configured CN API region", async () => {
postJsonRequestMock.mockResolvedValue({
response: streamedJsonResponse({
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" },
}),
headers: new Headers(),
});
const provider = buildPixVerseVideoGenerationProvider();
await provider.generateVideo({
provider: "pixverse",
model: "v6",
prompt: "cn endpoint",
cfg: {
models: {
providers: {
pixverse: {
region: "cn",
},
},
},
} as never,
});
expect(firstPostJsonRequest().url).toBe(
"https://app-api.pixverseai.cn/openapi/v2/video/text/generate",
);
expect(fetchWithTimeoutMock.mock.calls[0]?.[0]).toBe(
"https://app-api.pixverseai.cn/openapi/v2/video/result/123",
);
});
it("prefers configured baseUrl over API region", async () => {
postJsonRequestMock.mockResolvedValue({
response: streamedJsonResponse({
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" },
}),
headers: new Headers(),
});
const provider = buildPixVerseVideoGenerationProvider();
await provider.generateVideo({
provider: "pixverse",
model: "v6",
prompt: "custom base",
cfg: {
models: {
providers: {
pixverse: {
baseUrl: "https://proxy.example/openapi/v2",
region: "cn",
},
},
},
} as never,
});
expect(firstPostJsonRequest().url).toBe("https://proxy.example/openapi/v2/video/text/generate");
expect(fetchWithTimeoutMock.mock.calls[0]?.[0]).toBe(
"https://proxy.example/openapi/v2/video/result/123",
);
});
});

View File

@@ -0,0 +1,521 @@
// Pixverse provider module implements model/runtime integration.
import { randomUUID } from "node:crypto";
import { extensionForMime } from "openclaw/plugin-sdk/media-mime";
import { isProviderApiKeyConfigured } from "openclaw/plugin-sdk/provider-auth";
import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/provider-auth-runtime";
import {
assertOkOrThrowHttpError,
createProviderOperationDeadline,
pollProviderOperationJson,
postJsonRequest,
postMultipartRequest,
readProviderJsonResponse,
resolveProviderOperationTimeoutMs,
resolveProviderHttpRequestConfig,
sanitizeConfiguredModelProviderRequest,
type ProviderOperationDeadline,
} from "openclaw/plugin-sdk/provider-http";
import {
asFiniteNumber,
asSafeIntegerInRange,
normalizeOptionalString,
} from "openclaw/plugin-sdk/string-coerce-runtime";
import type {
GeneratedVideoAsset,
VideoGenerationProvider,
VideoGenerationRequest,
VideoGenerationSourceAsset,
} from "openclaw/plugin-sdk/video-generation";
import {
DEFAULT_PIXVERSE_MODEL_ID,
DEFAULT_PIXVERSE_REGION,
PIXVERSE_BASE_URL_BY_REGION,
PIXVERSE_PROVIDER_ID,
type PixVerseApiRegion,
} from "./constants.js";
const DEFAULT_PIXVERSE_BASE_URL = PIXVERSE_BASE_URL_BY_REGION[DEFAULT_PIXVERSE_REGION];
const DEFAULT_PIXVERSE_QUALITY = "540p";
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",
"4:3",
"1:1",
"3:4",
"9:16",
"2:3",
"3:2",
"21:9",
] as const;
const PIXVERSE_QUALITIES = ["360p", "540p", "720p", "1080p"] as const;
type PixVerseEnvelope<T> = {
ErrCode?: unknown;
ErrMsg?: unknown;
Resp?: T;
};
type PixVerseUploadImageResponse = {
img_id?: unknown;
img_url?: unknown;
};
type PixVerseVideoCreateResponse = {
video_id?: unknown;
};
type PixVerseVideoResultResponse = {
id?: unknown;
status?: unknown;
url?: unknown;
outputWidth?: unknown;
outputHeight?: unknown;
seed?: unknown;
size?: unknown;
};
function resolvePixVerseBaseUrl(req: VideoGenerationRequest): string {
const provider = req.cfg?.models?.providers?.[PIXVERSE_PROVIDER_ID];
const configuredBaseUrl = normalizeOptionalString(provider?.baseUrl);
if (configuredBaseUrl) {
return configuredBaseUrl;
}
const region = resolvePixVerseApiRegion(provider?.region);
return PIXVERSE_BASE_URL_BY_REGION[region];
}
function resolvePixVerseApiRegion(value: unknown): PixVerseApiRegion {
const region = normalizeOptionalString(value)?.toLowerCase();
switch (region) {
case "cn":
case "china":
case "mainland":
case "pai":
return "cn";
case "global":
case "intl":
case "international":
case undefined:
return DEFAULT_PIXVERSE_REGION;
default:
throw new Error(`Unsupported PixVerse API region "${region}". Use "international" or "cn".`);
}
}
function normalizePixVerseModel(model: string | undefined): string {
const normalized = normalizeOptionalString(model)?.replace(/^pixverse\//iu, "");
return normalized?.toLowerCase() || DEFAULT_PIXVERSE_MODEL_ID;
}
function resolvePixVerseQuality(req: VideoGenerationRequest): string {
const optionQuality =
normalizeOptionalString(req.providerOptions?.quality) ??
normalizeOptionalString(req.resolution);
const requested = optionQuality ?? normalizeOptionalString(req.size);
if (!requested) {
return DEFAULT_PIXVERSE_QUALITY;
}
const normalized = requested.toLowerCase() === "480p" ? "540p" : requested.toLowerCase();
return PIXVERSE_QUALITIES.includes(normalized as (typeof PIXVERSE_QUALITIES)[number])
? normalized
: DEFAULT_PIXVERSE_QUALITY;
}
function resolvePixVerseDurationSeconds(value: number | undefined): number {
if (typeof value !== "number" || !Number.isFinite(value)) {
return 5;
}
return Math.max(1, Math.min(MAX_DURATION_SECONDS, Math.round(value)));
}
function appendOptionalNumber(body: Record<string, unknown>, key: string, value: unknown): void {
const numberValue = asFiniteNumber(value);
if (numberValue != null) {
body[key] = numberValue;
}
}
function appendOptionalInt32Seed(body: Record<string, unknown>, value: unknown): void {
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<string, unknown>, key: string, value: unknown): void {
const stringValue = normalizeOptionalString(value);
if (stringValue) {
body[key] = stringValue;
}
}
function buildPixVerseHeaders(headers: Headers, contentType?: string): Headers {
const next = new Headers(headers);
next.set("Ai-trace-id", randomUUID());
if (contentType) {
next.set("Content-Type", contentType);
} else {
next.delete("Content-Type");
}
return next;
}
function readPixVerseSuccess<T>(payload: PixVerseEnvelope<T>, label: string): T {
if (!payload || typeof payload !== "object") {
throw new Error(`${label}: malformed JSON response`);
}
const code = asFiniteNumber(payload.ErrCode);
if (code !== 0) {
const message = normalizeOptionalString(payload.ErrMsg) ?? `ErrCode ${String(payload.ErrCode)}`;
throw new Error(`${label}: ${message}`);
}
if (payload.Resp === undefined || payload.Resp === null) {
throw new Error(`${label}: response missing Resp`);
}
return payload.Resp;
}
// Reads a PixVerse JSON response through the shared provider JSON reader so a
// provider that streams an unbounded body cannot force the runtime to buffer the
// whole payload before parsing it on the success path. The shared helper applies
// the established 16 MiB provider JSON cap and the standard malformed-JSON
// wrapping; PixVerse envelope validation stays local via readPixVerseSuccess.
async function readPixVerseJson<T>(response: Response, label: string): Promise<T> {
const payload = await readProviderJsonResponse(response, label);
return readPixVerseSuccess(payload as PixVerseEnvelope<T>, label);
}
function readPixVerseVideoId(payload: PixVerseVideoCreateResponse): number {
const videoId = asSafeIntegerInRange(payload.video_id, { min: 0 });
if (videoId == null) {
throw new Error("PixVerse video generation response missing video_id");
}
return videoId;
}
function readPixVerseImageId(payload: PixVerseUploadImageResponse): number {
const imageId = asSafeIntegerInRange(payload.img_id, { min: 0 });
if (imageId == null) {
throw new Error("PixVerse image upload response missing img_id");
}
return imageId;
}
function readPixVerseStatus(payload: PixVerseVideoResultResponse): number {
const status = asSafeIntegerInRange(payload.status, { min: 0 });
if (status == null) {
throw new Error("PixVerse video status response missing status");
}
return status;
}
function buildUploadImageForm(asset: VideoGenerationSourceAsset): FormData {
const form = new FormData();
const url = normalizeOptionalString(asset.url);
if (url) {
form.set("image_url", url);
return form;
}
if (!asset.buffer) {
throw new Error("PixVerse image-to-video input is missing image data.");
}
const mimeType = normalizeOptionalString(asset.mimeType) ?? "image/png";
const extension = extensionForMime(mimeType)?.slice(1) ?? "png";
const fileName = normalizeOptionalString(asset.fileName) ?? `image.${extension}`;
const bytes = new Uint8Array(asset.buffer.byteLength);
bytes.set(asset.buffer);
form.set("image", new File([bytes], fileName, { type: mimeType }));
return form;
}
function buildVideoBody(
req: VideoGenerationRequest,
model: string,
imageId?: number,
): Record<string, unknown> {
const options = req.providerOptions ?? {};
const body: Record<string, unknown> = {
duration: resolvePixVerseDurationSeconds(req.durationSeconds),
model,
prompt: req.prompt,
quality: resolvePixVerseQuality(req),
};
if (imageId !== undefined) {
body.img_id = imageId;
body.motion_mode =
normalizeOptionalString(options.motion_mode) ??
normalizeOptionalString(options.motionMode) ??
"normal";
} else {
body.aspect_ratio = normalizeOptionalString(req.aspectRatio) ?? "16:9";
}
appendOptionalString(
body,
"negative_prompt",
normalizeOptionalString(options.negative_prompt) ??
normalizeOptionalString(options.negativePrompt),
);
appendOptionalString(
body,
"camera_movement",
normalizeOptionalString(options.camera_movement) ??
normalizeOptionalString(options.cameraMovement),
);
appendOptionalNumber(
body,
"template_id",
asFiniteNumber(options.template_id) ?? asFiniteNumber(options.templateId),
);
appendOptionalInt32Seed(body, options.seed);
if (req.audio !== undefined) {
body.generate_audio_switch = req.audio;
}
return body;
}
function readPixVerseFailureMessage(payload: PixVerseVideoResultResponse): string | undefined {
switch (readPixVerseStatus(payload)) {
case 7:
return "PixVerse video generation failed content moderation";
case 8:
return "PixVerse video generation failed";
case 6:
return "PixVerse video generation was deleted before completion";
default:
return undefined;
}
}
async function pollPixVerseVideo(params: {
videoId: number;
baseUrl: string;
deadline: ProviderOperationDeadline;
fetchFn: typeof fetch;
allowPrivateNetwork: boolean;
dispatcherPolicy?: Parameters<typeof postJsonRequest>[0]["dispatcherPolicy"];
headers: Headers;
}): Promise<PixVerseVideoResultResponse> {
const readResult = (payload: PixVerseEnvelope<PixVerseVideoResultResponse>) =>
readPixVerseSuccess(payload, "PixVerse video status request failed");
const payload = await pollProviderOperationJson<PixVerseEnvelope<PixVerseVideoResultResponse>>({
url: `${params.baseUrl}/video/result/${params.videoId}`,
headers: () => buildPixVerseHeaders(params.headers),
deadline: params.deadline,
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
fetchFn: params.fetchFn,
maxAttempts: MAX_POLL_ATTEMPTS,
pollIntervalMs: POLL_INTERVAL_MS,
requestFailedMessage: "PixVerse video status request failed",
timeoutMessage: `PixVerse video generation task ${params.videoId} did not finish in time`,
isComplete: (candidate) => readPixVerseStatus(readResult(candidate)) === 1,
getFailureMessage: (candidate) => readPixVerseFailureMessage(readResult(candidate)),
allowPrivateNetwork: params.allowPrivateNetwork,
dispatcherPolicy: params.dispatcherPolicy,
});
return readResult(payload);
}
function extractPixVerseVideo(payload: PixVerseVideoResultResponse): GeneratedVideoAsset {
const url = normalizeOptionalString(payload.url);
if (!url) {
throw new Error("PixVerse video generation completed without output URL");
}
return {
url,
mimeType: "video/mp4",
fileName: "video-1.mp4",
metadata: {
sourceUrl: url,
outputWidth: asFiniteNumber(payload.outputWidth),
outputHeight: asFiniteNumber(payload.outputHeight),
},
};
}
export function buildPixVerseVideoGenerationProvider(): VideoGenerationProvider {
return {
id: PIXVERSE_PROVIDER_ID,
label: "PixVerse",
defaultModel: DEFAULT_PIXVERSE_MODEL_ID,
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
models: [...PIXVERSE_VIDEO_MODELS],
isConfigured: ({ agentDir }) =>
isProviderApiKeyConfigured({
provider: PIXVERSE_PROVIDER_ID,
agentDir,
}),
capabilities: {
generate: {
maxVideos: 1,
maxDurationSeconds: MAX_DURATION_SECONDS,
supportedDurationSeconds: Array.from(
{ length: MAX_DURATION_SECONDS },
(_, index) => index + 1,
),
aspectRatios: [...PIXVERSE_TEXT_ASPECT_RATIOS],
resolutions: ["360P", "540P", "720P", "1080P"],
supportsAspectRatio: true,
supportsResolution: true,
supportsAudio: true,
providerOptions: {
seed: "number",
negative_prompt: "string",
negativePrompt: "string",
quality: "string",
camera_movement: "string",
cameraMovement: "string",
template_id: "number",
templateId: "number",
},
},
imageToVideo: {
enabled: true,
maxVideos: 1,
maxInputImages: 1,
maxDurationSeconds: MAX_DURATION_SECONDS,
supportedDurationSeconds: Array.from(
{ length: MAX_DURATION_SECONDS },
(_, index) => index + 1,
),
resolutions: ["360P", "540P", "720P", "1080P"],
supportsResolution: true,
supportsAudio: true,
providerOptions: {
seed: "number",
negative_prompt: "string",
negativePrompt: "string",
quality: "string",
motion_mode: "string",
motionMode: "string",
camera_movement: "string",
cameraMovement: "string",
template_id: "number",
templateId: "number",
},
},
videoToVideo: {
enabled: false,
},
},
async generateVideo(req) {
if ((req.inputVideos?.length ?? 0) > 0) {
throw new Error("PixVerse video generation does not support video reference inputs.");
}
if ((req.inputImages?.length ?? 0) > 1) {
throw new Error("PixVerse image-to-video supports at most one input image.");
}
const auth = await resolveApiKeyForProvider({
provider: PIXVERSE_PROVIDER_ID,
cfg: req.cfg,
agentDir: req.agentDir,
store: req.authStore,
});
if (!auth.apiKey) {
throw new Error("PixVerse API key missing");
}
const model = normalizePixVerseModel(req.model);
const fetchFn = fetch;
const providerConfig = req.cfg?.models?.providers?.[PIXVERSE_PROVIDER_ID];
const deadline = createProviderOperationDeadline({
timeoutMs: req.timeoutMs,
label: "PixVerse video generation",
});
const { baseUrl, allowPrivateNetwork, headers, dispatcherPolicy } =
resolveProviderHttpRequestConfig({
baseUrl: resolvePixVerseBaseUrl(req),
defaultBaseUrl: DEFAULT_PIXVERSE_BASE_URL,
request: sanitizeConfiguredModelProviderRequest(providerConfig?.request),
defaultHeaders: {
"API-KEY": auth.apiKey,
},
provider: PIXVERSE_PROVIDER_ID,
capability: "video",
transport: "http",
});
const image = req.inputImages?.[0];
let imageId: number | undefined;
if (image) {
const upload = await postMultipartRequest({
url: `${baseUrl}/image/upload`,
headers: buildPixVerseHeaders(headers),
body: buildUploadImageForm(image),
timeoutMs: resolveProviderOperationTimeoutMs({
deadline,
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
}),
fetchFn,
allowPrivateNetwork,
dispatcherPolicy,
});
try {
await assertOkOrThrowHttpError(upload.response, "PixVerse image upload failed");
imageId = readPixVerseImageId(
await readPixVerseJson<PixVerseUploadImageResponse>(
upload.response,
"PixVerse image upload failed",
),
);
} finally {
await upload.release();
}
}
const endpoint = imageId === undefined ? "/video/text/generate" : "/video/img/generate";
const create = await postJsonRequest({
url: `${baseUrl}${endpoint}`,
headers: buildPixVerseHeaders(headers, "application/json"),
body: buildVideoBody(req, model, imageId),
timeoutMs: resolveProviderOperationTimeoutMs({
deadline,
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
}),
fetchFn,
allowPrivateNetwork,
dispatcherPolicy,
});
try {
await assertOkOrThrowHttpError(create.response, "PixVerse video generation failed");
const videoId = readPixVerseVideoId(
await readPixVerseJson<PixVerseVideoCreateResponse>(
create.response,
"PixVerse video generation failed",
),
);
const completed = await pollPixVerseVideo({
videoId,
baseUrl,
deadline,
fetchFn,
allowPrivateNetwork,
dispatcherPolicy,
headers,
});
return {
videos: [extractPixVerseVideo(completed)],
model,
metadata: {
endpoint,
videoId,
status: readPixVerseStatus(completed),
seed: readPixVerseSeed(completed.seed),
size: asFiniteNumber(completed.size),
},
};
} finally {
await create.release();
}
},
};
}