Skip to content

Commit 2c57ec7

Browse files
xieyongliangyongliang.xieodysseus0odysseus0
authored
video_generate: add providerOptions, inputAudios, and imageRoles (openclaw#61987)
* video_generate: add providerOptions, inputAudios, and imageRoles - VideoGenerationSourceAsset gains an optional `role` field (e.g. "first_frame", "last_frame"); core treats it as opaque and forwards it to the provider unchanged. - VideoGenerationRequest gains `inputAudios` (reference audio assets, e.g. background music) and `providerOptions` (arbitrary provider-specific key/value pairs forwarded as-is). - VideoGenerationProviderCapabilities gains `maxInputAudios`. - video_generate tool schema adds: - `imageRoles` array (parallel to `images`, sets role per asset) - `audioRef` / `audioRefs` (single/multi reference audio inputs) - `providerOptions` (JSON object passed through to the provider) - `MAX_INPUT_IMAGES` bumped 5 → 9; `MAX_INPUT_AUDIOS` = 3 - Capability validation extended to gate on `maxInputAudios`. - runtime.ts threads `inputAudios` and `providerOptions` through to `provider.generateVideo`. - Docs and runtime tests updated. Made-with: Cursor * docs: fix BytePlus Seedance capability table — split 1.5 and 2.0 rows 1.5 Pro supports at most 2 input images (first_frame + last_frame); 2.0 supports up to 9 reference images, 3 videos, and 3 audios. Provider notes section updated accordingly. Made-with: Cursor * docs: list all Seedance 1.0 models in video-generation provider table - Default model updated to seedance-1-0-pro-250528 (was the T2V lite) - Provider notes now enumerate all five 1.0 model IDs with T2V/I2V capability notes Made-with: Cursor * video_generate: address review feedback (P1/P2) P1: Add "adaptive" to SUPPORTED_ASPECT_RATIOS so provider-specific ratio passthrough (used by Seedance 1.5/2.0) is accepted instead of throwing. Update error message to include "adaptive" in the allowed list. P1: Fix audio input capability default — when a provider does not declare maxInputAudios, default to 0 (no audio support) instead of MAX_INPUT_AUDIOS. Providers must explicitly opt in via maxInputAudios to accept audio inputs. P2: Remove unnecessary type cast in imageRoles assignment; VideoGenerationSourceAsset already declares role?: string so a non-null assertion suffices. P2: Add videoRoles and audioRoles tool parameters, parallel to imageRoles, so callers can assign semantic role hints to reference video and audio assets (e.g. "reference_video", "reference_audio" for Seedance 2.0). Made-with: Cursor * video_generate: fix check-docs formatting and snake_case param reading Made-with: Cursor * video_generate: clarify *Roles are parallel to combined input list (P2) Made-with: Cursor * video_generate: add missing duration import; fix corrupted docs section Made-with: Cursor * video_generate: pass mode inputs to duration resolver; note plugin requirement (P2) Made-with: Cursor * plugin-sdk: sync new video-gen fields — role, inputAudios, providerOptions, maxInputAudios Add fields introduced by core in the PR1 batch to the public plugin-sdk mirror so TypeScript provider plugins can declare and consume them without type assertions: - VideoGenerationSourceAsset.role?: string - VideoGenerationRequest.inputAudios and .providerOptions - VideoGenerationModeCapabilities.maxInputAudios The AssertAssignable bidirectional checks still pass because all new fields are optional; this change makes the SDK surface complete. Made-with: Cursor * video-gen runtime: skip failover candidates lacking audio capability Made-with: Cursor * video-gen: fall back to flat capabilities.maxInputAudios in failover and tool validation Made-with: Cursor * video-gen: defer audio-count check to runtime, enabling fallback for audio-capable candidates Made-with: Cursor * video-gen: defer maxDurationSeconds check to runtime, enabling fallback for higher-cap candidates Made-with: Cursor * video-gen: add VideoGenerationAssetRole union and typed providerOptions capability Introduces a canonical VideoGenerationAssetRole union (first_frame, last_frame, reference_image, reference_video, reference_audio) for the source-asset role hint, and a VideoGenerationProviderOptionType tag ('number' | 'boolean' | 'string') plus a new capabilities.providerOptions schema that providers use to declare which opaque providerOptions keys they accept and with what primitive type. Types are additive and backwards compatible. The role field accepts both canonical union values and arbitrary provider-specific strings via a `VideoGenerationAssetRole | (string & {})` union, so autocomplete works for the common case without blocking provider-specific extensions. Runtime enforcement of providerOptions (skip-in-fallback, unknown key and type mismatch) lands in a follow-up commit. Co-authored-by: yongliang.xie <yongliang.xie@bytedance.com> * video-gen: enforce typed providerOptions schema via skip-in-fallback Adds `validateProviderOptionsAgainstDeclaration` in the video-generation runtime and wires it into the `generateVideo` candidate loop alongside the existing audio-count and duration-cap skip guards. Behavior: - Candidates with no declared `capabilities.providerOptions` skip any non-empty providerOptions payload with a clear skip reason, so a provider that would ignore `{seed: 42}` and succeed without the caller's intent never gets reached. - Candidates that declare a schema reject unknown keys with the list of accepted keys in the error. - Candidates that declare a schema reject type mismatches (expected number/boolean/string) with the declared type in the error. - All skip reasons push into `attempts` so the aggregated failure message at the end of the fallback chain explains exactly why each candidate was rejected. Also hardens the tool boundary: `providerOptions` that is not a plain JSON object (including bogus arrays like `["seed", 42]`) now throws a `ToolInputError` up front instead of being cast to `Record` and forwarded with numeric-string keys. Consistent with the audio/duration skip-in-fallback pattern introduced by yongliang.xie in earlier commits on this branch. Co-authored-by: yongliang.xie <yongliang.xie@bytedance.com> * video-gen: harden *Roles parity + document canonical role values Replaces the inline `parseRolesArg` lambda with a dedicated `parseRoleArray` helper that throws a ToolInputError when the caller supplies more roles than assets. Off-by-one alignment mistakes in `imageRoles` / `videoRoles` / `audioRoles` now fail loudly at the tool boundary instead of silently dropping trailing roles. Also tightens the schema descriptions to document the canonical VideoGenerationAssetRole values (first_frame, last_frame, reference_*) and the skip-in-fallback contract on providerOptions, and rejects non-array inputs to any `*Roles` field early rather than coercing them to an empty list. Co-authored-by: yongliang.xie <yongliang.xie@bytedance.com> * video-gen: surface dropped aspectRatio sentinels in ignoredOverrides "adaptive" and other provider-specific sentinel aspect ratios are unparseable as numeric ratios, so when the active provider does not declare the sentinel in caps.aspectRatios, `resolveClosestAspectRatio` returns undefined and the previous code silently nulled out `aspectRatio` without surfacing a warning. Push the dropped value into `ignoredOverrides` so the tool result warning path ("Ignored unsupported overrides for …") picks it up, and the caller gets visible feedback that the request was dropped instead of a silent no-op. Also corrects the tool-side comment on SUPPORTED_ASPECT_RATIOS to describe actual behavior. Co-authored-by: yongliang.xie <yongliang.xie@bytedance.com> * video-gen: surface declared providerOptions + maxInputAudios in action=list `video_generate action=list` now includes the declared providerOptions schema (key:type) per provider, so agents can discover which opaque keys each provider accepts without trial and error. Both mode-level and flat-provider providerOptions declarations are merged, matching the runtime lookup order in `generateVideo`. Also surfaces `maxInputAudios` alongside the other max-input counts for completeness — previously the list output did not expose the audio cap at all, even though the tool validates against it. Co-authored-by: yongliang.xie <yongliang.xie@bytedance.com> * video-gen: warn once per request when runtime skips a fallback candidate The skip-in-fallback guards (audio cap, duration cap, providerOptions) all logged at debug level, which meant operators had no visible signal when the primary provider was silently passed over in favor of a fallback. Add a first-skip log.warn in the runtime loop so the reason for the first rejection is surfaced once per request, and leave the rest of the skip events at debug to avoid flooding on long chains. Co-authored-by: yongliang.xie <yongliang.xie@bytedance.com> * video-gen: cover new tool-level behavior with regression tests Adds regression tests for: - providerOptions shape rejection (arrays, strings) - providerOptions happy-path forwarding to runtime - imageRoles length-parity guard - *Roles non-array rejection - positional role attachment to loaded reference images - audio data: URL templated rejection branch - aspectRatio='adaptive' acceptance and forwarding - unsupported aspectRatio rejection (mentions 'adaptive' in the error) All eight new cases run in the existing video-generate-tool suite and use the same provider-mock pattern already established in the file. Co-authored-by: yongliang.xie <yongliang.xie@bytedance.com> * video-gen: cover runtime providerOptions skip-in-fallback branches Adds runtime regression tests for the new typed-providerOptions guard: - candidates without a declared providerOptions schema are skipped when any providerOptions is supplied (prevents silent drop) - candidates that declare a schema skip on unknown keys with the accepted-key list surfaced in the error - candidates that declare a schema skip on type mismatches with the declared type surfaced in the error - end-to-end fallback: openai (no providerOptions) is skipped and byteplus (declared schema) accepts the same request, with an attempt entry recording the first skip reason Also updates the existing 'forwards providerOptions to the provider unchanged' case so the destination provider declares the matching typed schema, and wires a `warn` stub into the hoisted logger mock so the new first-skip log.warn call path does not blow up. Co-authored-by: yongliang.xie <yongliang.xie@bytedance.com> * changelog: note video_generate providerOptions / inputAudios / role hints Adds an Unreleased Changes entry describing the user-visible surface expansion for video_generate: typed providerOptions capability, inputAudios reference audio, per-asset role hints via the canonical VideoGenerationAssetRole union, the 'adaptive' aspect-ratio sentinel, maxInputAudios capability, and the relaxed 9-image cap. Credits the original PR author. Co-authored-by: yongliang.xie <yongliang.xie@bytedance.com> * byteplus: declare providerOptions schema (seed, draft, camerafixed) and forward to API Made-with: Cursor * byteplus: fix camera_fixed body field (API uses underscore, not camerafixed) Made-with: Cursor * fix(byteplus): normalize resolution to lowercase before API call The Seedance API rejects resolution values with uppercase letters — "480P", "720P" etc return InvalidParameter, while "480p", "720p" are accepted. This was breaking the video generation live test (resolveLiveVideoResolution returns "480P"). Normalize req.resolution to lowercase at the provider layer before setting body.resolution, so any caller-supplied casing is corrected without requiring changes to the VideoGenerationResolution type or live-test helpers. Verified via direct API call: body.resolution = "480P" → HTTP 400 InvalidParameter body.resolution = "480p" → task created successfully body.resolution = "720p" → task created successfully (t2v, i2v, 1.5-pro) body.resolution = "1080p" → task created successfully Made-with: Cursor * video-gen/byteplus: auto-select i2v model when input images provided with t2v model Seedance 1.0 uses separate model IDs for T2V (seedance-1-0-lite-t2v-250428) and I2V (seedance-1-0-lite-i2v-250428). When the caller requests a T2V model but also provides inputImages, the API rejects with task_type i2v not supported on t2v model. Fix: when inputImages are present and the requested model contains "-t2v-", auto-substitute "-i2v-" so the API receives the correct model. Seedance 1.5 Pro uses a single model ID for both modes and is unaffected by this substitution. Verified via live test: both mode=generate and mode=imageToVideo pass for byteplus/seedance-1-0-lite-t2v-250428 with no failures. Co-authored-by: odysseus0 <odysseus0@example.com> Made-with: Cursor * video-gen: fix duration rounding + align BytePlus (1.0) docs (P2) Made-with: Cursor * video-gen: relax providerOptions gate for undeclared-schema providers (P1) Distinguish undefined (not declared = backward-compat pass-through) from {} (explicitly declared empty = no options accepted) in validateProviderOptionsAgainstDeclaration. Providers without a declared schema receive providerOptions as-is; providers with an explicit empty schema still skip. Typed schemas continue to validate key names and types. Also: restore camera_fixed (underscore) in BytePlus provider schema and body key (regression from earlier rebase), remove duplicate local readBooleanToolParam definition now imported from media-tool-shared, update tests and docs accordingly. Made-with: Cursor * video_generate: add landing follow-up coverage * video_generate: finalize plugin-sdk baseline (openclaw#61987) (thanks @xieyongliang) --------- Co-authored-by: yongliang.xie <yongliang.xie@bytedance.com> Co-authored-by: George Zhang <georgezhangtj97@gmail.com> Co-authored-by: odysseus0 <odysseus0@example.com>
1 parent f2a4a5a commit 2c57ec7

14 files changed

Lines changed: 1454 additions & 117 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ Docs: https://docs.openclaw.ai
158158
- QA/lab: add character-vibes evaluation reports with model selection and parallel runs so live QA can compare candidate behavior faster.
159159
- Plugins/provider-auth: let provider manifests declare `providerAuthAliases` so provider variants can share env vars, auth profiles, config-backed auth, and API-key onboarding choices without core-specific wiring.
160160
- iOS: pin release versioning to an explicit CalVer in `apps/ios/version.json`, keep TestFlight iteration on the same short version until maintainers intentionally promote the next gateway version, and add the documented `pnpm ios:version:pin -- --from-gateway` workflow for release trains. (#63001) Thanks @ngutman.
161+
- Tools/video_generate: extend the tool and the Plugin SDK with `providerOptions` (vendor-specific options forwarded as a JSON object), `inputAudios` / `audioRef` / `audioRefs` reference audio inputs, per-asset semantic role hints (`imageRoles` / `videoRoles` / `audioRoles`) using a typed `VideoGenerationAssetRole` union, a new `"adaptive"` aspect-ratio sentinel, and `maxInputAudios` provider capability declarations. Providers opt into `providerOptions` by declaring a typed `capabilities.providerOptions` schema (`{ seed: "number", draft: "boolean", ... }`); unknown keys and type mismatches cause the runtime fallback loop to skip the candidate with a visible warning and an `attempts` entry, so vendor-specific options never silently reach the wrong provider. Also raises the in-tool image input cap to 9 and updates the docs table to list all new parameters. (#61987) Thanks @xieyongliang.
161162

162163
### Fixes
163164

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
ee16273fa5ad8c5408e9dad8d96fde86dfa666ef8eb44840b78135814ff97173 plugin-sdk-api-baseline.json
2-
2bd0d5edf23e6a889d6bedb74d0d06411dd7750dac6ebf24971c789f8a69253a plugin-sdk-api-baseline.jsonl
1+
7a9bb7a5e4b243e2123af94301ba363d57eddab2baa6378d16cd37a1cb8a55f7 plugin-sdk-api-baseline.json
2+
2bdca027d5fda72399479569927cd34d18b56b242e4b12ac45e7c2352e551c77 plugin-sdk-api-baseline.jsonl

docs/tools/video-generation.md

Lines changed: 98 additions & 77 deletions
Large diffs are not rendered by default.

extensions/byteplus/video-generation-provider.test.ts

Lines changed: 80 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,35 @@ beforeAll(async () => {
1414

1515
installProviderHttpMockCleanup();
1616

17+
function mockSuccessfulBytePlusTask(params?: { model?: string }) {
18+
postJsonRequestMock.mockResolvedValue({
19+
response: {
20+
json: async () => ({
21+
id: "task_123",
22+
}),
23+
},
24+
release: vi.fn(async () => {}),
25+
});
26+
fetchWithTimeoutMock
27+
.mockResolvedValueOnce({
28+
json: async () => ({
29+
id: "task_123",
30+
status: "succeeded",
31+
content: {
32+
video_url: "https://example.com/byteplus.mp4",
33+
},
34+
model: params?.model ?? "seedance-1-0-lite-t2v-250428",
35+
}),
36+
})
37+
.mockResolvedValueOnce({
38+
headers: new Headers({ "content-type": "video/mp4" }),
39+
arrayBuffer: async () => Buffer.from("mp4-bytes"),
40+
});
41+
}
42+
1743
describe("byteplus video generation provider", () => {
1844
it("creates a content-generation task, polls, and downloads the video", async () => {
19-
postJsonRequestMock.mockResolvedValue({
20-
response: {
21-
json: async () => ({
22-
id: "task_123",
23-
}),
24-
},
25-
release: vi.fn(async () => {}),
26-
});
27-
fetchWithTimeoutMock
28-
.mockResolvedValueOnce({
29-
json: async () => ({
30-
id: "task_123",
31-
status: "succeeded",
32-
content: {
33-
video_url: "https://example.com/byteplus.mp4",
34-
},
35-
model: "seedance-1-0-lite-t2v-250428",
36-
}),
37-
})
38-
.mockResolvedValueOnce({
39-
headers: new Headers({ "content-type": "video/mp4" }),
40-
arrayBuffer: async () => Buffer.from("mp4-bytes"),
41-
});
45+
mockSuccessfulBytePlusTask();
4246

4347
const provider = buildBytePlusVideoGenerationProvider();
4448
const result = await provider.generateVideo({
@@ -60,4 +64,57 @@ describe("byteplus video generation provider", () => {
6064
}),
6165
);
6266
});
67+
68+
it("switches t2v image requests to i2v models and lowercases resolution", async () => {
69+
mockSuccessfulBytePlusTask({ model: "seedance-1-0-lite-i2v-250428" });
70+
71+
const provider = buildBytePlusVideoGenerationProvider();
72+
await provider.generateVideo({
73+
provider: "byteplus",
74+
model: "seedance-1-0-lite-t2v-250428",
75+
prompt: "Animate this still image",
76+
resolution: "720P",
77+
inputImages: [{ url: "https://example.com/first-frame.png" }],
78+
cfg: {},
79+
});
80+
81+
const request = postJsonRequestMock.mock.calls[0]?.[0] as { body?: Record<string, unknown> };
82+
expect(request.body).toMatchObject({
83+
model: "seedance-1-0-lite-i2v-250428",
84+
resolution: "720p",
85+
content: [
86+
{ type: "text", text: "Animate this still image" },
87+
{
88+
type: "image_url",
89+
image_url: { url: "https://example.com/first-frame.png" },
90+
role: "first_frame",
91+
},
92+
],
93+
});
94+
});
95+
96+
it("maps declared providerOptions into the request body", async () => {
97+
mockSuccessfulBytePlusTask({ model: "seedance-1-0-pro-250528" });
98+
99+
const provider = buildBytePlusVideoGenerationProvider();
100+
await provider.generateVideo({
101+
provider: "byteplus",
102+
model: "seedance-1-0-pro-250528",
103+
prompt: "A cinematic lobster montage",
104+
providerOptions: {
105+
seed: 42,
106+
draft: true,
107+
camera_fixed: false,
108+
},
109+
cfg: {},
110+
});
111+
112+
const request = postJsonRequestMock.mock.calls[0]?.[0] as { body?: Record<string, unknown> };
113+
expect(request.body).toMatchObject({
114+
model: "seedance-1-0-pro-250528",
115+
seed: 42,
116+
resolution: "480p",
117+
camera_fixed: false,
118+
});
119+
});
63120
});

extensions/byteplus/video-generation-provider.ts

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,11 @@ export function buildBytePlusVideoGenerationProvider(): VideoGenerationProvider
141141
agentDir,
142142
}),
143143
capabilities: {
144+
providerOptions: {
145+
seed: "number",
146+
draft: "boolean",
147+
camera_fixed: "boolean",
148+
},
144149
generate: {
145150
maxVideos: 1,
146151
maxDurationSeconds: 12,
@@ -191,6 +196,17 @@ export function buildBytePlusVideoGenerationProvider(): VideoGenerationProvider
191196
capability: "video",
192197
transport: "http",
193198
});
199+
// Seedance 1.0 has separate T2V and I2V model IDs (e.g. seedance-1-0-lite-t2v-250428 vs
200+
// seedance-1-0-lite-i2v-250428). When input images are provided with a T2V model, auto-
201+
// switch to the corresponding I2V variant so the API does not reject with task_type mismatch.
202+
// 1.5 Pro uses a single model ID for both modes and is unaffected by this substitution.
203+
const hasInputImages = (req.inputImages?.length ?? 0) > 0;
204+
const requestedModel = normalizeOptionalString(req.model) || DEFAULT_BYTEPLUS_VIDEO_MODEL;
205+
const resolvedModel =
206+
hasInputImages && requestedModel.includes("-t2v-")
207+
? requestedModel.replace("-t2v-", "-i2v-")
208+
: requestedModel;
209+
194210
const content: Array<Record<string, unknown>> = [{ type: "text", text: req.prompt }];
195211
const imageUrl = resolveBytePlusImageUrl(req);
196212
if (imageUrl) {
@@ -201,15 +217,18 @@ export function buildBytePlusVideoGenerationProvider(): VideoGenerationProvider
201217
});
202218
}
203219
const body: Record<string, unknown> = {
204-
model: normalizeOptionalString(req.model) || DEFAULT_BYTEPLUS_VIDEO_MODEL,
220+
model: resolvedModel,
205221
content,
206222
};
207223
const aspectRatio = normalizeOptionalString(req.aspectRatio);
208224
if (aspectRatio) {
209225
body.ratio = aspectRatio;
210226
}
211-
if (req.resolution) {
212-
body.resolution = req.resolution;
227+
// Seedance API requires lowercase resolution values (e.g. "480p", "720p"); uppercase
228+
// variants like "480P" are rejected with InvalidParameter.
229+
const resolution = normalizeOptionalString(req.resolution)?.toLowerCase();
230+
if (resolution) {
231+
body.resolution = resolution;
213232
}
214233
if (typeof req.durationSeconds === "number" && Number.isFinite(req.durationSeconds)) {
215234
body.duration = Math.max(1, Math.round(req.durationSeconds));
@@ -221,6 +240,23 @@ export function buildBytePlusVideoGenerationProvider(): VideoGenerationProvider
221240
body.watermark = req.watermark;
222241
}
223242

243+
// Forward declared providerOptions: seed, draft, camerafixed.
244+
// draft=true forces 480p resolution for faster generation.
245+
const opts = req.providerOptions ?? {};
246+
const seed = typeof opts.seed === "number" ? opts.seed : undefined;
247+
const draft = opts.draft === true;
248+
// Official JSON body field is camera_fixed (with underscore).
249+
const cameraFixed = typeof opts.camera_fixed === "boolean" ? opts.camera_fixed : undefined;
250+
if (seed != null) {
251+
body.seed = seed;
252+
}
253+
if (draft && !body.resolution) {
254+
body.resolution = "480p";
255+
}
256+
if (cameraFixed != null) {
257+
body.camera_fixed = cameraFixed;
258+
}
259+
224260
const { response, release } = await postJsonRequest({
225261
url: `${baseUrl}/contents/generations/tasks`,
226262
headers,
@@ -255,7 +291,7 @@ export function buildBytePlusVideoGenerationProvider(): VideoGenerationProvider
255291
});
256292
return {
257293
videos: [video],
258-
model: completed.model ?? req.model ?? DEFAULT_BYTEPLUS_VIDEO_MODEL,
294+
model: completed.model ?? resolvedModel,
259295
metadata: {
260296
taskId,
261297
status: completed.status,

src/agents/tools/video-generate-tool.actions.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,36 @@ function summarizeVideoGenerationCapabilities(
2121
const generate = provider.capabilities.generate;
2222
const imageToVideo = provider.capabilities.imageToVideo;
2323
const videoToVideo = provider.capabilities.videoToVideo;
24+
// providerOptions may be declared at the mode level (generate) or at the flat
25+
// provider-capabilities level. The runtime checks both; surface the union so
26+
// the agent sees a single merged view of which opaque keys each provider
27+
// actually accepts.
28+
const declaredProviderOptions: Record<string, string> = {};
29+
for (const [key, type] of Object.entries(provider.capabilities.providerOptions ?? {})) {
30+
declaredProviderOptions[key] = type;
31+
}
32+
for (const [key, type] of Object.entries(generate?.providerOptions ?? {})) {
33+
declaredProviderOptions[key] = type;
34+
}
35+
for (const [key, type] of Object.entries(imageToVideo?.providerOptions ?? {})) {
36+
declaredProviderOptions[key] = type;
37+
}
38+
for (const [key, type] of Object.entries(videoToVideo?.providerOptions ?? {})) {
39+
declaredProviderOptions[key] = type;
40+
}
41+
const maxInputAudios =
42+
generate?.maxInputAudios ??
43+
imageToVideo?.maxInputAudios ??
44+
videoToVideo?.maxInputAudios ??
45+
provider.capabilities.maxInputAudios;
2446
const capabilities = [
2547
supportedModes.length > 0 ? `modes=${supportedModes.join("/")}` : null,
2648
generate?.maxVideos ? `maxVideos=${generate.maxVideos}` : null,
2749
imageToVideo?.maxInputImages ? `maxInputImages=${imageToVideo.maxInputImages}` : null,
2850
videoToVideo?.maxInputVideos ? `maxInputVideos=${videoToVideo.maxInputVideos}` : null,
51+
typeof maxInputAudios === "number" && maxInputAudios > 0
52+
? `maxInputAudios=${maxInputAudios}`
53+
: null,
2954
generate?.maxDurationSeconds ? `maxDurationSeconds=${generate.maxDurationSeconds}` : null,
3055
generate?.supportedDurationSeconds?.length
3156
? `supportedDurationSeconds=${generate.supportedDurationSeconds.join("/")}`
@@ -41,6 +66,11 @@ function summarizeVideoGenerationCapabilities(
4166
generate?.supportsSize ? "size" : null,
4267
generate?.supportsAudio ? "audio" : null,
4368
generate?.supportsWatermark ? "watermark" : null,
69+
Object.keys(declaredProviderOptions).length > 0
70+
? `providerOptions={${Object.entries(declaredProviderOptions)
71+
.map(([key, type]) => `${key}:${type}`)
72+
.join(", ")}}`
73+
: null,
4474
]
4575
.filter((entry): entry is string => Boolean(entry))
4676
.join(", ");

0 commit comments

Comments
 (0)