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.
= 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],