Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/seedance-25-1080p.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
---
'@tanstack/ai-byteplus': patch
---

Allow 1080p on Seedance 2.5 (`dreamina-seedance-2-5-260628`).

BytePlus ModelArk and the live fal Seedance 2.5 spec now accept native
`480p` / `720p` / `1080p` (default still `720p`). `4k` remains exclusive
to `dreamina-seedance-2-0-260128`.
6 changes: 3 additions & 3 deletions docs/adapters/byteplus.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -332,7 +332,7 @@ Seedance options are model-specific, and **Ark rejects an inapplicable field wit

`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: 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.
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 accepts **480p/720p/1080p**, 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`. 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.

Expand All@@ -347,7 +347,7 @@ import { byteplusVideo } from '@tanstack/ai-byteplus'
const { jobId } = await generateVideo({
adapter: byteplusVideo('dreamina-seedance-2-5-260628'),
prompt: 'a guitar being played in a store',
size: '16:9_720p',
size: '16:9_1080p',
duration: 10,
modelOptions: {
generate_audio: true,
Expand All@@ -360,7 +360,7 @@ const { jobId } = await generateVideo({
| Capability | Seedance 2.5 |
| --- | --- |
| Duration | 4–30s, or `-1` (model chooses; required for video-editing tasks) |
| Resolution | `480p`, `720p`(default `720p`) — no 1080p / 4k |
| Resolution | `480p`, `720p`, `1080p` (default `720p`) — no 4k |
| Reference media | images 1–30, videos 0–10, audio 0–10; **audio-only allowed** |
| First + last frame | yes |
| `priority` / `generate_audio` | yes |
Expand Down
4 changes: 2 additions & 2 deletions docs/config.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -452,7 +452,7 @@
"label": "Video Generation",
"to": "media/video-generation",
"addedAt": "2026-04-15",
"updatedAt": "2026-08-18"
"updatedAt": "2026-08-19"
},
{
"label": "Generation Hooks",
Expand DownExpand Up@@ -916,7 +916,7 @@
"label": "BytePlus",
"to": "adapters/byteplus",
"addedAt": "2026-08-04",
"updatedAt": "2026-08-07"
"updatedAt": "2026-08-19"
}
]
},
Expand Down
2 changes: 1 addition & 1 deletion docs/media/video-generation.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -790,7 +790,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`. 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:
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`) accepts 480p/720p/1080p and runs up to 30 seconds. There is no 2K tier on any Seedance model:

```typescript
import { generateVideo } from "@tanstack/ai";
Expand Down
3 changes: 2 additions & 1 deletion examples/ts-react-media/src/lib/seedance.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -60,7 +60,8 @@ export const SEEDANCE_MODELS = [
{
id: 'dreamina-seedance-2-5-260628',
name: 'Seedance 2.5',
blurb: 'Current multimodal flagship — up to 30s, audio-only references',
blurb:
'Current multimodal flagship — up to 30s, native 1080p, audio-only references',
extras: {
generateAudio: true,
cameraFixed: false,
Expand Down
12 changes: 6 additions & 6 deletions packages/ai-byteplus/src/model-meta.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -491,8 +491,8 @@ export type BytePlusVideoRatio =
* 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).
* model**, and `4k` exists only on `dreamina-seedance-2-0-260128`. Seedance
* 2.5 accepts 480p/720p/1080p (ModelArk + fal spec, 2026-08-19).
*
* The API matches this field case-insensitively (`4K`, `4k` and `1080P` are
* all accepted), so this package standardizes on the lowercase spelling.
Expand DownExpand Up@@ -607,12 +607,12 @@ export type BytePlusVideoModelInputModalitiesByName = {
* Type-only map from video model name to the resolutions it accepts.
*
* 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.
* public ModelArk create-task docs, refreshed 2026-08-19 when native 1080p
* shipped. 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_5.name]: '480p' | '720p' | '1080p'
[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'
Expand Down
19 changes: 10 additions & 9 deletions packages/ai-byteplus/src/video/video-provider-options.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,8 +9,8 @@
* 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. Seedance 2.5
* cells come from the public ModelArk create-task docs once the model was
* fully opened (2026-08-07).
* cells come from the public ModelArk create-task docs (opened 2026-08-07);
* its resolution row was refreshed 2026-08-19 when native 1080p shipped.
*
* Ark rejects an inapplicable field outright — "the specified parameter
* `draft` is not supported for model seedance-1-0-pro in t2v, must be empty" —
Expand DownExpand Up@@ -91,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`
* (Seedance 2.5 is 480p/720p only), and there is no 2K tier on any model.
* lowercase throughout. `4k` exists only on `dreamina-seedance-2-0-260128`,
* and there is no 2K tier on any model.
*/
resolution?: BytePlusVideoResolution

Expand DownExpand Up@@ -228,17 +228,18 @@ const BYTEPLUS_VIDEO_RATIOS: ReadonlyArray<string> = [
/**
* Resolutions each model accepts.
*
* 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
* 2.0 / 1.x cells were live-probed; 2.5 comes from the public ModelArk docs
* and the live fal Seedance 2.5 spec (modelschemas.com, 2026-08-19). 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
* as 480p/720p only. Seedance 2.5 is 480p/720p/1080p — it does **not** offer
* the 2.0 flagship's 4k tier.
*/
const BYTEPLUS_VIDEO_RESOLUTIONS: {
readonly [K in BytePlusVideoModel]: ReadonlyArray<BytePlusVideoResolution>
} = {
'dreamina-seedance-2-5-260628': ['480p', '720p'],
'dreamina-seedance-2-5-260628': ['480p', '720p', '1080p'],
'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'],
Expand Down
20 changes: 13 additions & 7 deletions packages/ai-byteplus/tests/video.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -616,18 +616,24 @@ describe('createVideoJob content roles', () => {
)
})

it('rejects 1080p and 4k on Seedance 2.5', async () => {
it('accepts 1080p 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({ size: '16:9_1080p' }))

expect(sentRequest(fetchMock).resolution).toBe('1080p')
})

it('rejects 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' }),
createOptions({ size: '16:9_4k' as '16:9_1080p' }),
),
).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/)
).rejects.toThrow(/resolution "4k" is not supported.*480p, 720p, 1080p/s)
expect(fetchMock).not.toHaveBeenCalled()
})

Expand DownExpand Up@@ -1185,7 +1191,7 @@ describe('model id typing', () => {
// does not offer stays a compile error.
expectTypeOf<
ResolveBytePlusVideoSize<'dreamina-seedance-2-5-260628'>
>().toEqualTypeOf<BytePlusVideoSize<'480p' | '720p'>>()
>().toEqualTypeOf<BytePlusVideoSize<'480p' | '720p' | '1080p'>>()
expectTypeOf<
ResolveBytePlusVideoSize<'dreamina-seedance-2-0-fast-260128'>
>().toEqualTypeOf<BytePlusVideoSize<'480p' | '720p'>>()
Expand Down
48 changes: 35 additions & 13 deletions testing/e2e/src/routeTree.gen.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,14 +59,15 @@ import { Route as ApiInterruptsTestRouteImport } from './routes/api.interrupts-t
import { Route as ApiImageRouteImport } from './routes/api.image'
import { Route as ApiGenerationPersistenceServerRouteImport } from './routes/api.generation-persistence-server'
import { Route as ApiGenerationPersistenceResumeRouteImport } from './routes/api.generation-persistence-resume'
import { Route as ApiGeminiImageGaModelsRouteImport } from './routes/api.gemini-image-ga-models'
import { Route as ApiGeminiNativeImageWireRouteImport } from './routes/api.gemini-native-image-wire'
import { Route as ApiGeminiImageGaModelsRouteImport } from './routes/api.gemini-image-ga-models'
import { Route as ApiForeignInterruptRouteImport } from './routes/api.foreign-interrupt'
import { Route as ApiEmbeddingRouteImport } from './routes/api.embedding'
import { Route as ApiDurableTakeoverRouteImport } from './routes/api.durable-takeover'
import { Route as ApiDurableDeliveryRouteImport } from './routes/api.durable-delivery'
import { Route as ApiDevtoolsMemoryRouteImport } from './routes/api.devtools-memory'
import { Route as ApiChatRouteImport } from './routes/api.chat'
import { Route as ApiByteplusSeedance1080pWireRouteImport } from './routes/api.byteplus-seedance-1080p-wire'
import { Route as ApiAudioRouteImport } from './routes/api.audio'
import { Route as ApiArktypeToolWireRouteImport } from './routes/api.arktype-tool-wire'
import { Route as ApiAnthropicStructuredUsageRouteImport } from './routes/api.anthropic-structured-usage'
Expand DownExpand Up@@ -338,17 +339,17 @@ const ApiGenerationPersistenceResumeRoute =
path: '/api/generation-persistence-resume',
getParentRoute: () => rootRouteImport,
} as any)
const ApiGeminiImageGaModelsRoute = ApiGeminiImageGaModelsRouteImport.update({
id: '/api/gemini-image-ga-models',
path: '/api/gemini-image-ga-models',
getParentRoute: () => rootRouteImport,
} as any)
const ApiGeminiNativeImageWireRoute =
ApiGeminiNativeImageWireRouteImport.update({
id: '/api/gemini-native-image-wire',
path: '/api/gemini-native-image-wire',
getParentRoute: () => rootRouteImport,
} as any)
const ApiGeminiImageGaModelsRoute = ApiGeminiImageGaModelsRouteImport.update({
id: '/api/gemini-image-ga-models',
path: '/api/gemini-image-ga-models',
getParentRoute: () => rootRouteImport,
} as any)
const ApiForeignInterruptRoute = ApiForeignInterruptRouteImport.update({
id: '/api/foreign-interrupt',
path: '/api/foreign-interrupt',
Expand DownExpand Up@@ -379,6 +380,12 @@ const ApiChatRoute = ApiChatRouteImport.update({
path: '/api/chat',
getParentRoute: () => rootRouteImport,
} as any)
const ApiByteplusSeedance1080pWireRoute =
ApiByteplusSeedance1080pWireRouteImport.update({
id: '/api/byteplus-seedance-1080p-wire',
path: '/api/byteplus-seedance-1080p-wire',
getParentRoute: () => rootRouteImport,
} as any)
const ApiAudioRoute = ApiAudioRouteImport.update({
id: '/api/audio',
path: '/api/audio',
Expand DownExpand Up@@ -460,6 +467,7 @@ export interface FileRoutesByFullPath {
'/api/anthropic-structured-usage': typeof ApiAnthropicStructuredUsageRoute
'/api/arktype-tool-wire': typeof ApiArktypeToolWireRoute
'/api/audio': typeof ApiAudioRouteWithChildren
'/api/byteplus-seedance-1080p-wire': typeof ApiByteplusSeedance1080pWireRoute
'/api/chat': typeof ApiChatRoute
'/api/devtools-memory': typeof ApiDevtoolsMemoryRoute
'/api/durable-delivery': typeof ApiDurableDeliveryRoute
Expand DownExpand Up@@ -531,6 +539,7 @@ export interface FileRoutesByTo {
'/api/anthropic-structured-usage': typeof ApiAnthropicStructuredUsageRoute
'/api/arktype-tool-wire': typeof ApiArktypeToolWireRoute
'/api/audio': typeof ApiAudioRouteWithChildren
'/api/byteplus-seedance-1080p-wire': typeof ApiByteplusSeedance1080pWireRoute
'/api/chat': typeof ApiChatRoute
'/api/devtools-memory': typeof ApiDevtoolsMemoryRoute
'/api/durable-delivery': typeof ApiDurableDeliveryRoute
Expand DownExpand Up@@ -603,6 +612,7 @@ export interface FileRoutesById {
'/api/anthropic-structured-usage': typeof ApiAnthropicStructuredUsageRoute
'/api/arktype-tool-wire': typeof ApiArktypeToolWireRoute
'/api/audio': typeof ApiAudioRouteWithChildren
'/api/byteplus-seedance-1080p-wire': typeof ApiByteplusSeedance1080pWireRoute
'/api/chat': typeof ApiChatRoute
'/api/devtools-memory': typeof ApiDevtoolsMemoryRoute
'/api/durable-delivery': typeof ApiDurableDeliveryRoute
Expand DownExpand Up@@ -676,6 +686,7 @@ export interface FileRouteTypes {
| '/api/anthropic-structured-usage'
| '/api/arktype-tool-wire'
| '/api/audio'
| '/api/byteplus-seedance-1080p-wire'
| '/api/chat'
| '/api/devtools-memory'
| '/api/durable-delivery'
Expand DownExpand Up@@ -747,6 +758,7 @@ export interface FileRouteTypes {
| '/api/anthropic-structured-usage'
| '/api/arktype-tool-wire'
| '/api/audio'
| '/api/byteplus-seedance-1080p-wire'
| '/api/chat'
| '/api/devtools-memory'
| '/api/durable-delivery'
Expand DownExpand Up@@ -818,6 +830,7 @@ export interface FileRouteTypes {
| '/api/anthropic-structured-usage'
| '/api/arktype-tool-wire'
| '/api/audio'
| '/api/byteplus-seedance-1080p-wire'
| '/api/chat'
| '/api/devtools-memory'
| '/api/durable-delivery'
Expand DownExpand Up@@ -890,6 +903,7 @@ export interface RootRouteChildren {
ApiAnthropicStructuredUsageRoute: typeof ApiAnthropicStructuredUsageRoute
ApiArktypeToolWireRoute: typeof ApiArktypeToolWireRoute
ApiAudioRoute: typeof ApiAudioRouteWithChildren
ApiByteplusSeedance1080pWireRoute: typeof ApiByteplusSeedance1080pWireRoute
ApiChatRoute: typeof ApiChatRoute
ApiDevtoolsMemoryRoute: typeof ApiDevtoolsMemoryRoute
ApiDurableDeliveryRoute: typeof ApiDurableDeliveryRoute
Expand DownExpand Up@@ -1285,20 +1299,20 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ApiGenerationPersistenceResumeRouteImport
parentRoute: typeof rootRouteImport
}
'/api/gemini-image-ga-models': {
id: '/api/gemini-image-ga-models'
path: '/api/gemini-image-ga-models'
fullPath: '/api/gemini-image-ga-models'
preLoaderRoute: typeof ApiGeminiImageGaModelsRouteImport
parentRoute: typeof rootRouteImport
}
'/api/gemini-native-image-wire': {
id: '/api/gemini-native-image-wire'
path: '/api/gemini-native-image-wire'
fullPath: '/api/gemini-native-image-wire'
preLoaderRoute: typeof ApiGeminiNativeImageWireRouteImport
parentRoute: typeof rootRouteImport
}
'/api/gemini-image-ga-models': {
id: '/api/gemini-image-ga-models'
path: '/api/gemini-image-ga-models'
fullPath: '/api/gemini-image-ga-models'
preLoaderRoute: typeof ApiGeminiImageGaModelsRouteImport
parentRoute: typeof rootRouteImport
}
'/api/foreign-interrupt': {
id: '/api/foreign-interrupt'
path: '/api/foreign-interrupt'
Expand DownExpand Up@@ -1341,6 +1355,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof ApiChatRouteImport
parentRoute: typeof rootRouteImport
}
'/api/byteplus-seedance-1080p-wire': {
id: '/api/byteplus-seedance-1080p-wire'
path: '/api/byteplus-seedance-1080p-wire'
fullPath: '/api/byteplus-seedance-1080p-wire'
preLoaderRoute: typeof ApiByteplusSeedance1080pWireRouteImport
parentRoute: typeof rootRouteImport
}
'/api/audio': {
id: '/api/audio'
path: '/api/audio'
Expand DownExpand Up@@ -1503,6 +1524,7 @@ const rootRouteChildren: RootRouteChildren = {
ApiAnthropicStructuredUsageRoute: ApiAnthropicStructuredUsageRoute,
ApiArktypeToolWireRoute: ApiArktypeToolWireRoute,
ApiAudioRoute: ApiAudioRouteWithChildren,
ApiByteplusSeedance1080pWireRoute: ApiByteplusSeedance1080pWireRoute,
ApiChatRoute: ApiChatRoute,
ApiDevtoolsMemoryRoute: ApiDevtoolsMemoryRoute,
ApiDurableDeliveryRoute: ApiDurableDeliveryRoute,
Expand Down
Loading
Loading