From ce4f5e879656ee79b479b0e57d1e762beadfc1ea Mon Sep 17 00:00:00 2001 From: Tom Beckenham <34339192+tombeckenham@users.noreply.github.com> Date: Wed, 19 Aug 2026 13:02:37 +1000 Subject: [PATCH] feat(ai-byteplus): add 1080p support for Seedance 2.5 (#1146) Seedance 2.5 now accepts native 480p/720p/1080p (default 720p). 4k remains exclusive to dreamina-seedance-2-0-260128. --- .changeset/seedance-25-1080p.md | 9 +++ docs/adapters/byteplus.md | 6 +- docs/config.json | 4 +- docs/media/video-generation.md | 2 +- examples/ts-react-media/src/lib/seedance.ts | 3 +- packages/ai-byteplus/src/model-meta.ts | 12 +-- .../src/video/video-provider-options.ts | 19 ++--- packages/ai-byteplus/tests/video.test.ts | 20 +++-- testing/e2e/src/routeTree.gen.ts | 48 ++++++++---- .../api.byteplus-seedance-1080p-wire.ts | 78 +++++++++++++++++++ .../byteplus-seedance-1080p-wire.spec.ts | 30 +++++++ 11 files changed, 189 insertions(+), 42 deletions(-) create mode 100644 .changeset/seedance-25-1080p.md create mode 100644 testing/e2e/src/routes/api.byteplus-seedance-1080p-wire.ts create mode 100644 testing/e2e/tests/byteplus-seedance-1080p-wire.spec.ts diff --git a/.changeset/seedance-25-1080p.md b/.changeset/seedance-25-1080p.md new file mode 100644 index 0000000000..3fe6b2fc99 --- /dev/null +++ b/.changeset/seedance-25-1080p.md @@ -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`. diff --git a/docs/adapters/byteplus.md b/docs/adapters/byteplus.md index 7a47a5940c..0a806c7597 100644 --- a/docs/adapters/byteplus.md +++ b/docs/adapters/byteplus.md @@ -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. @@ -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, @@ -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 | diff --git a/docs/config.json b/docs/config.json index 32f9ffab32..6ab97268ed 100644 --- a/docs/config.json +++ b/docs/config.json @@ -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", @@ -916,7 +916,7 @@ "label": "BytePlus", "to": "adapters/byteplus", "addedAt": "2026-08-04", - "updatedAt": "2026-08-07" + "updatedAt": "2026-08-19" } ] }, diff --git a/docs/media/video-generation.md b/docs/media/video-generation.md index 4b83af2f92..83e6cae28e 100644 --- a/docs/media/video-generation.md +++ b/docs/media/video-generation.md @@ -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"; diff --git a/examples/ts-react-media/src/lib/seedance.ts b/examples/ts-react-media/src/lib/seedance.ts index 8718f2be15..4932c8486f 100644 --- a/examples/ts-react-media/src/lib/seedance.ts +++ b/examples/ts-react-media/src/lib/seedance.ts @@ -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, diff --git a/packages/ai-byteplus/src/model-meta.ts b/packages/ai-byteplus/src/model-meta.ts index f911f90f8c..9f1a126bc3 100644 --- a/packages/ai-byteplus/src/model-meta.ts +++ b/packages/ai-byteplus/src/model-meta.ts @@ -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. @@ -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' diff --git a/packages/ai-byteplus/src/video/video-provider-options.ts b/packages/ai-byteplus/src/video/video-provider-options.ts index 1d36c51fb7..175abefb28 100644 --- a/packages/ai-byteplus/src/video/video-provider-options.ts +++ b/packages/ai-byteplus/src/video/video-provider-options.ts @@ -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" — @@ -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 @@ -228,17 +228,18 @@ const BYTEPLUS_VIDEO_RATIOS: ReadonlyArray = [ /** * 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 } = { - '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'], diff --git a/packages/ai-byteplus/tests/video.test.ts b/packages/ai-byteplus/tests/video.test.ts index 5cdd8f50eb..73a97bfca7 100644 --- a/packages/ai-byteplus/tests/video.test.ts +++ b/packages/ai-byteplus/tests/video.test.ts @@ -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() }) @@ -1185,7 +1191,7 @@ describe('model id typing', () => { // does not offer stays a compile error. expectTypeOf< ResolveBytePlusVideoSize<'dreamina-seedance-2-5-260628'> - >().toEqualTypeOf>() + >().toEqualTypeOf>() expectTypeOf< ResolveBytePlusVideoSize<'dreamina-seedance-2-0-fast-260128'> >().toEqualTypeOf>() diff --git a/testing/e2e/src/routeTree.gen.ts b/testing/e2e/src/routeTree.gen.ts index dc6e573c26..d6cbc88372 100644 --- a/testing/e2e/src/routeTree.gen.ts +++ b/testing/e2e/src/routeTree.gen.ts @@ -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' @@ -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', @@ -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', @@ -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 @@ -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 @@ -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 @@ -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' @@ -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' @@ -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' @@ -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 @@ -1285,13 +1299,6 @@ 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' @@ -1299,6 +1306,13 @@ declare module '@tanstack/react-router' { 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' @@ -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' @@ -1503,6 +1524,7 @@ const rootRouteChildren: RootRouteChildren = { ApiAnthropicStructuredUsageRoute: ApiAnthropicStructuredUsageRoute, ApiArktypeToolWireRoute: ApiArktypeToolWireRoute, ApiAudioRoute: ApiAudioRouteWithChildren, + ApiByteplusSeedance1080pWireRoute: ApiByteplusSeedance1080pWireRoute, ApiChatRoute: ApiChatRoute, ApiDevtoolsMemoryRoute: ApiDevtoolsMemoryRoute, ApiDurableDeliveryRoute: ApiDurableDeliveryRoute, diff --git a/testing/e2e/src/routes/api.byteplus-seedance-1080p-wire.ts b/testing/e2e/src/routes/api.byteplus-seedance-1080p-wire.ts new file mode 100644 index 0000000000..d0c9c22c07 --- /dev/null +++ b/testing/e2e/src/routes/api.byteplus-seedance-1080p-wire.ts @@ -0,0 +1,78 @@ +import { createFileRoute } from '@tanstack/react-router' +import { generateVideo } from '@tanstack/ai' +import { createBytePlusVideo } from '@tanstack/ai-byteplus' + +const LLMOCK_DEFAULT_BASE = process.env.LLMOCK_URL || 'http://127.0.0.1:4010' +const DUMMY_KEY = 'sk-e2e-test-dummy-key' + +/** + * Regression coverage for #1146 (Seedance 2.5 native 1080p). + * + * Before that change the BytePlus adapter's per-model table listed + * `dreamina-seedance-2-5-260628` as 480p/720p only, so `size: '16:9_1080p'` + * threw locally and never reached Ark. This route drives `generateVideo()` + * against that model with 1080p, hitting `byteplusSeedanceMount` in + * `global-setup.ts`. A wrap around `fetch` records the create-task body so + * the spec can assert `resolution: '1080p'` crossed the wire — not just that + * the call didn't throw. + */ +export const Route = createFileRoute('/api/byteplus-seedance-1080p-wire')({ + server: { + handlers: { + POST: async () => { + let captured: { resolution?: string; model?: string } | undefined + const adapter = createBytePlusVideo( + 'dreamina-seedance-2-5-260628', + DUMMY_KEY, + { + baseURL: `${LLMOCK_DEFAULT_BASE}/api/v3`, + fetch: async (input, init) => { + if (typeof init?.body === 'string') { + try { + const body = JSON.parse(init.body) as { + resolution?: string + model?: string + } + if (body.model !== undefined) captured = body + } catch { + // Non-JSON bodies (poll GETs have none) are ignored. + } + } + return fetch(input, init) + }, + }, + ) + + try { + const result = await generateVideo({ + adapter, + prompt: 'a guitar being played in a store', + size: '16:9_1080p', + }) + return new Response( + JSON.stringify({ + ok: true, + jobId: result.jobId, + model: captured?.model, + resolution: captured?.resolution, + }), + { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }, + ) + } catch (error) { + return new Response( + JSON.stringify({ + ok: false, + error: error instanceof Error ? error.message : String(error), + model: captured?.model, + resolution: captured?.resolution, + }), + { status: 200, headers: { 'Content-Type': 'application/json' } }, + ) + } + }, + }, + }, +}) diff --git a/testing/e2e/tests/byteplus-seedance-1080p-wire.spec.ts b/testing/e2e/tests/byteplus-seedance-1080p-wire.spec.ts new file mode 100644 index 0000000000..bf1210408a --- /dev/null +++ b/testing/e2e/tests/byteplus-seedance-1080p-wire.spec.ts @@ -0,0 +1,30 @@ +import { test, expect } from './fixtures' + +/** + * Regression coverage for #1146 (Seedance 2.5 native 1080p). See + * `api.byteplus-seedance-1080p-wire.ts` for the mechanism and + * `byteplusSeedanceMount` in `global-setup.ts` for the mock that accepts the + * create-task POST. + */ +test.describe('byteplus — Seedance 2.5 1080p wire (#1146)', () => { + test('dreamina-seedance-2-5-260628 accepts 16:9_1080p and sends resolution 1080p', async ({ + request, + }) => { + const res = await request.post('/api/byteplus-seedance-1080p-wire') + expect(res.ok()).toBe(true) + + const body = (await res.json()) as { + ok: boolean + error?: string + jobId?: string + model?: string + resolution?: string + } + + expect(body.error ?? null).toBeNull() + expect(body.ok).toBe(true) + expect(body.model).toBe('dreamina-seedance-2-5-260628') + expect(body.resolution).toBe('1080p') + expect(body.jobId).toEqual(expect.any(String)) + }) +})