From c5083b7de176fb515f46f27d89cc769617ee1773 Mon Sep 17 00:00:00 2001 From: Tom Beckenham <34339192+tombeckenham@users.noreply.github.com> Date: Fri, 7 Aug 2026 20:54:40 +1000 Subject: [PATCH] feat(ai-byteplus): add first-class Seedance 2.5 support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Promote dreamina-seedance-2-5-260628 from the open string escape hatch into BYTEPLUS_VIDEO_MODELS with documented capabilities: 4–30s duration, 480p/720p, multimodal references including audio-only input, priority/generate_audio, and output_format. Update adapter guards, unit tests, docs, and the Seedance Studio example catalog. --- .changeset/seedance-2-5.md | 12 ++ docs/adapters/byteplus.md | 38 ++++--- docs/config.json | 5 +- docs/media/video-generation.md | 8 +- .../src/components/SeedanceStudio.tsx | 7 +- examples/ts-react-media/src/lib/seedance.ts | 56 ++++++---- .../src/lib/server-functions.ts | 3 +- packages/ai-byteplus/README.md | 3 +- packages/ai-byteplus/src/adapters/video.ts | 34 +++--- packages/ai-byteplus/src/index.ts | 2 + packages/ai-byteplus/src/model-meta.ts | 86 ++++++++------ .../src/video/video-provider-options.ts | 105 ++++++++++++------ packages/ai-byteplus/src/video/wire-types.ts | 27 +++-- packages/ai-byteplus/tests/model-meta.test.ts | 8 +- packages/ai-byteplus/tests/video.test.ts | 90 ++++++++++++--- 15 files changed, 327 insertions(+), 157 deletions(-) create mode 100644 .changeset/seedance-2-5.md diff --git a/.changeset/seedance-2-5.md b/.changeset/seedance-2-5.md new file mode 100644 index 000000000..19a245535 --- /dev/null +++ b/.changeset/seedance-2-5.md @@ -0,0 +1,12 @@ +--- +'@tanstack/ai-byteplus': patch +--- + +Add first-class Seedance 2.5 support (`dreamina-seedance-2-5-260628`). + +The model is fully open on ModelArk with documented capabilities: 4–30s +duration (or `-1`), 480p/720p only, multimodal reference media including +audio-only input, first-and-last-frame mode, `priority`, `generate_audio`, and +`output_format` (`mp4` | `mov`). The video adapter, typed model tables, unit +tests, docs, and Seedance Studio example catalog all treat it as a known +model rather than an escape-hatch string. diff --git a/docs/adapters/byteplus.md b/docs/adapters/byteplus.md index daa293af3..7a47a5940 100644 --- a/docs/adapters/byteplus.md +++ b/docs/adapters/byteplus.md @@ -321,42 +321,52 @@ Seedance options are model-specific, and **Ark rejects an inapplicable field wit | Option | Models that accept it | | --- | --- | -| `service_tier` (`'default'` \| `'flex'`) | Seedance 1.x only — the 2.0 family rejects it | +| `service_tier` (`'default'` \| `'flex'`) | Seedance 1.x only — Seedance 2.5 and the 2.0 family reject it | | `camera_fixed` | Seedance 1.x only | | `frames` (fractional-second output, `25 + 4n` in `[29, 289]`) | `seedance-1-0-pro-250528`, `seedance-1-0-pro-fast-251015` | | `draft` (cheap low-fidelity preview) | `seedance-1-5-pro-251215` only | -| `priority` (`0`–`9`) | the `dreamina-seedance-2-0-*` family only | -| `duration: -1` (model picks the length) | Seedance 2.0 and `seedance-1-5-pro-251215` | +| `priority` (`0`–`9`) | Seedance 2.5 and the `dreamina-seedance-2-0-*` family | +| `duration: -1` (model picks the length) | Seedance 2.5, 2.0 and `seedance-1-5-pro-251215` | +| `output_format` (`'mp4'` \| `'mov'`) | Seedance 2.5 (default `mp4`) | | `seed`, `watermark`, `generate_audio`, `return_last_frame`, `callback_url` | every model | -`watermark` defaults to `false` for video (the opposite of Seedream images). `generate_audio` is accepted everywhere but only Seedance 2.0 and 1.5-pro actually produce an audio track. +`watermark` defaults to `false` for video (the opposite of Seedream images). `generate_audio` is accepted everywhere but only Seedance 2.5, 2.0 and 1.5-pro actually produce an audio track. -Resolutions are per model too, and the shipped table comes from live probes rather than the published docs: there is **no 2K tier on any Seedance model**, `4k` exists only on `dreamina-seedance-2-0-260128`, and `seedance-1-0-pro-fast-251015` does accept `1080p` despite being documented as 480p/720p only. An unsupported combination is caught locally with a clear error before the request goes out. +Resolutions are per model too: there is **no 2K tier on any Seedance model**, `4k` exists only on `dreamina-seedance-2-0-260128`, Seedance 2.5 is **480p/720p only**, and `seedance-1-0-pro-fast-251015` does accept `1080p` despite older prose listing it as 480p/720p only. An unsupported combination is caught locally with a clear error before the request goes out. -Reference media follows the shared [role hints](../media/video-generation#role-hints) — `start_frame`, `end_frame` and `reference`. The Seedance 2.0 family takes full multimodal references (reference images, video and audio); the 1.x models take start/end frames only, and `seedance-1-0-pro-fast-251015` takes a start frame only. +Reference media follows the shared [role hints](../media/video-generation#role-hints) — `start_frame`, `end_frame` and `reference`. Seedance 2.5 and the 2.0 family take full multimodal references (reference images, video and audio); Seedance 2.5 additionally accepts **audio-only** reference input and up to 30 reference images / 10 videos / 10 audio clips (2.0 is 9 / 3 / 3). The 1.x models take start/end frames only, and `seedance-1-0-pro-fast-251015` takes a start frame only. ### Seedance 2.5 -Seedance 2.5 was announced on 2026-07-31, initially on BytePlus's consumer surfaces. Its Ark id — `dreamina-seedance-2-5-260628` — is real and reachable, but it is **activation-gated per account**: until the model is switched on in the Ark Console, Ark answers `404 ModelNotOpen`. - -Because its capabilities could not be probed from a non-activated account, 2.5 is **deliberately absent from the typed model tables** above. It is still usable today — `byteplusVideo()`'s model parameter accepts any string, so an id BytePlus publishes after this release works without upgrading the package: +Seedance 2.5 (`dreamina-seedance-2-5-260628`) is a first-class model in this package. It is the current multimodal flagship for longer clips (up to **30 seconds**), audio-only reference input, and `output_format: 'mov'`. Like the 2.0 series it may still require model activation / a resource pack in the Ark Console before the account can call it (`404 ModelNotOpen` until then). ```typescript import { generateVideo } from '@tanstack/ai' import { byteplusVideo } from '@tanstack/ai-byteplus' -// Activate Seedance 2.5 in the Ark Console first, or this 404s. const { jobId } = await generateVideo({ adapter: byteplusVideo('dreamina-seedance-2-5-260628'), prompt: 'a guitar being played in a store', size: '16:9_720p', - duration: 5, + duration: 10, + modelOptions: { + generate_audio: true, + priority: 5, + output_format: 'mp4', + }, }) ``` -An unknown id relaxes both halves of the adapter: `size` widens to any string, provider options are ungated, and the local runtime guards that encode per-model capabilities — resolution tiers, closing-frame and reference-media support, frame cardinality and mode exclusivity, duration snapping — stand down so Ark validates the request instead. Known ids keep their probe-verified narrowing. Typed narrowing for 2.5 follows in a package update once its capabilities can be verified. +| Capability | Seedance 2.5 | +| --- | --- | +| Duration | 4–30s, or `-1` (model chooses; required for video-editing tasks) | +| Resolution | `480p`, `720p` (default `720p`) — no 1080p / 4k | +| Reference media | images 1–30, videos 0–10, audio 0–10; **audio-only allowed** | +| First + last frame | yes | +| `priority` / `generate_audio` | yes | +| `service_tier` / `camera_fixed` / `frames` / `draft` | no | -The quickest way to try a newly-released id is the [Seedance Studio example](https://github.com/TanStack/ai/tree/main/examples/ts-react-media): its **Advanced: custom model id** field (placeholder `dreamina-seedance-2-5-260628`) takes an arbitrary id, switches the studio into unknown-model mode with every option enabled, and spells the activation caveat out in the UI — so a `ModelNotOpen` response reads as expected rather than broken. +`byteplusVideo()` still accepts any string for ids BytePlus ships between package releases. An unknown id relaxes local guards so Ark validates the request; known ids (including 2.5) keep their documented narrowing. ### Seedance here vs. Seedance via fal @@ -495,7 +505,7 @@ Audio can be a `File`, base64, a data URL or a public URL, up to two hours and 1 ## Supported models - **Chat** — `dola-seed-2-1-turbo-260628`, `seed-2-0-lite-260428`, `seed-2-0-mini-260428`, `seed-2-0-pro-260328`, `seed-2-0-lite-260228`, `seed-2-0-mini-260215`, `seed-2-0-code-preview-260328`, `seed-1-8-251228`, `seed-1-6-250915`, `seed-1-6-250615`, `seed-1-6-flash-250715`, `seed-1-6-flash-250615`, `glm-5-2-260617`, `glm-4-7-251222`, `deepseek-v4-pro-260425`, `deepseek-v4-flash-260425`, `deepseek-v3-2-251201`, `gpt-oss-120b-250805`. -- **Video** — `dreamina-seedance-2-0-260128`, `dreamina-seedance-2-0-fast-260128`, `dreamina-seedance-2-0-mini-260615`, `seedance-1-5-pro-251215`, `seedance-1-0-pro-250528`, `seedance-1-0-pro-fast-251015`. (Seedance 2.5, `dreamina-seedance-2-5-260628`, is untyped-but-usable pending account activation — see [Seedance 2.5](#seedance-25).) +- **Video** — `dreamina-seedance-2-5-260628`, `dreamina-seedance-2-0-260128`, `dreamina-seedance-2-0-fast-260128`, `dreamina-seedance-2-0-mini-260615`, `seedance-1-5-pro-251215`, `seedance-1-0-pro-250528`, `seedance-1-0-pro-fast-251015`. - **Image** — `dola-seedream-5-0-pro-260628`, `seedream-5-0-260128`, `seedream-5-0-lite-260128`, `seedream-4-5-251128`, `seedream-4-0-250828`. - **Speech** — `seed-audio-1.0` (TTS) and `seed-asr` (transcription). diff --git a/docs/config.json b/docs/config.json index 67c19a8db..2f33d0718 100644 --- a/docs/config.json +++ b/docs/config.json @@ -439,7 +439,7 @@ "label": "Video Generation", "to": "media/video-generation", "addedAt": "2026-04-15", - "updatedAt": "2026-08-04" + "updatedAt": "2026-08-07" }, { "label": "Generation Hooks", @@ -850,7 +850,8 @@ { "label": "BytePlus", "to": "adapters/byteplus", - "addedAt": "2026-08-04" + "addedAt": "2026-08-04", + "updatedAt": "2026-08-07" } ] }, diff --git a/docs/media/video-generation.md b/docs/media/video-generation.md index 8999435eb..f4b36b689 100644 --- a/docs/media/video-generation.md +++ b/docs/media/video-generation.md @@ -392,7 +392,7 @@ await generateVideo({ | **OpenAI** | Sora-2 / Sora-2-Pro → the image part goes to `input_reference`; flattened text is the prompt. Single image only — throws if more than one. | | **fal.ai** | Field names resolve per endpoint from a map generated from the fal SDK's endpoint types — e.g. `role: 'start_frame'` lands on `image_url` for Kling/Veo image-to-video, `first_frame_url` for first-last-frame endpoints, and `start_image_url` otherwise. Defaults: single input → `image_url` (start frame); `role: 'end_frame'` → `end_image_url`; `role: 'reference'` / `'character'` → `reference_image_urls`. Override per-endpoint via `modelOptions` — the media-conditioning fields are typed optional there (even when the endpoint requires them) since they usually arrive as prompt parts. | | **Gemini** | Veo → the first un-roled / `'start_frame'` image becomes the input image; `'end_frame'` → `lastFrame`; `'reference'` / `'character'` → `referenceImages` (asset references, Veo 3.1). Throws on multiple starting images. | -| **BytePlus** | Seedance → a single un-roled or `'start_frame'` image becomes `first_frame`; `'end_frame'` → `last_frame` (needs a first frame alongside it, and is rejected by `seedance-1-0-pro-fast-251015`); `'reference'` / `'character'` → `reference_image`, video parts → `reference_video`, audio parts → `reference_audio` (Seedance 2.0 family only). Frame roles and reference roles are mutually exclusive modes — mixing them throws. | +| **BytePlus** | Seedance → a single un-roled or `'start_frame'` image becomes `first_frame`; `'end_frame'` → `last_frame` (needs a first frame alongside it, and is rejected by `seedance-1-0-pro-fast-251015`); `'reference'` / `'character'` → `reference_image`, video parts → `reference_video`, audio parts → `reference_audio` (Seedance 2.5 and 2.0 family; 2.5 also accepts audio-only reference input). Frame roles and reference roles are mutually exclusive modes — mixing them throws. | Adapters whose underlying API can't accept image inputs throw a clear runtime error so calls fail fast. @@ -739,7 +739,7 @@ Generated clips include an audio track. When the job completes, the adapter repo #### BytePlus (Seedance) Model Options -Seedance is aspect-ratio sized like Grok Imagine — `size` takes a `ratio` or `ratio_resolution` template. Ratios are `16:9`, `9:16`, `4:3`, `3:4`, `1:1`, `21:9` and `adaptive`; resolutions are `480p`, `720p`, `1080p` and (on `dreamina-seedance-2-0-260128` only) `4k`. There is no 2K tier on any Seedance model: +Seedance is aspect-ratio sized like Grok Imagine — `size` takes a `ratio` or `ratio_resolution` template. Ratios are `16:9`, `9:16`, `4:3`, `3:4`, `1:1`, `21:9` and `adaptive`; resolutions are `480p`, `720p`, `1080p` and (on `dreamina-seedance-2-0-260128` only) `4k`. Seedance 2.5 (`dreamina-seedance-2-5-260628`) is 480p/720p only and runs up to 30 seconds. There is no 2K tier on any Seedance model: ```typescript import { generateVideo } from '@tanstack/ai' @@ -753,12 +753,12 @@ const { jobId } = await generateVideo({ modelOptions: { seed: 42, generate_audio: true, - priority: 5, // Seedance 2.0 family only — queue priority, 0-9 + priority: 5, // Seedance 2.5 / 2.0 family — queue priority, 0-9 }, }) ``` -Options are **model-specific and validated server-side**: Ark rejects an inapplicable field with a `400` instead of ignoring it. `service_tier` and `camera_fixed` are Seedance 1.x only, `frames` works on the 1.0-pro models, `draft` on 1.5-pro, `priority` on the 2.0 family, and `duration: -1` (let the model choose) on 2.0 and 1.5-pro. Durations are 4–15s on the 2.0 family, 4–12s on 1.5-pro and 2–12s on the 1.0-pro models. +Options are **model-specific and validated server-side**: Ark rejects an inapplicable field with a `400` instead of ignoring it. `service_tier` and `camera_fixed` are Seedance 1.x only, `frames` works on the 1.0-pro models, `draft` on 1.5-pro, `priority` on Seedance 2.5 and the 2.0 family, and `duration: -1` (let the model choose) on 2.5, 2.0 and 1.5-pro. Durations are 4–30s on Seedance 2.5, 4–15s on the 2.0 family, 4–12s on 1.5-pro and 2–12s on the 1.0-pro models. **Seedance video URLs expire 24 hours after the task completes** (the task record is kept for seven days), so persist the bytes rather than the link. See the [BytePlus adapter](../adapters/byteplus#video-generation-seedance) for the full option table. diff --git a/examples/ts-react-media/src/components/SeedanceStudio.tsx b/examples/ts-react-media/src/components/SeedanceStudio.tsx index 7930fe1cc..1c585029d 100644 --- a/examples/ts-react-media/src/components/SeedanceStudio.tsx +++ b/examples/ts-react-media/src/components/SeedanceStudio.tsx @@ -870,9 +870,8 @@ export default function SeedanceStudio({

Capabilities unverified — every option below is enabled and the API validates the request, because the adapter switches - its per-model guards off for an id it has no table for. - Seedance 2.5 additionally requires activation in the Ark - Console; without it the task returns 404{' '} + its per-model guards off for an id it has no table for. A + model that is not activated on your Ark account returns 404{' '} ModelNotOpen.

@@ -1110,7 +1109,7 @@ export default function SeedanceStudio({ {entry.extras.priority && ( - +
= T /** - * The six Seedance models, with the option applicability probed live against - * Ark on 2026-07-31 and documented on `BytePlusVideoProviderOptions`. Ark 400s - * on an inapplicable field rather than ignoring it, so these flags decide what - * the studio is allowed to send — not just what it renders. + * The Seedance models, with option applicability from + * `BytePlusVideoProviderOptions` (2.0/1.x live-probed; 2.5 from ModelArk + * docs). Ark 400s on an inapplicable field rather than ignoring it, so these + * flags decide what the studio is allowed to send — not just what it renders. */ export const SEEDANCE_MODELS = [ + { + id: 'dreamina-seedance-2-5-260628', + name: 'Seedance 2.5', + blurb: 'Current multimodal flagship — up to 30s, audio-only references', + extras: { + generateAudio: true, + cameraFixed: false, + serviceTier: false, + frames: false, + draft: false, + priority: true, + autoDuration: true, + }, + }, { id: 'dreamina-seedance-2-0-260128', name: 'Seedance 2.0', - blurb: 'Flagship — the only Seedance model with a 4k tier', + blurb: '2.0 flagship — the only Seedance model with a 4k tier', extras: { generateAudio: true, cameraFixed: false, @@ -143,10 +158,10 @@ export const SEEDANCE_MODELS = [ ] as const satisfies ReadonlyArray /** - * Compile-time link between this catalog and the package's model list: a - * seventh model shipping in `@tanstack/ai-byteplus` fails the example's build - * here — naming the id it is missing — instead of silently going absent from - * the picker with no capability copy or option policy. + * Compile-time link between this catalog and the package's model list: a new + * model shipping in `@tanstack/ai-byteplus` fails the example's build here — + * naming the id it is missing — instead of silently going absent from the + * picker with no capability copy or option policy. */ export type SeedanceCatalogCoversEveryModel = AssertNever< Exclude @@ -182,14 +197,11 @@ export interface SeedanceCapability { } /** - * Placeholder for the advanced custom-id field: the real Seedance 2.5 id. - * - * The June date suffix is the whole reason it needs typing out — guessing ids - * around the 2026-07-31 announcement never landed on it. The id is reachable - * but activation-gated: an account that has not enabled it in the Ark Console - * gets 404 `ModelNotOpen`. + * Placeholder for the advanced custom-id field when trying an id this package + * has not catalogued yet. Known models (including Seedance 2.5) live in the + * picker above. */ -export const SEEDANCE_CUSTOM_MODEL_PLACEHOLDER = 'dreamina-seedance-2-5-260628' +export const SEEDANCE_CUSTOM_MODEL_PLACEHOLDER = 'dreamina-seedance-…-yymmdd' /** * Option applicability for an id the package has no table for: everything on. @@ -234,7 +246,7 @@ export interface SeedanceJobOptions { * unknown model's template shape before handing the values to Ark. */ size?: string - /** Whole seconds, or `-1` to let the model choose (2.0 / 1.5-pro only). */ + /** Whole seconds, or `-1` to let the model choose (2.5 / 2.0 / 1.5-pro). */ duration?: number /** Frame count instead of seconds; wins over `duration` server-side. */ frames?: number diff --git a/examples/ts-react-media/src/lib/server-functions.ts b/examples/ts-react-media/src/lib/server-functions.ts index 64432beff..c1e08dc93 100644 --- a/examples/ts-react-media/src/lib/server-functions.ts +++ b/examples/ts-react-media/src/lib/server-functions.ts @@ -558,8 +558,7 @@ export const getSeedanceCapabilitiesFn = createServerFn({ interface SeedanceRequest { prompt: MediaPrompt // Open by design: the studio's advanced field takes an id this package - // has no metadata for (Seedance 2.5 today), which the adapter forwards - // ungated for Ark to judge. + // has no metadata for, which the adapter forwards ungated for Ark to judge. model: BytePlusVideoModelOrString options?: SeedanceJobOptions } diff --git a/packages/ai-byteplus/README.md b/packages/ai-byteplus/README.md index d31d74489..6bd57fbc2 100644 --- a/packages/ai-byteplus/README.md +++ b/packages/ai-byteplus/README.md @@ -166,7 +166,8 @@ Result URLs expire after 24 hours; pass `response_format: 'b64_json'` in - **Chat** — `dola-seed-2-1-turbo-260628`, the `seed-2-0-*` family, `seed-1-8-251228`, the `seed-1-6-*` family, plus `glm-*`, `deepseek-*` and `gpt-oss-120b-250805`. -- **Video** — `dreamina-seedance-2-0-260128` (and `-fast-`/`-mini-`), +- **Video** — `dreamina-seedance-2-5-260628`, + `dreamina-seedance-2-0-260128` (and `-fast-`/`-mini-`), `seedance-1-5-pro-251215`, `seedance-1-0-pro-250528`, `seedance-1-0-pro-fast-251015`. - **Image** — `dola-seedream-5-0-pro-260628`, `seedream-5-0-260128`, diff --git a/packages/ai-byteplus/src/adapters/video.ts b/packages/ai-byteplus/src/adapters/video.ts index 58b2186d5..7b25441fe 100644 --- a/packages/ai-byteplus/src/adapters/video.ts +++ b/packages/ai-byteplus/src/adapters/video.ts @@ -17,6 +17,7 @@ import { import { resolveBytePlusVideoResolution, resolveBytePlusVideoSize, + supportsAudioOnlyReference, supportsLastFrame, supportsReferenceMedia, } from '../video/video-provider-options' @@ -161,7 +162,8 @@ function describeTaskFailure(task: BytePlusVideoTask): string { * `seedance-1-0-pro-fast-251015` does not support it at all. * - `'reference'` / `'character'` → `reference_image`, video parts → * `reference_video`, audio parts → `reference_audio` — subject and style - * references the model draws on (`r2v`, Seedance 2.0 family only). + * references the model draws on (`r2v`, Seedance 2.5 and 2.0 family). + * Seedance 2.5 also accepts audio-only reference input; 2.0 does not. * * Frame roles and reference roles cannot be combined in one request, so the * adapter rejects a mix up front rather than surfacing a raw 400. @@ -232,18 +234,18 @@ export class BytePlusVideoAdapter< if (resolved.text) content.push({ type: 'text', text: resolved.text }) // Every rule below except the role vocabulary itself is a claim about a - // *specific* model's capabilities, drawn from probing the six models that - // exist today. None of it can be true of a model that does not exist yet, - // so for an unknown id the guards stand down and Ark rules — otherwise the - // escape hatch would block exactly the requests it exists to enable (see + // *specific* model's capabilities, drawn from the known Seedance catalog. + // None of it can be true of a model that does not exist yet, so for an + // unknown id the guards stand down and Ark rules — otherwise the escape + // hatch would block exactly the requests it exists to enable (see // BytePlusVideoModelOrString). 'mask' / 'control' still throw: Seedance's // wire format has no field to carry them on any model. const gated = isKnownBytePlusVideoModel(model) let firstFrames = 0 let lastFrames = 0 - // Audio counts as a reference for the mode-exclusivity check but not for - // the "audio can't be the only reference" rule, which wants a visual. + // Audio counts as a reference for the mode-exclusivity check. On Seedance + // 2.0 it also needs a visual reference; 2.5 allows audio-only. let visualReferences = 0 let audioReferences = 0 @@ -277,8 +279,8 @@ export class BytePlusVideoAdapter< if (gated && !supportsReferenceMedia(model)) { throw new Error( `byteplus: ${model} does not support reference images. Reference ` + - `media is available on the Seedance 2.0 family; on this model use ` + - `'start_frame' / 'end_frame' images instead.`, + `media is available on Seedance 2.5 and the 2.0 family; on this ` + + `model use 'start_frame' / 'end_frame' images instead.`, ) } visualReferences++ @@ -311,7 +313,7 @@ export class BytePlusVideoAdapter< if (gated && !supportsReferenceMedia(model)) { throw new Error( `byteplus: ${model} does not accept video prompt parts. Reference ` + - `video is available on the Seedance 2.0 family only.`, + `video is available on Seedance 2.5 and the 2.0 family only.`, ) } visualReferences++ @@ -326,7 +328,7 @@ export class BytePlusVideoAdapter< if (gated && !supportsReferenceMedia(model)) { throw new Error( `byteplus: ${model} does not accept audio prompt parts. Reference ` + - `audio is available on the Seedance 2.0 family only.`, + `audio is available on Seedance 2.5 and the 2.0 family only.`, ) } audioReferences++ @@ -372,10 +374,16 @@ export class BytePlusVideoAdapter< ) } - if (gated && audioReferences > 0 && visualReferences === 0) { + if ( + gated && + audioReferences > 0 && + visualReferences === 0 && + !supportsAudioOnlyReference(model) + ) { throw new Error( `byteplus: a reference audio input cannot be the only reference on ` + - `model ${model}. Pair it with a reference image or video.`, + `model ${model}. Pair it with a reference image or video, or use ` + + `Seedance 2.5 which accepts audio-only reference input.`, ) } diff --git a/packages/ai-byteplus/src/index.ts b/packages/ai-byteplus/src/index.ts index e4a613cff..0a752d6e1 100644 --- a/packages/ai-byteplus/src/index.ts +++ b/packages/ai-byteplus/src/index.ts @@ -21,11 +21,13 @@ export { parseBytePlusVideoSize, resolveBytePlusVideoResolution, resolveBytePlusVideoSize, + supportsAudioOnlyReference, supportsLastFrame, supportsReferenceMedia, } from './video/video-provider-options' export type { BytePlusVideoModelProviderOptionsByName, + BytePlusVideoOutputFormat, BytePlusVideoProviderOptions, BytePlusVideoServiceTier, } from './video/video-provider-options' diff --git a/packages/ai-byteplus/src/model-meta.ts b/packages/ai-byteplus/src/model-meta.ts index f98baed08..f911f90f8 100644 --- a/packages/ai-byteplus/src/model-meta.ts +++ b/packages/ai-byteplus/src/model-meta.ts @@ -488,10 +488,11 @@ export type BytePlusVideoRatio = /** * Resolution tiers accepted by the Seedance task API. * - * All four are probe-verified per model (2026-07-31). Two findings contradict - * the BytePlus prose docs: there is **no 2K tier on any Seedance model** — - * `2k`/`2K` is rejected everywhere, including on the 2.0 flagship documented - * as reaching 4K — and `4k` exists only on `dreamina-seedance-2-0-260128`. + * Resolution tiers are model-specific (see + * {@link BytePlusVideoModelResolutionByName}). Two findings that still + * contradict older BytePlus prose: there is **no 2K tier on any Seedance + * model**, and `4k` exists only on `dreamina-seedance-2-0-260128` (Seedance + * 2.5 is 480p/720p only, per the live ModelArk docs). * * The API matches this field case-insensitively (`4K`, `4k` and `1080P` are * all accepted), so this package standardizes on the lowercase spelling. @@ -508,11 +509,18 @@ export type BytePlusVideoSize< TResolution extends BytePlusVideoResolution = BytePlusVideoResolution, > = BytePlusVideoRatio | `${BytePlusVideoRatio}_${TResolution}` -// The Seedance 2.0 family's `audio` input modality is docs-derived, not -// live-probed: the docs' multimodal-reference caps list `reference_audio` -// parts (up to 3, never sent without a visual reference). Every 2.0 model id -// below is itself probe-verified live; only the audio-reference capability -// rests on the docs. +// Multimodal reference-media capabilities (reference images / video / audio) +// are docs-derived from the ModelArk create-task page. Model ids and the +// resolution / duration tables for 2.0 were also live-probed on 2026-07-31; +// 2.5 lands from the public docs once the model was fully opened (2026-08-07). +const DREAMINA_SEEDANCE_2_5 = { + name: 'dreamina-seedance-2-5-260628', + supports: { + input: ['text', 'image', 'video', 'audio'], + output: ['video', 'audio'], + }, +} as const satisfies ModelMeta + const DREAMINA_SEEDANCE_2_0 = { name: 'dreamina-seedance-2-0-260128', supports: { @@ -565,6 +573,7 @@ const SEEDANCE_1_0_PRO_FAST = { * All supported Seedance video model identifiers. */ export const BYTEPLUS_VIDEO_MODELS = [ + DREAMINA_SEEDANCE_2_5.name, DREAMINA_SEEDANCE_2_0.name, DREAMINA_SEEDANCE_2_0_FAST.name, DREAMINA_SEEDANCE_2_0_MINI.name, @@ -580,11 +589,12 @@ export type BytePlusVideoModel = (typeof BYTEPLUS_VIDEO_MODELS)[number] /** * Type-only map from video model name to the non-text prompt modalities it - * accepts. The Seedance 2.0 family takes multimodal references (start/end - * frames, reference images, reference video and audio); the 1.x models take - * start/end frames only. + * accepts. Seedance 2.5 and the 2.0 family take multimodal references + * (start/end frames, reference images, reference video and audio); the 1.x + * models take start/end frames only. */ export type BytePlusVideoModelInputModalitiesByName = { + [DREAMINA_SEEDANCE_2_5.name]: readonly ['image', 'video', 'audio'] [DREAMINA_SEEDANCE_2_0.name]: readonly ['image', 'video', 'audio'] [DREAMINA_SEEDANCE_2_0_FAST.name]: readonly ['image', 'video', 'audio'] [DREAMINA_SEEDANCE_2_0_MINI.name]: readonly ['image', 'video', 'audio'] @@ -596,10 +606,13 @@ export type BytePlusVideoModelInputModalitiesByName = { /** * Type-only map from video model name to the resolutions it accepts. * - * Probe-verified per model on 2026-07-31. Note `seedance-1-0-pro-fast-251015` - * does accept `1080p`, despite the BytePlus docs listing it as 480p/720p. + * 2.0 / 1.x cells were probe-verified on 2026-07-31; 2.5 comes from the + * public ModelArk create-task docs (2026-08-07). Note + * `seedance-1-0-pro-fast-251015` does accept `1080p`, despite older BytePlus + * prose listing it as 480p/720p. */ export type BytePlusVideoModelResolutionByName = { + [DREAMINA_SEEDANCE_2_5.name]: '480p' | '720p' [DREAMINA_SEEDANCE_2_0.name]: '480p' | '720p' | '1080p' | '4k' [DREAMINA_SEEDANCE_2_0_FAST.name]: '480p' | '720p' [DREAMINA_SEEDANCE_2_0_MINI.name]: '480p' | '720p' @@ -621,24 +634,16 @@ export type BytePlusVideoModelSizeByName = { * A Seedance model id: one this package knows, or any other string. * * The open half is a deliberate escape hatch for models BytePlus ships between - * releases of this package. **Seedance 2.5 is the live example.** Its real id - * is `dreamina-seedance-2-5-260628` — note the June date suffix, which is why - * guessing ids around its 2026-07-31 announcement never landed. It is absent - * from the table below because its capability cells are unverified, not - * because it is unreachable: probing it returns 404 `ModelNotOpen` ("your - * account has not activated the model"), so no capability question can be - * answered until someone enables it in the Ark Console. Passing it through - * the escape hatch works today for an account that has. - * - * Adding a model here *narrows* it — the adapter's guards switch on and reject - * against this file's tables. For a model whose real limits are unknown that - * is strictly worse than the open path, which lets Ark judge. So an id lands - * here only once probed. + * releases of this package. Adding a model to {@link BYTEPLUS_VIDEO_MODELS} + * *narrows* it — the adapter's guards switch on and reject against this + * file's tables. For a model whose real limits are unknown that is strictly + * worse than the open path, which lets Ark judge. So an id lands in the + * known table only once its capability cells are documented or probed. * * Discovering ids: `GET /models` on the Ark data plane enumerates the catalog - * (id, `task_type`, `modalities`, `status`) and is how 2.5 was found. It is - * not exhaustive — `seedream-5-0-lite-260128` answers requests but is missing - * from the listing — so absence there is not evidence of absence. The ModelArk + * (id, `task_type`, `modalities`, `status`). It is not exhaustive — + * `seedream-5-0-lite-260128` answers requests but is missing from the + * listing — so absence there is not evidence of absence. The ModelArk * release notes (https://docs.byteplus.com/en/docs/ModelArk/1159178) are the * other watch surface. * @@ -651,7 +656,7 @@ export type BytePlusVideoModelSizeByName = { * Unknown ids trade compile-time narrowing for reach: the full size surface is * accepted, provider options are ungated, and the adapter's model-specific * runtime guards stand down so a new model's legitimate request reaches Ark. - * Known ids keep their probe-verified narrowing. + * Known ids keep their documented / probe-verified narrowing. */ export type BytePlusVideoModelOrString = BytePlusVideoModel | (string & {}) @@ -694,8 +699,8 @@ export function isKnownBytePlusVideoModel( * Per-model duration type. Seedance accepts any integer second inside the * model's range, so this is a continuous range expressed as `number` — a * literal union cannot represent it. (The API also accepts `duration: -1` on - * Seedance 2.0 and 1.5-pro to let the model choose; that is reachable through - * provider options, not through the generic `duration`.) + * Seedance 2.5, 2.0 and 1.5-pro to let the model choose; that is reachable + * through provider options, not through the generic `duration`.) */ export type BytePlusVideoModelDurationByName = { [K in BytePlusVideoModel]: number @@ -709,6 +714,13 @@ export const BYTEPLUS_VIDEO_DURATIONS: { BytePlusVideoModelDurationByName[TModel] > } = { + 'dreamina-seedance-2-5-260628': { + kind: 'range', + min: 4, + max: 30, + step: 1, + unit: 'seconds', + }, 'dreamina-seedance-2-0-260128': { kind: 'range', min: 4, @@ -757,15 +769,15 @@ export const BYTEPLUS_VIDEO_DURATIONS: { * Duration hint for a model this package has no table for. * * Spans every range Seedance has shipped so far (2s on the 1.0 models through - * 15s on the 2.0 family) so `availableDurations()` can still drive a UI. It is + * 30s on Seedance 2.5) so `availableDurations()` can still drive a UI. It is * a hint, not a contract: the adapter does **not** snap an unknown model's - * duration against it, because clamping a future model's legitimate 20-second - * request down to 15 would corrupt the request rather than protect it. + * duration against it, because clamping a future model's legitimate longer + * request down to 30 would corrupt the request rather than protect it. */ export const BYTEPLUS_VIDEO_FALLBACK_DURATIONS: DurationOptions = { kind: 'range', min: 2, - max: 15, + max: 30, step: 1, unit: 'seconds', } diff --git a/packages/ai-byteplus/src/video/video-provider-options.ts b/packages/ai-byteplus/src/video/video-provider-options.ts index 841327f08..1d36c51fb 100644 --- a/packages/ai-byteplus/src/video/video-provider-options.ts +++ b/packages/ai-byteplus/src/video/video-provider-options.ts @@ -2,13 +2,15 @@ * Provider options and per-model capability tables for the BytePlus Seedance * video models. * - * Every applicability claim below was probed live against + * Applicability for Seedance 1.x / 2.0 was probed live against * `https://ark.ap-southeast.bytepluses.com/api/v3` on 2026-07-31. The probe * sent an out-of-range `seed` alongside the field under test, so requests that * passed validation still failed before a task was created (nothing billed): * an error naming the field under test means "rejected", an error naming * `seed` means "accepted". Ark reports only one arbitrary invalid parameter - * per request, so each cell was retried until a verdict repeated. + * per request, so each cell was retried until a verdict repeated. Seedance 2.5 + * cells come from the public ModelArk create-task docs once the model was + * fully opened (2026-08-07). * * Ark rejects an inapplicable field outright — "the specified parameter * `draft` is not supported for model seedance-1-0-pro in t2v, must be empty" — @@ -17,15 +19,15 @@ * * **Where the adapter guards, and where it doesn't** (deliberate, not an * oversight). Scalar applicability — `service_tier`, `draft`, `priority`, - * `frames`, `camera_fixed` — is left to Ark, whose 400 names the offending - * field and the model precisely enough to act on, and whose per-model rules - * shift as BytePlus ships models. Duplicating that here would mean a table - * that silently goes stale and starts rejecting requests the API would have - * accepted. The adapter guards locally only where the API's own error is - * misleading or arrives too late to be actionable: prompt media shape (role - * vocabulary, frame-vs-reference exclusivity, frame cardinality) and the - * resolution tier, both of which are derived from a caller's `prompt` / - * `size` rather than passed through verbatim. + * `frames`, `camera_fixed`, `output_format` — is left to Ark, whose 400 names + * the offending field and the model precisely enough to act on, and whose + * per-model rules shift as BytePlus ships models. Duplicating that here would + * mean a table that silently goes stale and starts rejecting requests the API + * would have accepted. The adapter guards locally only where the API's own + * error is misleading or arrives too late to be actionable: prompt media shape + * (role vocabulary, frame-vs-reference exclusivity, frame cardinality, + * audio-only reference) and the resolution tier, both of which are derived + * from a caller's `prompt` / `size` rather than passed through verbatim. * * @experimental Video generation is an experimental feature and may change. */ @@ -47,13 +49,24 @@ import type { * price, with no latency guarantee. Task ids come back with a `cgt-batch-` * prefix (live-verified). * - * Only the Seedance 1.x models accept this field. The Seedance 2.0 family - * rejects it ("service_tier is not supported … must be empty"). + * Only the Seedance 1.x models accept this field. Seedance 2.5 and the 2.0 + * family reject it ("service_tier is not supported … must be empty" / "not + * currently supported"). * * @experimental Video generation is an experimental feature and may change. */ export type BytePlusVideoServiceTier = 'default' | 'flex' +/** + * Container format of the generated video. + * + * Seedance 2.5 documents `mp4` (default) and `mov`. Other models historically + * return `mp4` only; scalar applicability is left to Ark (see file header). + * + * @experimental Video generation is an experimental feature and may change. + */ +export type BytePlusVideoOutputFormat = 'mp4' | 'mov' + /** * Provider-specific options for Seedance video generation. These map one-to-one * onto the create-task request body and take precedence over the values the @@ -69,8 +82,8 @@ export interface BytePlusVideoProviderOptions { /** * Output aspect ratio. Overrides the ratio half of the generic `size`. * - * `adaptive` (follow the input frame) is the default on Seedance 2.0 and - * 1.5-pro but is rejected by Seedance 1.0-pro / 1.0-pro-fast for + * `adaptive` (follow the input frame) is the default on Seedance 2.5, 2.0 + * and 1.5-pro but is rejected by Seedance 1.0-pro / 1.0-pro-fast for * text-to-video. */ ratio?: BytePlusVideoRatio @@ -78,8 +91,8 @@ export interface BytePlusVideoProviderOptions { /** * Output resolution tier. Overrides the resolution half of the generic * `size`. Matched case-insensitively by the API; this package uses - * lowercase throughout. `4k` exists only on `dreamina-seedance-2-0-260128`, - * and there is no 2K tier on any model. + * lowercase throughout. `4k` exists only on `dreamina-seedance-2-0-260128` + * (Seedance 2.5 is 480p/720p only), and there is no 2K tier on any model. */ resolution?: BytePlusVideoResolution @@ -87,8 +100,8 @@ export interface BytePlusVideoProviderOptions { * Whole seconds of output. Overrides the generic `duration`, and unlike it * is sent verbatim rather than snapped into the model's range. * - * `-1` asks the model to choose its own length; accepted by Seedance 2.0 - * and 1.5-pro only. + * `-1` asks the model to choose its own length; accepted by Seedance 2.5, + * 2.0 and 1.5-pro only. On 2.5 video-editing tasks, `-1` is required. */ duration?: number @@ -111,7 +124,7 @@ export interface BytePlusVideoProviderOptions { * Appends a "fix the camera" instruction to the prompt. Best-effort — the * model is not constrained to obey it. * - * Seedance 1.5-pro, 1.0-pro and 1.0-pro-fast only; the 2.0 family rejects + * Seedance 1.5-pro, 1.0-pro and 1.0-pro-fast only; the 2.x family rejects * it. */ camera_fixed?: boolean @@ -125,12 +138,13 @@ export interface BytePlusVideoProviderOptions { * better results. * * Accepted by every model at the API's validation layer, but only Seedance - * 2.0 and 1.5-pro actually produce audio. + * 2.5, 2.0 and 1.5-pro actually produce audio. */ generate_audio?: boolean /** - * Inference queue. Seedance 1.x only — the 2.0 family has no offline tier. + * Inference queue. Seedance 1.x only — Seedance 2.5 and the 2.0 family have + * no offline tier. */ service_tier?: BytePlusVideoServiceTier @@ -150,15 +164,21 @@ export interface BytePlusVideoProviderOptions { draft?: boolean /** - * Queue priority, `[0, 9]`. Seedance 2.0 family only — 1.5-pro rejects it, - * and the 1.0 models accept it without acting on it. + * Queue priority, `[0, 9]`. Seedance 2.5 and the 2.0 family — 1.5-pro + * rejects it, and the 1.0 models accept it without acting on it. */ priority?: number + /** + * Container of the generated video. Seedance 2.5 documents `mp4` (default) + * and `mov`; other models historically ship `mp4` only. + */ + output_format?: BytePlusVideoOutputFormat + /** * Seconds after `created_at` at which an unfinished task is abandoned and * marked `expired`. Documented range `[3600, 259200]`, default 172800 - * (48 hours). The floor is enforced on Seedance 1.x but not on the 2.0 + * (48 hours). The floor is enforced on Seedance 1.x but not on the 2.x * family. */ execution_expires_after?: number @@ -206,17 +226,19 @@ const BYTEPLUS_VIDEO_RATIOS: ReadonlyArray = [ ] /** - * Resolutions each model accepts, live-probed. + * Resolutions each model accepts. * - * Two findings here contradict the BytePlus prose docs and are worth calling - * out: there is no 2K tier on any Seedance model (`2k`/`2K` is rejected - * everywhere, including on the 2.0 flagship whose docs advertise "up to 4K"), - * and `seedance-1-0-pro-fast-251015` does accept `1080p` despite being - * documented as 480p/720p only. + * 2.0 / 1.x cells were live-probed; 2.5 comes from the public ModelArk docs. + * Two findings still contradict older prose: there is no 2K tier on any + * Seedance model (`2k`/`2K` is rejected everywhere), and + * `seedance-1-0-pro-fast-251015` does accept `1080p` despite being documented + * as 480p/720p only. Seedance 2.5 is 480p/720p only — it does **not** offer + * the 2.0 flagship's 4k tier. */ const BYTEPLUS_VIDEO_RESOLUTIONS: { readonly [K in BytePlusVideoModel]: ReadonlyArray } = { + 'dreamina-seedance-2-5-260628': ['480p', '720p'], 'dreamina-seedance-2-0-260128': ['480p', '720p', '1080p', '4k'], 'dreamina-seedance-2-0-fast-260128': ['480p', '720p'], 'dreamina-seedance-2-0-mini-260615': ['480p', '720p'], @@ -231,6 +253,7 @@ const BYTEPLUS_VIDEO_RESOLUTIONS: { * models reject it with "the specified task_type r2v does not support model …". */ const BYTEPLUS_VIDEO_REFERENCE_MEDIA_MODELS: ReadonlySet = new Set([ + 'dreamina-seedance-2-5-260628', 'dreamina-seedance-2-0-260128', 'dreamina-seedance-2-0-fast-260128', 'dreamina-seedance-2-0-mini-260615', @@ -242,6 +265,7 @@ const BYTEPLUS_VIDEO_REFERENCE_MEDIA_MODELS: ReadonlySet = new Set([ * does text-to-video and single-first-frame image-to-video only. */ const BYTEPLUS_VIDEO_LAST_FRAME_MODELS: ReadonlySet = new Set([ + 'dreamina-seedance-2-5-260628', 'dreamina-seedance-2-0-260128', 'dreamina-seedance-2-0-fast-260128', 'dreamina-seedance-2-0-mini-260615', @@ -249,6 +273,15 @@ const BYTEPLUS_VIDEO_LAST_FRAME_MODELS: ReadonlySet = new Set([ 'seedance-1-0-pro-250528', ]) +/** + * Models that accept a reference-audio input without a visual reference + * alongside it. Seedance 2.5 documents audio-only reference-to-video; the 2.0 + * family rejects it with "reference_audio cannot be the only reference input". + */ +const BYTEPLUS_VIDEO_AUDIO_ONLY_REFERENCE_MODELS: ReadonlySet = new Set( + ['dreamina-seedance-2-5-260628'], +) + /** * True when the model is *known* to support reference-media mode (reference * images, video and audio). An id this package has no metadata for answers @@ -271,6 +304,16 @@ export function supportsLastFrame(model: string): boolean { return BYTEPLUS_VIDEO_LAST_FRAME_MODELS.has(model) } +/** + * True when the model is *known* to accept a reference-audio input without a + * visual reference. Same unknown-id caveat as {@link supportsReferenceMedia}. + * + * @experimental Video generation is an experimental feature and may change. + */ +export function supportsAudioOnlyReference(model: string): boolean { + return BYTEPLUS_VIDEO_AUDIO_ONLY_REFERENCE_MODELS.has(model) +} + /** * Splits a `size` template into its Seedance request fields. * diff --git a/packages/ai-byteplus/src/video/wire-types.ts b/packages/ai-byteplus/src/video/wire-types.ts index 1015d32fb..224aa38cf 100644 --- a/packages/ai-byteplus/src/video/wire-types.ts +++ b/packages/ai-byteplus/src/video/wire-types.ts @@ -85,8 +85,11 @@ export interface BytePlusVideoVideoContent { } /** - * An audio input. Live-verified: audio can only accompany another reference - * input — "reference_audio cannot be the only reference input". + * An audio input. + * + * On Seedance 2.0, audio can only accompany another reference input — + * "reference_audio cannot be the only reference input". Seedance 2.5 + * documents audio-only reference-to-video. */ export interface BytePlusVideoAudioContent { type: 'audio_url' @@ -98,9 +101,10 @@ export interface BytePlusVideoAudioContent { * One entry of the `content[]` array. * * The create schema declares `maxItems: 5`, but the live API does not enforce - * it — 7 entries (6 reference images plus text) were accepted on - * `dreamina-seedance-2-0-260128`. The adapter therefore does not cap the array - * locally; a genuinely over-long request gets whatever Ark decides to say. + * it — Seedance 2.5 accepts up to 30 reference images plus videos and audio, + * and 7 entries were accepted on `dreamina-seedance-2-0-260128`. The adapter + * therefore does not cap the array locally; a genuinely over-long request + * gets whatever Ark decides to say. */ export type BytePlusVideoContentPart = | BytePlusVideoTextContent @@ -115,14 +119,13 @@ export type BytePlusVideoContentPart = * model-dependent — Ark rejects an inapplicable field outright ("the * specified parameter `draft` is not supported for model … must be empty") * rather than ignoring it, so the adapter only sends what the caller asked - * for. See `video-provider-options.ts` for the live-probed applicability - * matrix. + * for. See `video-provider-options.ts` for the applicability matrix. */ export interface BytePlusVideoCreateRequest { /** Seedance model id (or a preconfigured endpoint id). */ model: string - /** Prompt text plus any image / video / audio inputs, max 5 entries. */ + /** Prompt text plus any image / video / audio inputs. */ content: Array /** Output aspect ratio, e.g. `16:9`. `adaptive` follows the input frame. */ @@ -131,7 +134,10 @@ export interface BytePlusVideoCreateRequest { /** Resolution tier, e.g. `720p`. Matched case-insensitively by the API. */ resolution?: string - /** Whole seconds of output. `-1` lets the model choose (Seedance 2.0 / 1.5). */ + /** + * Whole seconds of output. `-1` lets the model choose (Seedance 2.5 / 2.0 / + * 1.5-pro). + */ duration?: number /** Frame count, an alternative to `duration` that allows fractional seconds. */ @@ -161,6 +167,9 @@ export interface BytePlusVideoCreateRequest { /** Queue priority `[0, 9]`. */ priority?: number + /** Container of the generated video, e.g. `mp4` or `mov`. */ + output_format?: string + /** Seconds from `created_at` after which the task is marked `expired`. */ execution_expires_after?: number diff --git a/packages/ai-byteplus/tests/model-meta.test.ts b/packages/ai-byteplus/tests/model-meta.test.ts index 2afe3b493..a007a2919 100644 --- a/packages/ai-byteplus/tests/model-meta.test.ts +++ b/packages/ai-byteplus/tests/model-meta.test.ts @@ -34,13 +34,14 @@ describe('model lists', () => { }) it('keeps the prefixes the API requires', () => { - // Seedance 2.0 only resolves with the `dreamina-` prefix, and the 2.1 + // Seedance 2.x only resolves with the `dreamina-` prefix, and the 2.1 // turbo chat model / 5.0 Pro image model echo back the `dola-` prefix. for (const model of BYTEPLUS_VIDEO_MODELS) { - if (model.includes('seedance-2-0')) { + if (/seedance-2-[05]/.test(model)) { expect(model.startsWith('dreamina-')).toBe(true) } } + expect(BYTEPLUS_VIDEO_MODELS).toContain('dreamina-seedance-2-5-260628') expect(BYTEPLUS_CHAT_MODELS).toContain('dola-seed-2-1-turbo-260628') expect(BYTEPLUS_IMAGE_MODELS).toContain('dola-seedream-5-0-pro-260628') }) @@ -108,6 +109,9 @@ describe('video durations', () => { }) it('encodes the per-family ranges', () => { + expect( + getBytePlusVideoDurationOptions('dreamina-seedance-2-5-260628'), + ).toMatchObject({ min: 4, max: 30 }) expect( getBytePlusVideoDurationOptions('dreamina-seedance-2-0-260128'), ).toMatchObject({ min: 4, max: 15 }) diff --git a/packages/ai-byteplus/tests/video.test.ts b/packages/ai-byteplus/tests/video.test.ts index b6d56e87c..5cdd8f50e 100644 --- a/packages/ai-byteplus/tests/video.test.ts +++ b/packages/ai-byteplus/tests/video.test.ts @@ -571,7 +571,7 @@ describe('createVideoJob content roles', () => { expect(fetchMock).not.toHaveBeenCalled() }) - it('rejects a reference audio that is the only reference input', async () => { + it('rejects a reference audio that is the only reference input on 2.0', async () => { const fetchMock = mockFetch(() => jsonResponse({ id: JOB_ID })) const adapter = adapterWithFetch(fetchMock, 'dreamina-seedance-2-0-260128') @@ -590,6 +590,60 @@ describe('createVideoJob content roles', () => { ).rejects.toThrow(/cannot be the only reference/) }) + it('accepts audio-only reference input on Seedance 2.5', async () => { + const fetchMock = mockFetch(() => jsonResponse({ id: JOB_ID })) + const adapter = adapterWithFetch(fetchMock, 'dreamina-seedance-2-5-260628') + + await adapter.createVideoJob( + createOptions({ + prompt: [ + { type: 'text', content: 'use this score' }, + { + type: 'audio', + source: { type: 'url', value: 'https://x/a.mp3' }, + }, + ], + }), + ) + + expect(sentRequest(fetchMock).content).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: 'audio_url', + role: 'reference_audio', + }), + ]), + ) + }) + + it('rejects 1080p and 4k on Seedance 2.5', async () => { + const fetchMock = mockFetch(() => jsonResponse({ id: JOB_ID })) + const adapter = adapterWithFetch(fetchMock, 'dreamina-seedance-2-5-260628') + + await expect( + adapter.createVideoJob( + createOptions({ size: '16:9_1080p' as '16:9_720p' }), + ), + ).rejects.toThrow(/resolution "1080p" is not supported.*480p, 720p/s) + await expect( + adapter.createVideoJob(createOptions({ size: '16:9_4k' as '16:9_720p' })), + ).rejects.toThrow(/resolution "4k" is not supported/) + expect(fetchMock).not.toHaveBeenCalled() + }) + + it('forwards output_format on Seedance 2.5', async () => { + const fetchMock = mockFetch(() => jsonResponse({ id: JOB_ID })) + const adapter = adapterWithFetch(fetchMock, 'dreamina-seedance-2-5-260628') + + await adapter.createVideoJob( + createOptions({ + modelOptions: { output_format: 'mov' }, + }), + ) + + expect(sentRequest(fetchMock).output_format).toBe('mov') + }) + it('rejects reference images on a model without reference mode', async () => { const fetchMock = mockFetch(() => jsonResponse({ id: JOB_ID })) const adapter = adapterWithFetch(fetchMock, 'seedance-1-5-pro-251215') @@ -991,12 +1045,11 @@ describe('getVideoUrl', () => { }) }) -// Seedance 2.5 was announced 2026-07-31 as a consumer product with the Ark -// API "available soon"; no 2.5 id resolves on the data plane yet. Rather than -// ship a guessed id, the factories accept any string so the real id works the -// day BytePlus publishes it. These cover that path. +// The factories accept any string so an id BytePlus ships between package +// releases still works. Seedance 2.5 used to be this example; it is now a +// first-class known model. These cover the still-open unknown-id path. describe('unknown model ids', () => { - const FUTURE = 'dreamina-seedance-2-5-260901' + const FUTURE = 'dreamina-seedance-9-9-999999' it('accepts a bare string through the factories', async () => { const fetchMock = mockFetch(() => jsonResponse({ id: JOB_ID })) @@ -1032,11 +1085,11 @@ describe('unknown model ids', () => { fetch: fetchMock, }) - // 20s is outside every range shipping today; clamping to 15 would corrupt + // 45s is outside every range shipping today; clamping to 30 would corrupt // a legitimate request for a model with a longer ceiling. - await adapter.createVideoJob(createOptions({ duration: 20 })) + await adapter.createVideoJob(createOptions({ duration: 45 })) - expect(sentRequest(fetchMock).duration).toBe(20) + expect(sentRequest(fetchMock).duration).toBe(45) }) it('passes provider options through ungated', async () => { @@ -1045,7 +1098,7 @@ describe('unknown model ids', () => { fetch: fetchMock, }) - // draft is 1.5-pro-only and priority is 2.0-only today; on an unknown + // draft is 1.5-pro-only and priority is 2.x-only today; on an unknown // model neither is second-guessed. await adapter.createVideoJob( createOptions({ modelOptions: { draft: true, priority: 3 } }), @@ -1060,8 +1113,8 @@ describe('unknown model ids', () => { fetch: fetchMock, }) - // Frame + reference is mutually exclusive on every model today. If 2.5 - // relaxes it, blocking the request locally would defeat the escape hatch. + // Frame + reference is mutually exclusive on every known model. A future + // model that relaxes it must not be blocked by today's tables. await adapter.createVideoJob( createOptions({ prompt: [ @@ -1106,7 +1159,7 @@ describe('unknown model ids', () => { expect(adapter.availableDurations()).toEqual({ kind: 'range', min: 2, - max: 15, + max: 30, step: 1, unit: 'seconds', }) @@ -1114,6 +1167,7 @@ describe('unknown model ids', () => { it('knows which ids it has metadata for', () => { expect(isKnownBytePlusVideoModel('seedance-1-5-pro-251215')).toBe(true) + expect(isKnownBytePlusVideoModel('dreamina-seedance-2-5-260628')).toBe(true) expect(isKnownBytePlusVideoModel(FUTURE)).toBe(false) }) }) @@ -1123,12 +1177,15 @@ describe('model id typing', () => { // A plain string compiles through both factories — the escape hatch's // whole point. `toBeCallableWith` checks the signature without running // byteplusVideo, which would need ARK_API_KEY. - expectTypeOf(byteplusVideo).toBeCallableWith('dreamina-seedance-2-5-260901') - const adapter = createBytePlusVideo('dreamina-seedance-2-5-260901', 'k') - expectTypeOf(adapter.model).toEqualTypeOf<'dreamina-seedance-2-5-260901'>() + expectTypeOf(byteplusVideo).toBeCallableWith('dreamina-seedance-9-9-999999') + const adapter = createBytePlusVideo('dreamina-seedance-9-9-999999', 'k') + expectTypeOf(adapter.model).toEqualTypeOf<'dreamina-seedance-9-9-999999'>() // Known ids keep their probe-verified size union, so a tier the model // does not offer stays a compile error. + expectTypeOf< + ResolveBytePlusVideoSize<'dreamina-seedance-2-5-260628'> + >().toEqualTypeOf>() expectTypeOf< ResolveBytePlusVideoSize<'dreamina-seedance-2-0-fast-260128'> >().toEqualTypeOf>() @@ -1145,6 +1202,7 @@ describe('model id typing', () => { describe('durations', () => { it.each([ + ['dreamina-seedance-2-5-260628', 4, 30], ['dreamina-seedance-2-0-260128', 4, 15], ['dreamina-seedance-2-0-fast-260128', 4, 15], ['dreamina-seedance-2-0-mini-260615', 4, 15],