@tanstack/ai-byteplus 0.1.2 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37,7 +37,9 @@ function describeFailures(failures) {
37
37
  *
38
38
  * BytePlus bills per generated image and does not count input tokens, so
39
39
  * `promptTokens` is always 0 and `generated_images` is surfaced as
40
- * `unitsBilled` — the count the price is applied to.
40
+ * `usage.billed` (`{ quantity, unit: 'images' }`) — the count the price is
41
+ * applied to. The deprecated `unitsBilled` is still populated for
42
+ * backward compatibility.
41
43
  */
42
44
  function buildBytePlusImageUsage(usage) {
43
45
  if (!usage) return void 0;
@@ -46,7 +48,13 @@ function buildBytePlusImageUsage(usage) {
46
48
  promptTokens: 0,
47
49
  completionTokens,
48
50
  totalTokens: usage.total_tokens ?? completionTokens,
49
- ...usage.generated_images !== void 0 && { unitsBilled: usage.generated_images }
51
+ ...usage.generated_images !== void 0 && {
52
+ billed: {
53
+ quantity: usage.generated_images,
54
+ unit: "images"
55
+ },
56
+ unitsBilled: usage.generated_images
57
+ }
50
58
  };
51
59
  }
52
60
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"image.js","names":[],"sources":["../../../src/adapters/image.ts"],"sourcesContent":["import { resolveMediaPrompt } from '@tanstack/ai'\nimport { BaseImageAdapter } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport { generateId } from '@tanstack/ai-utils'\nimport {\n bytePlusArkError,\n bytePlusArkHeaders,\n bytePlusTimeoutSignal,\n describeBody,\n getBytePlusArkApiKeyFromEnv,\n readJsonBody,\n toHeaderRecord,\n withBytePlusArkDefaults,\n} from '../utils/client'\nimport {\n resolveBytePlusImageSize,\n resolveBytePlusSequentialImages,\n validateBytePlusImagePrompt,\n validateBytePlusReferenceImages,\n} from '../image/image-provider-options'\nimport type {\n GeneratedImage,\n ImageGenerationOptions,\n ImageGenerationResult,\n ImagePart,\n MediaInputMetadata,\n TokenUsage,\n} from '@tanstack/ai'\nimport type { InternalLogger } from '@tanstack/ai/adapter-internals'\nimport type {\n BytePlusImageErrorObject,\n BytePlusImageGenerationRequest,\n BytePlusImageGenerationResponse,\n BytePlusImageUsage,\n} from '../image/wire-types'\nimport type {\n BytePlusImageModelInputModalitiesByName,\n BytePlusImageModelProviderOptionsByName,\n BytePlusImageProviderOptions,\n} from '../image/image-provider-options'\nimport type {\n BytePlusImageModel,\n BytePlusImageModelSizeByName,\n} from '../model-meta'\nimport type { BytePlusArkConfig } from '../utils/client'\n\n/**\n * Configuration for the BytePlus Seedream image adapter.\n */\nexport interface BytePlusImageConfig extends BytePlusArkConfig {}\n\n/**\n * Roles Seedream can honour. Every input image is a reference — there is no\n * inpainting mask, control-image or frame channel — so this is an allow-list\n * rather than a deny-list: a role added to the core union later (or a\n * video-oriented one like `start_frame`) fails loudly here instead of being\n * silently flattened into a plain reference.\n */\nconst SUPPORTED_INPUT_ROLES: ReadonlySet<string> = new Set([\n 'reference',\n 'character',\n])\n\n/**\n * Converts a prompt image part to the string Seedream's `image` field takes:\n * URLs pass through (BytePlus fetches them server-side), data sources become\n * data URIs. BytePlus requires the format in `data:image/<format>;base64,` to\n * be lowercase, so the mime type is lowercased on the way out.\n */\nfunction imagePartToImageRef(part: ImagePart<MediaInputMetadata>): string {\n const { source } = part\n if (source.type === 'url') return source.value\n if (source.value.startsWith('data:')) return source.value\n return `data:${source.mimeType.toLowerCase()};base64,${source.value}`\n}\n\n/**\n * Renders provider error objects as `code: message` pairs for a log line or\n * an error message.\n */\nfunction describeFailures(\n failures: ReadonlyArray<BytePlusImageErrorObject>,\n): string {\n return failures\n .map((failure) =>\n [failure.code, failure.message].filter(Boolean).join(': '),\n )\n .filter((text) => text.length > 0)\n .join('; ')\n}\n\n/**\n * Maps Seedream's usage block onto `TokenUsage`.\n *\n * BytePlus bills per generated image and does not count input tokens, so\n * `promptTokens` is always 0 and `generated_images` is surfaced as\n * `unitsBilled` — the count the price is applied to.\n */\nfunction buildBytePlusImageUsage(\n usage: BytePlusImageUsage | undefined,\n): TokenUsage | undefined {\n if (!usage) return undefined\n\n const completionTokens = usage.output_tokens ?? 0\n return {\n promptTokens: 0,\n completionTokens,\n totalTokens: usage.total_tokens ?? completionTokens,\n ...(usage.generated_images !== undefined && {\n unitsBilled: usage.generated_images,\n }),\n }\n}\n\n/**\n * BytePlus Seedream image generation adapter.\n *\n * Drives Ark's `POST /images/generations` endpoint directly rather than\n * through the OpenAI SDK: the endpoint takes size tokens (`2K`) as well as\n * pixel sizes, has no `n` parameter, and carries reference images for editing\n * in the generation request instead of a separate edits endpoint.\n *\n * Features:\n * - Text-to-image and image-conditioned generation (editing, multi-reference)\n * from a single call, with per-model reference-count limits enforced.\n * - Size validation across both accepted forms.\n * - `numberOfImages` mapped onto Seedream's group-image mode.\n *\n * @example\n * ```typescript\n * const adapter = byteplusImage('seedream-4-0-250828')\n * const result = await generateImage({\n * adapter,\n * prompt: 'A guitar in a sunlit workshop',\n * size: '2K',\n * modelOptions: { watermark: false },\n * })\n * ```\n */\nexport class BytePlusImageAdapter<\n TModel extends BytePlusImageModel,\n> extends BaseImageAdapter<\n TModel,\n BytePlusImageProviderOptions,\n BytePlusImageModelProviderOptionsByName,\n BytePlusImageModelSizeByName,\n BytePlusImageModelInputModalitiesByName\n> {\n override readonly kind = 'image' as const\n readonly name = 'byteplus' as const\n\n /** Config with the Ark base URL resolved and trailing slashes trimmed. */\n private readonly clientConfig: Omit<BytePlusImageConfig, 'baseURL'> & {\n baseURL: string\n }\n\n constructor(model: TModel, config: BytePlusImageConfig) {\n super(model, {})\n this.clientConfig = withBytePlusArkDefaults(config)\n }\n\n async generateImages(\n options: ImageGenerationOptions<\n BytePlusImageProviderOptions,\n BytePlusImageModelSizeByName[TModel]\n >,\n ): Promise<ImageGenerationResult> {\n const { numberOfImages, size, modelOptions, logger } = options\n const model = this.model\n\n const resolved = resolveMediaPrompt(options.prompt)\n\n if (resolved.videos.length > 0 || resolved.audios.length > 0) {\n throw new Error(\n `byteplus.generateImages does not support video / audio prompt parts on model ${model}.`,\n )\n }\n\n const unsupportedRole = resolved.images.find(\n (part) =>\n part.metadata?.role !== undefined &&\n !SUPPORTED_INPUT_ROLES.has(part.metadata.role),\n )\n if (unsupportedRole) {\n throw new Error(\n `byteplus: Seedream has no ${unsupportedRole.metadata?.role} input; ` +\n `it accepts reference images only (${[...SUPPORTED_INPUT_ROLES].join(', ')}).`,\n )\n }\n\n validateBytePlusImagePrompt(model, resolved.text)\n validateBytePlusReferenceImages(model, resolved.images.length)\n\n const imageRefs = resolved.images.map(imagePartToImageRef)\n const request: BytePlusImageGenerationRequest = {\n ...(imageRefs.length > 0 && { image: imageRefs }),\n ...(size !== undefined && {\n size: resolveBytePlusImageSize(size),\n }),\n ...resolveBytePlusSequentialImages(model, numberOfImages),\n // Explicit provider options win over the values derived from the\n // generic options above (e.g. forcing `sequential_image_generation`).\n ...modelOptions,\n model,\n prompt: resolved.text,\n }\n\n try {\n logger.request(\n `activity=image provider=${this.name} model=${model} size=${request.size ?? 'default'} refs=${imageRefs.length}`,\n { provider: this.name, model },\n )\n\n const fetchImpl = this.clientConfig.fetch ?? fetch\n const signal = bytePlusTimeoutSignal(this.clientConfig.timeout)\n const response = await fetchImpl(\n `${this.clientConfig.baseURL}/images/generations`,\n {\n method: 'POST',\n ...(signal && { signal }),\n headers: bytePlusArkHeaders(\n this.clientConfig.apiKey,\n toHeaderRecord(this.clientConfig.defaultHeaders),\n ),\n body: JSON.stringify(request),\n },\n )\n\n const body = await readJsonBody(response)\n if (!response.ok) {\n throw bytePlusArkError(response.status, body, 'image generation')\n }\n\n return this.transformResponse(body, logger, numberOfImages)\n } catch (error: unknown) {\n logger.errors(`${this.name}.generateImages fatal`, {\n error: toRunErrorPayload(error, `${this.name}.generateImages failed`),\n source: `${this.name}.generateImages`,\n })\n throw error\n }\n }\n\n private transformResponse(\n body: unknown,\n logger: InternalLogger,\n numberOfImages: number | undefined,\n ): ImageGenerationResult {\n // Shape pinned by a live seedream-4-0-250828 call and the Ark OpenAPI\n // document. Validate rather than cast: `readJsonBody` returns `undefined`\n // for an empty body and the raw text for a non-JSON one (an HTML error\n // page from a proxy in front of the API), and casting either would report\n // \"returned no images\" with the body — the only evidence of what actually\n // happened — thrown away.\n if (typeof body !== 'object' || body === null) {\n throw bytePlusArkError(\n 200,\n body,\n 'image generation returned a non-object body',\n )\n }\n const payload = body as BytePlusImageGenerationResponse\n\n const images: Array<GeneratedImage> = []\n const failures: Array<BytePlusImageErrorObject> = []\n // Items matching none of the three known shapes. Ark's OpenAPI document\n // describes a second, nested item form, so this is a live possibility\n // rather than a defensive branch — and an unrecognized item that is\n // neither counted nor reported turns provider drift into an\n // \"returned no images\" with no attribution at all.\n let unrecognized = 0\n for (const item of payload.data ?? []) {\n if (item.b64_json) {\n images.push({ b64Json: item.b64_json })\n } else if (item.url) {\n images.push({ url: item.url })\n } else if (item.error) {\n // Group-image mode reports per-image failures alongside successes;\n // dropping them silently would make a short result look complete.\n failures.push(item.error)\n } else {\n unrecognized += 1\n }\n }\n if (payload.error) failures.push(payload.error)\n\n if (unrecognized > 0) {\n logger.errors(\n `${this.name}.generateImages: ${unrecognized} response item(s) matched ` +\n `none of b64_json / url / error — the response shape may have changed.`,\n {\n source: `${this.name}.generateImages`,\n provider: this.name,\n model: this.model,\n body,\n },\n )\n }\n\n if (images.length === 0) {\n const detail =\n describeFailures(failures) ||\n (unrecognized > 0\n ? `${unrecognized} unrecognized response item(s): ${describeBody(body) ?? ''}`\n : '')\n throw new Error(\n `byteplus: image generation returned no images` +\n (detail ? `: ${detail}` : '.'),\n )\n }\n\n if (failures.length > 0) {\n logger.errors(\n `${this.name}.generateImages dropped ${failures.length} failed image(s): ${describeFailures(failures)}`,\n {\n source: `${this.name}.generateImages`,\n provider: this.name,\n model: this.model,\n failures,\n },\n )\n // The caller asked for a group and is getting a short array. Warn\n // unconditionally: the `numberOfImages` warning below only fires when\n // the count was set explicitly, so a partial failure would otherwise\n // return successfully with no signal at all.\n logger.warn(\n `byteplus: ${failures.length} of ${failures.length + images.length} ` +\n `images failed to generate; returning ${images.length}.`,\n { provider: this.name, model: this.model },\n )\n }\n\n if (numberOfImages !== undefined && images.length < numberOfImages) {\n logger.warn(\n `byteplus: requested ${numberOfImages} images, received ${images.length}. ` +\n `Seedream has no exact count — sequential_image_generation.max_images is ` +\n `an upper bound and the model decides how many the prompt warrants.`,\n { provider: this.name, model: this.model },\n )\n }\n\n const usage = buildBytePlusImageUsage(payload.usage)\n\n return {\n id: generateId(this.name),\n model: this.model,\n images,\n ...(usage ? { usage } : {}),\n }\n }\n}\n\n/**\n * Creates a BytePlus Seedream image adapter with an explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'seedream-4-0-250828')\n * @param apiKey - Your BytePlus Ark API key\n * @param config - Optional additional configuration\n * @returns Configured BytePlus image adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createBytePlusImage('seedream-5-0-260128', 'ark-...')\n *\n * const result = await generateImage({\n * adapter,\n * prompt: 'A cute baby sea otter',\n * size: '2K',\n * })\n * ```\n */\nexport function createBytePlusImage<TModel extends BytePlusImageModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<BytePlusImageConfig, 'apiKey'>,\n): BytePlusImageAdapter<TModel> {\n return new BytePlusImageAdapter(model, { apiKey, ...config })\n}\n\n/**\n * Creates a BytePlus Seedream image adapter, reading `ARK_API_KEY` from the\n * environment. Type resolution happens here at the call site.\n *\n * Note that Ark keys are region-isolated: a key issued for `ap-southeast`\n * does not work against the EU host.\n *\n * @param model - The model name (e.g., 'seedream-4-0-250828')\n * @param config - Optional configuration (excluding apiKey, auto-detected)\n * @returns Configured BytePlus image adapter instance with resolved types\n * @throws Error if ARK_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * const adapter = byteplusImage('seedream-4-0-250828')\n *\n * const result = await generateImage({\n * adapter,\n * prompt: 'A beautiful sunset over mountains',\n * modelOptions: { watermark: false },\n * })\n * ```\n */\nexport function byteplusImage<TModel extends BytePlusImageModel>(\n model: TModel,\n config?: Omit<BytePlusImageConfig, 'apiKey'>,\n): BytePlusImageAdapter<TModel> {\n return createBytePlusImage(model, getBytePlusArkApiKeyFromEnv(), config)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"image.js","names":[],"sources":["../../../src/adapters/image.ts"],"sourcesContent":["import { resolveMediaPrompt } from '@tanstack/ai'\nimport { BaseImageAdapter } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport { generateId } from '@tanstack/ai-utils'\nimport {\n bytePlusArkError,\n bytePlusArkHeaders,\n bytePlusTimeoutSignal,\n describeBody,\n getBytePlusArkApiKeyFromEnv,\n readJsonBody,\n toHeaderRecord,\n withBytePlusArkDefaults,\n} from '../utils/client'\nimport {\n resolveBytePlusImageSize,\n resolveBytePlusSequentialImages,\n validateBytePlusImagePrompt,\n validateBytePlusReferenceImages,\n} from '../image/image-provider-options'\nimport type {\n GeneratedImage,\n ImageGenerationOptions,\n ImageGenerationResult,\n ImagePart,\n MediaInputMetadata,\n TokenUsage,\n} from '@tanstack/ai'\nimport type { InternalLogger } from '@tanstack/ai/adapter-internals'\nimport type {\n BytePlusImageErrorObject,\n BytePlusImageGenerationRequest,\n BytePlusImageGenerationResponse,\n BytePlusImageUsage,\n} from '../image/wire-types'\nimport type {\n BytePlusImageModelInputModalitiesByName,\n BytePlusImageModelProviderOptionsByName,\n BytePlusImageProviderOptions,\n} from '../image/image-provider-options'\nimport type {\n BytePlusImageModel,\n BytePlusImageModelSizeByName,\n} from '../model-meta'\nimport type { BytePlusArkConfig } from '../utils/client'\n\n/**\n * Configuration for the BytePlus Seedream image adapter.\n */\nexport interface BytePlusImageConfig extends BytePlusArkConfig {}\n\n/**\n * Roles Seedream can honour. Every input image is a reference — there is no\n * inpainting mask, control-image or frame channel — so this is an allow-list\n * rather than a deny-list: a role added to the core union later (or a\n * video-oriented one like `start_frame`) fails loudly here instead of being\n * silently flattened into a plain reference.\n */\nconst SUPPORTED_INPUT_ROLES: ReadonlySet<string> = new Set([\n 'reference',\n 'character',\n])\n\n/**\n * Converts a prompt image part to the string Seedream's `image` field takes:\n * URLs pass through (BytePlus fetches them server-side), data sources become\n * data URIs. BytePlus requires the format in `data:image/<format>;base64,` to\n * be lowercase, so the mime type is lowercased on the way out.\n */\nfunction imagePartToImageRef(part: ImagePart<MediaInputMetadata>): string {\n const { source } = part\n if (source.type === 'url') return source.value\n if (source.value.startsWith('data:')) return source.value\n return `data:${source.mimeType.toLowerCase()};base64,${source.value}`\n}\n\n/**\n * Renders provider error objects as `code: message` pairs for a log line or\n * an error message.\n */\nfunction describeFailures(\n failures: ReadonlyArray<BytePlusImageErrorObject>,\n): string {\n return failures\n .map((failure) =>\n [failure.code, failure.message].filter(Boolean).join(': '),\n )\n .filter((text) => text.length > 0)\n .join('; ')\n}\n\n/**\n * Maps Seedream's usage block onto `TokenUsage`.\n *\n * BytePlus bills per generated image and does not count input tokens, so\n * `promptTokens` is always 0 and `generated_images` is surfaced as\n * `usage.billed` (`{ quantity, unit: 'images' }`) — the count the price is\n * applied to. The deprecated `unitsBilled` is still populated for\n * backward compatibility.\n */\nfunction buildBytePlusImageUsage(\n usage: BytePlusImageUsage | undefined,\n): TokenUsage | undefined {\n if (!usage) return undefined\n\n const completionTokens = usage.output_tokens ?? 0\n return {\n promptTokens: 0,\n completionTokens,\n totalTokens: usage.total_tokens ?? completionTokens,\n ...(usage.generated_images !== undefined && {\n billed: { quantity: usage.generated_images, unit: 'images' },\n unitsBilled: usage.generated_images,\n }),\n }\n}\n\n/**\n * BytePlus Seedream image generation adapter.\n *\n * Drives Ark's `POST /images/generations` endpoint directly rather than\n * through the OpenAI SDK: the endpoint takes size tokens (`2K`) as well as\n * pixel sizes, has no `n` parameter, and carries reference images for editing\n * in the generation request instead of a separate edits endpoint.\n *\n * Features:\n * - Text-to-image and image-conditioned generation (editing, multi-reference)\n * from a single call, with per-model reference-count limits enforced.\n * - Size validation across both accepted forms.\n * - `numberOfImages` mapped onto Seedream's group-image mode.\n *\n * @example\n * ```typescript\n * const adapter = byteplusImage('seedream-4-0-250828')\n * const result = await generateImage({\n * adapter,\n * prompt: 'A guitar in a sunlit workshop',\n * size: '2K',\n * modelOptions: { watermark: false },\n * })\n * ```\n */\nexport class BytePlusImageAdapter<\n TModel extends BytePlusImageModel,\n> extends BaseImageAdapter<\n TModel,\n BytePlusImageProviderOptions,\n BytePlusImageModelProviderOptionsByName,\n BytePlusImageModelSizeByName,\n BytePlusImageModelInputModalitiesByName\n> {\n override readonly kind = 'image' as const\n readonly name = 'byteplus' as const\n\n /** Config with the Ark base URL resolved and trailing slashes trimmed. */\n private readonly clientConfig: Omit<BytePlusImageConfig, 'baseURL'> & {\n baseURL: string\n }\n\n constructor(model: TModel, config: BytePlusImageConfig) {\n super(model, {})\n this.clientConfig = withBytePlusArkDefaults(config)\n }\n\n async generateImages(\n options: ImageGenerationOptions<\n BytePlusImageProviderOptions,\n BytePlusImageModelSizeByName[TModel]\n >,\n ): Promise<ImageGenerationResult> {\n const { numberOfImages, size, modelOptions, logger } = options\n const model = this.model\n\n const resolved = resolveMediaPrompt(options.prompt)\n\n if (resolved.videos.length > 0 || resolved.audios.length > 0) {\n throw new Error(\n `byteplus.generateImages does not support video / audio prompt parts on model ${model}.`,\n )\n }\n\n const unsupportedRole = resolved.images.find(\n (part) =>\n part.metadata?.role !== undefined &&\n !SUPPORTED_INPUT_ROLES.has(part.metadata.role),\n )\n if (unsupportedRole) {\n throw new Error(\n `byteplus: Seedream has no ${unsupportedRole.metadata?.role} input; ` +\n `it accepts reference images only (${[...SUPPORTED_INPUT_ROLES].join(', ')}).`,\n )\n }\n\n validateBytePlusImagePrompt(model, resolved.text)\n validateBytePlusReferenceImages(model, resolved.images.length)\n\n const imageRefs = resolved.images.map(imagePartToImageRef)\n const request: BytePlusImageGenerationRequest = {\n ...(imageRefs.length > 0 && { image: imageRefs }),\n ...(size !== undefined && {\n size: resolveBytePlusImageSize(size),\n }),\n ...resolveBytePlusSequentialImages(model, numberOfImages),\n // Explicit provider options win over the values derived from the\n // generic options above (e.g. forcing `sequential_image_generation`).\n ...modelOptions,\n model,\n prompt: resolved.text,\n }\n\n try {\n logger.request(\n `activity=image provider=${this.name} model=${model} size=${request.size ?? 'default'} refs=${imageRefs.length}`,\n { provider: this.name, model },\n )\n\n const fetchImpl = this.clientConfig.fetch ?? fetch\n const signal = bytePlusTimeoutSignal(this.clientConfig.timeout)\n const response = await fetchImpl(\n `${this.clientConfig.baseURL}/images/generations`,\n {\n method: 'POST',\n ...(signal && { signal }),\n headers: bytePlusArkHeaders(\n this.clientConfig.apiKey,\n toHeaderRecord(this.clientConfig.defaultHeaders),\n ),\n body: JSON.stringify(request),\n },\n )\n\n const body = await readJsonBody(response)\n if (!response.ok) {\n throw bytePlusArkError(response.status, body, 'image generation')\n }\n\n return this.transformResponse(body, logger, numberOfImages)\n } catch (error: unknown) {\n logger.errors(`${this.name}.generateImages fatal`, {\n error: toRunErrorPayload(error, `${this.name}.generateImages failed`),\n source: `${this.name}.generateImages`,\n })\n throw error\n }\n }\n\n private transformResponse(\n body: unknown,\n logger: InternalLogger,\n numberOfImages: number | undefined,\n ): ImageGenerationResult {\n // Shape pinned by a live seedream-4-0-250828 call and the Ark OpenAPI\n // document. Validate rather than cast: `readJsonBody` returns `undefined`\n // for an empty body and the raw text for a non-JSON one (an HTML error\n // page from a proxy in front of the API), and casting either would report\n // \"returned no images\" with the body — the only evidence of what actually\n // happened — thrown away.\n if (typeof body !== 'object' || body === null) {\n throw bytePlusArkError(\n 200,\n body,\n 'image generation returned a non-object body',\n )\n }\n const payload = body as BytePlusImageGenerationResponse\n\n const images: Array<GeneratedImage> = []\n const failures: Array<BytePlusImageErrorObject> = []\n // Items matching none of the three known shapes. Ark's OpenAPI document\n // describes a second, nested item form, so this is a live possibility\n // rather than a defensive branch — and an unrecognized item that is\n // neither counted nor reported turns provider drift into an\n // \"returned no images\" with no attribution at all.\n let unrecognized = 0\n for (const item of payload.data ?? []) {\n if (item.b64_json) {\n images.push({ b64Json: item.b64_json })\n } else if (item.url) {\n images.push({ url: item.url })\n } else if (item.error) {\n // Group-image mode reports per-image failures alongside successes;\n // dropping them silently would make a short result look complete.\n failures.push(item.error)\n } else {\n unrecognized += 1\n }\n }\n if (payload.error) failures.push(payload.error)\n\n if (unrecognized > 0) {\n logger.errors(\n `${this.name}.generateImages: ${unrecognized} response item(s) matched ` +\n `none of b64_json / url / error — the response shape may have changed.`,\n {\n source: `${this.name}.generateImages`,\n provider: this.name,\n model: this.model,\n body,\n },\n )\n }\n\n if (images.length === 0) {\n const detail =\n describeFailures(failures) ||\n (unrecognized > 0\n ? `${unrecognized} unrecognized response item(s): ${describeBody(body) ?? ''}`\n : '')\n throw new Error(\n `byteplus: image generation returned no images` +\n (detail ? `: ${detail}` : '.'),\n )\n }\n\n if (failures.length > 0) {\n logger.errors(\n `${this.name}.generateImages dropped ${failures.length} failed image(s): ${describeFailures(failures)}`,\n {\n source: `${this.name}.generateImages`,\n provider: this.name,\n model: this.model,\n failures,\n },\n )\n // The caller asked for a group and is getting a short array. Warn\n // unconditionally: the `numberOfImages` warning below only fires when\n // the count was set explicitly, so a partial failure would otherwise\n // return successfully with no signal at all.\n logger.warn(\n `byteplus: ${failures.length} of ${failures.length + images.length} ` +\n `images failed to generate; returning ${images.length}.`,\n { provider: this.name, model: this.model },\n )\n }\n\n if (numberOfImages !== undefined && images.length < numberOfImages) {\n logger.warn(\n `byteplus: requested ${numberOfImages} images, received ${images.length}. ` +\n `Seedream has no exact count — sequential_image_generation.max_images is ` +\n `an upper bound and the model decides how many the prompt warrants.`,\n { provider: this.name, model: this.model },\n )\n }\n\n const usage = buildBytePlusImageUsage(payload.usage)\n\n return {\n id: generateId(this.name),\n model: this.model,\n images,\n ...(usage ? { usage } : {}),\n }\n }\n}\n\n/**\n * Creates a BytePlus Seedream image adapter with an explicit API key.\n * Type resolution happens here at the call site.\n *\n * @param model - The model name (e.g., 'seedream-4-0-250828')\n * @param apiKey - Your BytePlus Ark API key\n * @param config - Optional additional configuration\n * @returns Configured BytePlus image adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createBytePlusImage('seedream-5-0-260128', 'ark-...')\n *\n * const result = await generateImage({\n * adapter,\n * prompt: 'A cute baby sea otter',\n * size: '2K',\n * })\n * ```\n */\nexport function createBytePlusImage<TModel extends BytePlusImageModel>(\n model: TModel,\n apiKey: string,\n config?: Omit<BytePlusImageConfig, 'apiKey'>,\n): BytePlusImageAdapter<TModel> {\n return new BytePlusImageAdapter(model, { apiKey, ...config })\n}\n\n/**\n * Creates a BytePlus Seedream image adapter, reading `ARK_API_KEY` from the\n * environment. Type resolution happens here at the call site.\n *\n * Note that Ark keys are region-isolated: a key issued for `ap-southeast`\n * does not work against the EU host.\n *\n * @param model - The model name (e.g., 'seedream-4-0-250828')\n * @param config - Optional configuration (excluding apiKey, auto-detected)\n * @returns Configured BytePlus image adapter instance with resolved types\n * @throws Error if ARK_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * const adapter = byteplusImage('seedream-4-0-250828')\n *\n * const result = await generateImage({\n * adapter,\n * prompt: 'A beautiful sunset over mountains',\n * modelOptions: { watermark: false },\n * })\n * ```\n */\nexport function byteplusImage<TModel extends BytePlusImageModel>(\n model: TModel,\n config?: Omit<BytePlusImageConfig, 'apiKey'>,\n): BytePlusImageAdapter<TModel> {\n return createBytePlusImage(model, getBytePlusArkApiKeyFromEnv(), config)\n}\n"],"mappings":"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"}
@@ -162,6 +162,10 @@ function mapRecognizeResponse(data, text, logger) {
162
162
  promptTokens: 0,
163
163
  completionTokens: 0,
164
164
  totalTokens: 0,
165
+ billed: {
166
+ quantity: duration,
167
+ unit: "seconds"
168
+ },
165
169
  durationSeconds: duration
166
170
  } : void 0;
167
171
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"transcription.js","names":[],"sources":["../../../src/adapters/transcription.ts"],"sourcesContent":["import { BaseTranscriptionAdapter } from '@tanstack/ai/adapters'\nimport { arrayBufferToBase64, generateId } from '@tanstack/ai-utils'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport {\n BYTEPLUS_VOICE_BASE_URL,\n bytePlusVoiceError,\n bytePlusVoiceHeaders,\n getBytePlusVoiceApiKeyFromEnv,\n readJsonBody,\n withBytePlusVoiceDefaults,\n} from '../utils/client'\nimport {\n BYTEPLUS_ASR_RESOURCE_HEADER,\n BYTEPLUS_ASR_RESOURCE_ID,\n} from '../audio/wire-types'\nimport type {\n TokenUsage,\n TranscriptionOptions,\n TranscriptionResult,\n TranscriptionSegment,\n TranscriptionWord,\n} from '@tanstack/ai'\nimport type { InternalLogger } from '@tanstack/ai/adapter-internals'\nimport type { BytePlusVoiceConfig } from '../utils/client'\nimport type { BytePlusTranscriptionModel } from '../model-meta'\nimport type {\n BytePlusASRAudio,\n BytePlusASRRecognizeRequest,\n BytePlusASRRecognizeResponse,\n BytePlusASRUtterance,\n} from '../audio/wire-types'\nimport type { BytePlusTranscriptionProviderOptions } from '../audio/transcription-provider-options'\n\n/** Path of the synchronous (\"flash\") Seed ASR endpoint. */\nconst RECOGNIZE_FLASH_PATH = '/api/v3/auc/bigmodel/recognize/flash'\n\n/**\n * BytePlus-specific extension of `TranscriptionWord` carrying the per-word\n * confidence Seed ASR returns. The cross-provider contract has no field for\n * it, so callers who want it narrow the array — the same pattern the Grok\n * adapter uses:\n *\n * ```ts\n * const words = result.words as Array<BytePlusTranscriptionWord> | undefined\n * ```\n */\nexport interface BytePlusTranscriptionWord extends TranscriptionWord {\n /** Model confidence for the word, when Seed ASR returns one. */\n confidence?: number\n}\n\n/** Default `user.uid` echoed into BytePlus' request logs. */\nconst DEFAULT_UID = 'tanstack-ai'\n\n/**\n * BytePlus Seed Speech transcription (ASR) adapter.\n *\n * Talks to `POST {baseURL}/api/v3/auc/bigmodel/recognize/flash` — the\n * synchronous \"flash\" endpoint, which returns the whole transcript in one\n * response rather than requiring a submit/poll cycle. It accepts audio up to\n * 2 hours long or 100 MB, either as a publicly reachable URL or as base64\n * bytes.\n *\n * Two BytePlus-specific details:\n *\n * - The model is selected by the `X-Api-Resource-Id` header\n * (`volc.seedasr.auc_turbo`), not by a `model` field in the body. The\n * package's `seed-asr` model id exists to satisfy the SDK contract and to\n * give logs a stable value.\n * - Authentication uses `X-Api-Key` with the **Seed Speech** key, which is a\n * different key from `ARK_API_KEY`.\n *\n * All timings on the wire are milliseconds; they are converted to seconds to\n * match the cross-provider `TranscriptionResult`.\n *\n * @example\n * ```ts\n * const adapter = byteplusTranscription('seed-asr')\n * const result = await generateTranscription({\n * adapter,\n * audio: 'https://example.com/interview.mp3',\n * language: 'en-US',\n * })\n * ```\n */\nexport class BytePlusTranscriptionAdapter<\n TModel extends BytePlusTranscriptionModel = BytePlusTranscriptionModel,\n> extends BaseTranscriptionAdapter<\n TModel,\n BytePlusTranscriptionProviderOptions\n> {\n readonly name = 'byteplus' as const\n\n private readonly apiKey: string\n private readonly baseURL: string\n private readonly defaultHeaders: Record<string, string>\n private readonly fetchImpl: typeof fetch\n\n constructor(model: TModel, config: BytePlusVoiceConfig) {\n super(model, config)\n const resolved = withBytePlusVoiceDefaults(config)\n this.apiKey = resolved.apiKey\n this.baseURL = resolved.baseURL ?? BYTEPLUS_VOICE_BASE_URL\n this.defaultHeaders = resolved.defaultHeaders ?? {}\n this.fetchImpl = resolved.fetch ?? globalThis.fetch.bind(globalThis)\n }\n\n async transcribe(\n options: TranscriptionOptions<BytePlusTranscriptionProviderOptions>,\n ): Promise<TranscriptionResult> {\n const {\n logger,\n model,\n audio,\n language,\n prompt,\n responseFormat,\n modelOptions,\n } = options\n\n logger.request(\n `activity=generateTranscription provider=byteplus model=${model}`,\n { provider: 'byteplus', model },\n )\n\n if (prompt) {\n logger.warn(\n 'BytePlus Seed ASR has no prompt-biasing field on the flash endpoint — the `prompt` option is ignored.',\n { provider: 'byteplus', model },\n )\n }\n\n // The flash endpoint answers with one JSON shape and offers no format\n // negotiation, so srt/vtt/text/verbose_json can't be honoured. `segments`\n // on the result carry the timings a caller would have wanted from srt/vtt.\n if (responseFormat !== undefined && responseFormat !== 'json') {\n logger.warn(\n `BytePlus Seed ASR always returns JSON — the requested responseFormat \"${responseFormat}\" is ignored. Build srt/vtt from result.segments if you need them.`,\n { provider: 'byteplus', model, responseFormat },\n )\n }\n\n try {\n const audioPayload = await normalizeAudioInput(\n audio,\n modelOptions?.audio_format,\n )\n const body = buildRecognizeRequestBody({\n audio: audioPayload,\n language,\n modelOptions,\n })\n\n const response = await this.fetchImpl(\n `${this.baseURL}${RECOGNIZE_FLASH_PATH}`,\n {\n method: 'POST',\n headers: bytePlusVoiceHeaders(this.apiKey, {\n ...this.defaultHeaders,\n [BYTEPLUS_ASR_RESOURCE_HEADER]: BYTEPLUS_ASR_RESOURCE_ID,\n }),\n body: JSON.stringify(body),\n },\n )\n\n const payload = await readJsonBody(response)\n\n if (!response.ok) {\n throw bytePlusVoiceError(response.status, payload, 'transcription')\n }\n\n const data = payload as BytePlusASRRecognizeResponse\n const text = data.result?.text ?? data.transcript\n\n // The flash endpoint can answer HTTP 200 while carrying the numeric\n // error envelope, so an absent transcript is a failure rather than an\n // empty result.\n if (typeof text !== 'string') {\n throw bytePlusVoiceError(response.status, payload, 'transcription')\n }\n\n // An empty string is well-formed, so it isn't an error — silence is a\n // legitimate transcription. But it is also what a 200-wrapped failure\n // looks like, so say so rather than handing back a successful, empty\n // result with no signal.\n if (text === '' && !hasUtterances(data)) {\n logger.warn(\n `byteplus: transcription returned an empty transcript with no ` +\n `utterances. This is a valid result for silent audio, and is also ` +\n `what a 200-wrapped failure looks like.`,\n { provider: this.name, model },\n )\n }\n\n // Seed ASR doesn't echo the language back, so report the one that was\n // actually sent — which is `modelOptions.language` when it overrode the\n // cross-provider hint.\n const requestedLanguage = modelOptions?.language ?? language\n\n return {\n id: generateId(this.name),\n model,\n ...mapRecognizeResponse(data, text, logger),\n ...(requestedLanguage !== undefined && { language: requestedLanguage }),\n }\n } catch (error) {\n logger.errors('byteplus.transcribe fatal', {\n error: toRunErrorPayload(error, 'byteplus.transcribe failed'),\n source: 'byteplus.transcribe',\n })\n throw error\n }\n }\n}\n\n/**\n * Build the JSON body for `POST /api/v3/auc/bigmodel/recognize/flash`.\n *\n * `show_utterances` defaults to `true` so the response carries the\n * per-utterance breakdown that populates `segments` and `words`.\n */\nexport function buildRecognizeRequestBody(options: {\n audio: BytePlusASRAudio\n language: string | undefined\n modelOptions: BytePlusTranscriptionProviderOptions | undefined\n}): BytePlusASRRecognizeRequest {\n const { audio, language, modelOptions } = options\n\n const resolvedLanguage = modelOptions?.language ?? language\n\n return {\n user: { uid: modelOptions?.uid ?? DEFAULT_UID },\n audio,\n request: {\n model_name: modelOptions?.model_name ?? 'bigmodel',\n show_utterances: modelOptions?.show_utterances ?? true,\n ...(modelOptions?.enable_itn !== undefined && {\n enable_itn: modelOptions.enable_itn,\n }),\n ...(modelOptions?.enable_punc !== undefined && {\n enable_punc: modelOptions.enable_punc,\n }),\n ...(modelOptions?.enable_ddc !== undefined && {\n enable_ddc: modelOptions.enable_ddc,\n }),\n ...(modelOptions?.enable_speaker_info !== undefined && {\n enable_speaker_info: modelOptions.enable_speaker_info,\n }),\n ...(resolvedLanguage !== undefined && { language: resolvedLanguage }),\n },\n }\n}\n\n/**\n * Turn a recognition response into the transcript-shaped half of a\n * `TranscriptionResult`. Wire timings are milliseconds; everything returned\n * here is seconds.\n */\nexport function mapRecognizeResponse(\n data: BytePlusASRRecognizeResponse,\n text: string,\n logger?: InternalLogger,\n): Omit<TranscriptionResult, 'id' | 'model'> {\n const utterances = data.result?.utterances ?? data.utterances ?? []\n // `id` numbers the segments we emit, not the utterances we were given, so\n // dropping an untimed utterance doesn't leave a hole in the sequence.\n const segments = utterances\n .flatMap((utterance) => toSegment(utterance))\n .map((segment, index) => ({ ...segment, id: index }))\n\n const rawWords = utterances.flatMap((utterance) => utterance.words ?? [])\n const words = rawWords.flatMap((word) => {\n if (\n typeof word.text !== 'string' ||\n typeof word.start_time !== 'number' ||\n typeof word.end_time !== 'number'\n ) {\n return []\n }\n const mapped: BytePlusTranscriptionWord = {\n word: word.text,\n start: msToSeconds(word.start_time),\n end: msToSeconds(word.end_time),\n }\n if (word.confidence !== undefined) mapped.confidence = word.confidence\n return [mapped]\n })\n\n // Untimed entries are dropped rather than emitted with NaN timings, but a\n // silent drop leaves the caller unable to tell \"the provider sent no\n // timings\" from \"the adapter discarded them\" — the two have very different\n // fixes, and a field rename upstream (e.g. `text` → `word`) would empty\n // these arrays without a single error anywhere.\n const droppedWords = rawWords.length - words.length\n if (droppedWords > 0) {\n logger?.warn(\n `byteplus: dropped ${droppedWords} of ${rawWords.length} word(s) with ` +\n `missing or non-numeric timings.`,\n { provider: 'byteplus' },\n )\n }\n const droppedSegments = utterances.length - segments.length\n if (droppedSegments > 0) {\n logger?.warn(\n `byteplus: dropped ${droppedSegments} of ${utterances.length} ` +\n `utterance(s) with missing or non-numeric timings.`,\n { provider: 'byteplus' },\n )\n }\n\n const durationMs = data.audio_info?.duration\n const duration =\n typeof durationMs === 'number' && durationMs > 0\n ? msToSeconds(durationMs)\n : undefined\n\n // Seed ASR is duration-billed and reports no token counts, so `usage`\n // carries only the audio length — the same shape the Grok and OpenAI\n // whisper paths use.\n const usage: TokenUsage | undefined =\n duration !== undefined\n ? {\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n durationSeconds: duration,\n }\n : undefined\n\n return {\n text,\n ...(duration !== undefined && { duration }),\n ...(segments.length > 0 && { segments }),\n ...(words.length > 0 && { words }),\n ...(usage !== undefined && { usage }),\n }\n}\n\n/**\n * Convert one utterance into a segment, or nothing when it carries no\n * timings. The `id` is a placeholder — the caller renumbers after filtering.\n */\n/**\n * True when the response carries at least one utterance, in either envelope\n * form. Used to tell \"silent audio\" from a 200-wrapped failure: a genuinely\n * empty transcript usually still arrives with no utterances, so the pairing is\n * a hint rather than proof — hence a warning rather than a throw.\n */\nfunction hasUtterances(data: BytePlusASRRecognizeResponse): boolean {\n return (data.result?.utterances ?? data.utterances ?? []).length > 0\n}\n\nfunction toSegment(\n utterance: BytePlusASRUtterance,\n): Array<TranscriptionSegment> {\n if (\n typeof utterance.start_time !== 'number' ||\n typeof utterance.end_time !== 'number'\n ) {\n return []\n }\n const speaker = utterance.additions?.speaker\n return [\n {\n id: 0,\n start: msToSeconds(utterance.start_time),\n end: msToSeconds(utterance.end_time),\n text: utterance.text ?? '',\n ...(speaker !== undefined && { speaker }),\n },\n ]\n}\n\n/**\n * **Must verify when the Seed Speech key lands.** Every timing this adapter\n * reads — `audio_info.duration`, and each utterance's and word's\n * `start_time` / `end_time` — is assumed to be milliseconds. That comes from\n * the Volcengine flash-recognition reference this endpoint derives from\n * (a 2.499 s clip reports `duration: 2499`), not from a BytePlus response we\n * have seen. If BytePlus reports seconds instead, every duration, segment and\n * word timing here is 1000× too small, and this is the only place to fix.\n */\nfunction msToSeconds(milliseconds: number): number {\n return milliseconds / 1000\n}\n\n/**\n * Turn the cross-provider `audio` input into the endpoint's `audio` block.\n *\n * URLs are passed through untouched — Seed ASR fetches them itself, which\n * avoids pulling large media through this process. Everything else is sent as\n * base64 `data`, with the container inferred from the input's MIME type or\n * filename when the caller didn't pin `audio_format`.\n */\nexport async function normalizeAudioInput(\n audio: TranscriptionOptions['audio'],\n formatHint: string | undefined,\n): Promise<BytePlusASRAudio> {\n const withFormat = (\n payload: BytePlusASRAudio,\n inferred?: string,\n ): BytePlusASRAudio => {\n const format = formatHint ?? inferred\n return format ? { ...payload, format } : payload\n }\n\n if (typeof audio === 'string') {\n if (/^https?:\\/\\//i.test(audio)) {\n return withFormat({ url: audio }, extensionOf(audio))\n }\n const dataUrl = /^data:([^;,]+)?(?:;[^,]*)*,(.*)$/s.exec(audio)\n if (dataUrl) {\n return withFormat({ data: dataUrl[2] ?? '' }, formatFromMime(dataUrl[1]))\n }\n // A bare string that is neither a URL nor a data URL is already base64.\n return withFormat({ data: audio })\n }\n\n if (audio instanceof ArrayBuffer) {\n return withFormat({ data: arrayBufferToBase64(audio) })\n }\n\n const data = arrayBufferToBase64(await audio.arrayBuffer())\n const inferred =\n ('name' in audio && typeof audio.name === 'string'\n ? extensionOf(audio.name)\n : undefined) ?? formatFromMime(audio.type)\n return withFormat({ data }, inferred)\n}\n\nfunction extensionOf(pathOrName: string): string | undefined {\n const withoutQuery = pathOrName.split(/[?#]/)[0] ?? ''\n const match = /\\.([a-z0-9]+)$/i.exec(withoutQuery)\n return match?.[1]?.toLowerCase()\n}\n\nfunction formatFromMime(mime: string | undefined): string | undefined {\n if (!mime || !mime.startsWith('audio/')) return undefined\n const subtype = mime.slice('audio/'.length).toLowerCase()\n if (subtype === 'mpeg') return 'mp3'\n if (subtype === 'x-wav' || subtype === 'wave') return 'wav'\n return subtype.replace(/^x-/, '')\n}\n\n/**\n * Creates a BytePlus Seed Speech transcription adapter with an explicit API\n * key.\n *\n * The key is the **Seed Speech** key, not the Ark key used by the chat, image\n * and video adapters.\n */\nexport function createBytePlusTranscription<\n TModel extends BytePlusTranscriptionModel = BytePlusTranscriptionModel,\n>(\n model: TModel,\n apiKey: string,\n config?: Omit<BytePlusVoiceConfig, 'apiKey'>,\n): BytePlusTranscriptionAdapter<TModel> {\n return new BytePlusTranscriptionAdapter(model, { ...config, apiKey })\n}\n\n/**\n * Creates a BytePlus Seed Speech transcription adapter, reading the API key\n * from `BYTEPLUS_VOICE_API_KEY`.\n *\n * @throws Error if `BYTEPLUS_VOICE_API_KEY` is not set.\n */\nexport function byteplusTranscription<\n TModel extends BytePlusTranscriptionModel = BytePlusTranscriptionModel,\n>(\n model: TModel,\n config?: Omit<BytePlusVoiceConfig, 'apiKey'>,\n): BytePlusTranscriptionAdapter<TModel> {\n return createBytePlusTranscription(\n model,\n getBytePlusVoiceApiKeyFromEnv(),\n config,\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"transcription.js","names":[],"sources":["../../../src/adapters/transcription.ts"],"sourcesContent":["import { BaseTranscriptionAdapter } from '@tanstack/ai/adapters'\nimport { arrayBufferToBase64, generateId } from '@tanstack/ai-utils'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport {\n BYTEPLUS_VOICE_BASE_URL,\n bytePlusVoiceError,\n bytePlusVoiceHeaders,\n getBytePlusVoiceApiKeyFromEnv,\n readJsonBody,\n withBytePlusVoiceDefaults,\n} from '../utils/client'\nimport {\n BYTEPLUS_ASR_RESOURCE_HEADER,\n BYTEPLUS_ASR_RESOURCE_ID,\n} from '../audio/wire-types'\nimport type {\n TokenUsage,\n TranscriptionOptions,\n TranscriptionResult,\n TranscriptionSegment,\n TranscriptionWord,\n} from '@tanstack/ai'\nimport type { InternalLogger } from '@tanstack/ai/adapter-internals'\nimport type { BytePlusVoiceConfig } from '../utils/client'\nimport type { BytePlusTranscriptionModel } from '../model-meta'\nimport type {\n BytePlusASRAudio,\n BytePlusASRRecognizeRequest,\n BytePlusASRRecognizeResponse,\n BytePlusASRUtterance,\n} from '../audio/wire-types'\nimport type { BytePlusTranscriptionProviderOptions } from '../audio/transcription-provider-options'\n\n/** Path of the synchronous (\"flash\") Seed ASR endpoint. */\nconst RECOGNIZE_FLASH_PATH = '/api/v3/auc/bigmodel/recognize/flash'\n\n/**\n * BytePlus-specific extension of `TranscriptionWord` carrying the per-word\n * confidence Seed ASR returns. The cross-provider contract has no field for\n * it, so callers who want it narrow the array — the same pattern the Grok\n * adapter uses:\n *\n * ```ts\n * const words = result.words as Array<BytePlusTranscriptionWord> | undefined\n * ```\n */\nexport interface BytePlusTranscriptionWord extends TranscriptionWord {\n /** Model confidence for the word, when Seed ASR returns one. */\n confidence?: number\n}\n\n/** Default `user.uid` echoed into BytePlus' request logs. */\nconst DEFAULT_UID = 'tanstack-ai'\n\n/**\n * BytePlus Seed Speech transcription (ASR) adapter.\n *\n * Talks to `POST {baseURL}/api/v3/auc/bigmodel/recognize/flash` — the\n * synchronous \"flash\" endpoint, which returns the whole transcript in one\n * response rather than requiring a submit/poll cycle. It accepts audio up to\n * 2 hours long or 100 MB, either as a publicly reachable URL or as base64\n * bytes.\n *\n * Two BytePlus-specific details:\n *\n * - The model is selected by the `X-Api-Resource-Id` header\n * (`volc.seedasr.auc_turbo`), not by a `model` field in the body. The\n * package's `seed-asr` model id exists to satisfy the SDK contract and to\n * give logs a stable value.\n * - Authentication uses `X-Api-Key` with the **Seed Speech** key, which is a\n * different key from `ARK_API_KEY`.\n *\n * All timings on the wire are milliseconds; they are converted to seconds to\n * match the cross-provider `TranscriptionResult`.\n *\n * @example\n * ```ts\n * const adapter = byteplusTranscription('seed-asr')\n * const result = await generateTranscription({\n * adapter,\n * audio: 'https://example.com/interview.mp3',\n * language: 'en-US',\n * })\n * ```\n */\nexport class BytePlusTranscriptionAdapter<\n TModel extends BytePlusTranscriptionModel = BytePlusTranscriptionModel,\n> extends BaseTranscriptionAdapter<\n TModel,\n BytePlusTranscriptionProviderOptions\n> {\n readonly name = 'byteplus' as const\n\n private readonly apiKey: string\n private readonly baseURL: string\n private readonly defaultHeaders: Record<string, string>\n private readonly fetchImpl: typeof fetch\n\n constructor(model: TModel, config: BytePlusVoiceConfig) {\n super(model, config)\n const resolved = withBytePlusVoiceDefaults(config)\n this.apiKey = resolved.apiKey\n this.baseURL = resolved.baseURL ?? BYTEPLUS_VOICE_BASE_URL\n this.defaultHeaders = resolved.defaultHeaders ?? {}\n this.fetchImpl = resolved.fetch ?? globalThis.fetch.bind(globalThis)\n }\n\n async transcribe(\n options: TranscriptionOptions<BytePlusTranscriptionProviderOptions>,\n ): Promise<TranscriptionResult> {\n const {\n logger,\n model,\n audio,\n language,\n prompt,\n responseFormat,\n modelOptions,\n } = options\n\n logger.request(\n `activity=generateTranscription provider=byteplus model=${model}`,\n { provider: 'byteplus', model },\n )\n\n if (prompt) {\n logger.warn(\n 'BytePlus Seed ASR has no prompt-biasing field on the flash endpoint — the `prompt` option is ignored.',\n { provider: 'byteplus', model },\n )\n }\n\n // The flash endpoint answers with one JSON shape and offers no format\n // negotiation, so srt/vtt/text/verbose_json can't be honoured. `segments`\n // on the result carry the timings a caller would have wanted from srt/vtt.\n if (responseFormat !== undefined && responseFormat !== 'json') {\n logger.warn(\n `BytePlus Seed ASR always returns JSON — the requested responseFormat \"${responseFormat}\" is ignored. Build srt/vtt from result.segments if you need them.`,\n { provider: 'byteplus', model, responseFormat },\n )\n }\n\n try {\n const audioPayload = await normalizeAudioInput(\n audio,\n modelOptions?.audio_format,\n )\n const body = buildRecognizeRequestBody({\n audio: audioPayload,\n language,\n modelOptions,\n })\n\n const response = await this.fetchImpl(\n `${this.baseURL}${RECOGNIZE_FLASH_PATH}`,\n {\n method: 'POST',\n headers: bytePlusVoiceHeaders(this.apiKey, {\n ...this.defaultHeaders,\n [BYTEPLUS_ASR_RESOURCE_HEADER]: BYTEPLUS_ASR_RESOURCE_ID,\n }),\n body: JSON.stringify(body),\n },\n )\n\n const payload = await readJsonBody(response)\n\n if (!response.ok) {\n throw bytePlusVoiceError(response.status, payload, 'transcription')\n }\n\n const data = payload as BytePlusASRRecognizeResponse\n const text = data.result?.text ?? data.transcript\n\n // The flash endpoint can answer HTTP 200 while carrying the numeric\n // error envelope, so an absent transcript is a failure rather than an\n // empty result.\n if (typeof text !== 'string') {\n throw bytePlusVoiceError(response.status, payload, 'transcription')\n }\n\n // An empty string is well-formed, so it isn't an error — silence is a\n // legitimate transcription. But it is also what a 200-wrapped failure\n // looks like, so say so rather than handing back a successful, empty\n // result with no signal.\n if (text === '' && !hasUtterances(data)) {\n logger.warn(\n `byteplus: transcription returned an empty transcript with no ` +\n `utterances. This is a valid result for silent audio, and is also ` +\n `what a 200-wrapped failure looks like.`,\n { provider: this.name, model },\n )\n }\n\n // Seed ASR doesn't echo the language back, so report the one that was\n // actually sent — which is `modelOptions.language` when it overrode the\n // cross-provider hint.\n const requestedLanguage = modelOptions?.language ?? language\n\n return {\n id: generateId(this.name),\n model,\n ...mapRecognizeResponse(data, text, logger),\n ...(requestedLanguage !== undefined && { language: requestedLanguage }),\n }\n } catch (error) {\n logger.errors('byteplus.transcribe fatal', {\n error: toRunErrorPayload(error, 'byteplus.transcribe failed'),\n source: 'byteplus.transcribe',\n })\n throw error\n }\n }\n}\n\n/**\n * Build the JSON body for `POST /api/v3/auc/bigmodel/recognize/flash`.\n *\n * `show_utterances` defaults to `true` so the response carries the\n * per-utterance breakdown that populates `segments` and `words`.\n */\nexport function buildRecognizeRequestBody(options: {\n audio: BytePlusASRAudio\n language: string | undefined\n modelOptions: BytePlusTranscriptionProviderOptions | undefined\n}): BytePlusASRRecognizeRequest {\n const { audio, language, modelOptions } = options\n\n const resolvedLanguage = modelOptions?.language ?? language\n\n return {\n user: { uid: modelOptions?.uid ?? DEFAULT_UID },\n audio,\n request: {\n model_name: modelOptions?.model_name ?? 'bigmodel',\n show_utterances: modelOptions?.show_utterances ?? true,\n ...(modelOptions?.enable_itn !== undefined && {\n enable_itn: modelOptions.enable_itn,\n }),\n ...(modelOptions?.enable_punc !== undefined && {\n enable_punc: modelOptions.enable_punc,\n }),\n ...(modelOptions?.enable_ddc !== undefined && {\n enable_ddc: modelOptions.enable_ddc,\n }),\n ...(modelOptions?.enable_speaker_info !== undefined && {\n enable_speaker_info: modelOptions.enable_speaker_info,\n }),\n ...(resolvedLanguage !== undefined && { language: resolvedLanguage }),\n },\n }\n}\n\n/**\n * Turn a recognition response into the transcript-shaped half of a\n * `TranscriptionResult`. Wire timings are milliseconds; everything returned\n * here is seconds.\n */\nexport function mapRecognizeResponse(\n data: BytePlusASRRecognizeResponse,\n text: string,\n logger?: InternalLogger,\n): Omit<TranscriptionResult, 'id' | 'model'> {\n const utterances = data.result?.utterances ?? data.utterances ?? []\n // `id` numbers the segments we emit, not the utterances we were given, so\n // dropping an untimed utterance doesn't leave a hole in the sequence.\n const segments = utterances\n .flatMap((utterance) => toSegment(utterance))\n .map((segment, index) => ({ ...segment, id: index }))\n\n const rawWords = utterances.flatMap((utterance) => utterance.words ?? [])\n const words = rawWords.flatMap((word) => {\n if (\n typeof word.text !== 'string' ||\n typeof word.start_time !== 'number' ||\n typeof word.end_time !== 'number'\n ) {\n return []\n }\n const mapped: BytePlusTranscriptionWord = {\n word: word.text,\n start: msToSeconds(word.start_time),\n end: msToSeconds(word.end_time),\n }\n if (word.confidence !== undefined) mapped.confidence = word.confidence\n return [mapped]\n })\n\n // Untimed entries are dropped rather than emitted with NaN timings, but a\n // silent drop leaves the caller unable to tell \"the provider sent no\n // timings\" from \"the adapter discarded them\" — the two have very different\n // fixes, and a field rename upstream (e.g. `text` → `word`) would empty\n // these arrays without a single error anywhere.\n const droppedWords = rawWords.length - words.length\n if (droppedWords > 0) {\n logger?.warn(\n `byteplus: dropped ${droppedWords} of ${rawWords.length} word(s) with ` +\n `missing or non-numeric timings.`,\n { provider: 'byteplus' },\n )\n }\n const droppedSegments = utterances.length - segments.length\n if (droppedSegments > 0) {\n logger?.warn(\n `byteplus: dropped ${droppedSegments} of ${utterances.length} ` +\n `utterance(s) with missing or non-numeric timings.`,\n { provider: 'byteplus' },\n )\n }\n\n const durationMs = data.audio_info?.duration\n const duration =\n typeof durationMs === 'number' && durationMs > 0\n ? msToSeconds(durationMs)\n : undefined\n\n // Seed ASR is duration-billed and reports no token counts, so `usage`\n // carries only the audio length — the same shape the Grok and OpenAI\n // whisper paths use. `durationSeconds` is deprecated but still populated\n // alongside the self-describing `billed` pair.\n const usage: TokenUsage | undefined =\n duration !== undefined\n ? {\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n billed: { quantity: duration, unit: 'seconds' },\n durationSeconds: duration,\n }\n : undefined\n\n return {\n text,\n ...(duration !== undefined && { duration }),\n ...(segments.length > 0 && { segments }),\n ...(words.length > 0 && { words }),\n ...(usage !== undefined && { usage }),\n }\n}\n\n/**\n * Convert one utterance into a segment, or nothing when it carries no\n * timings. The `id` is a placeholder — the caller renumbers after filtering.\n */\n/**\n * True when the response carries at least one utterance, in either envelope\n * form. Used to tell \"silent audio\" from a 200-wrapped failure: a genuinely\n * empty transcript usually still arrives with no utterances, so the pairing is\n * a hint rather than proof — hence a warning rather than a throw.\n */\nfunction hasUtterances(data: BytePlusASRRecognizeResponse): boolean {\n return (data.result?.utterances ?? data.utterances ?? []).length > 0\n}\n\nfunction toSegment(\n utterance: BytePlusASRUtterance,\n): Array<TranscriptionSegment> {\n if (\n typeof utterance.start_time !== 'number' ||\n typeof utterance.end_time !== 'number'\n ) {\n return []\n }\n const speaker = utterance.additions?.speaker\n return [\n {\n id: 0,\n start: msToSeconds(utterance.start_time),\n end: msToSeconds(utterance.end_time),\n text: utterance.text ?? '',\n ...(speaker !== undefined && { speaker }),\n },\n ]\n}\n\n/**\n * **Must verify when the Seed Speech key lands.** Every timing this adapter\n * reads — `audio_info.duration`, and each utterance's and word's\n * `start_time` / `end_time` — is assumed to be milliseconds. That comes from\n * the Volcengine flash-recognition reference this endpoint derives from\n * (a 2.499 s clip reports `duration: 2499`), not from a BytePlus response we\n * have seen. If BytePlus reports seconds instead, every duration, segment and\n * word timing here is 1000× too small, and this is the only place to fix.\n */\nfunction msToSeconds(milliseconds: number): number {\n return milliseconds / 1000\n}\n\n/**\n * Turn the cross-provider `audio` input into the endpoint's `audio` block.\n *\n * URLs are passed through untouched — Seed ASR fetches them itself, which\n * avoids pulling large media through this process. Everything else is sent as\n * base64 `data`, with the container inferred from the input's MIME type or\n * filename when the caller didn't pin `audio_format`.\n */\nexport async function normalizeAudioInput(\n audio: TranscriptionOptions['audio'],\n formatHint: string | undefined,\n): Promise<BytePlusASRAudio> {\n const withFormat = (\n payload: BytePlusASRAudio,\n inferred?: string,\n ): BytePlusASRAudio => {\n const format = formatHint ?? inferred\n return format ? { ...payload, format } : payload\n }\n\n if (typeof audio === 'string') {\n if (/^https?:\\/\\//i.test(audio)) {\n return withFormat({ url: audio }, extensionOf(audio))\n }\n const dataUrl = /^data:([^;,]+)?(?:;[^,]*)*,(.*)$/s.exec(audio)\n if (dataUrl) {\n return withFormat({ data: dataUrl[2] ?? '' }, formatFromMime(dataUrl[1]))\n }\n // A bare string that is neither a URL nor a data URL is already base64.\n return withFormat({ data: audio })\n }\n\n if (audio instanceof ArrayBuffer) {\n return withFormat({ data: arrayBufferToBase64(audio) })\n }\n\n const data = arrayBufferToBase64(await audio.arrayBuffer())\n const inferred =\n ('name' in audio && typeof audio.name === 'string'\n ? extensionOf(audio.name)\n : undefined) ?? formatFromMime(audio.type)\n return withFormat({ data }, inferred)\n}\n\nfunction extensionOf(pathOrName: string): string | undefined {\n const withoutQuery = pathOrName.split(/[?#]/)[0] ?? ''\n const match = /\\.([a-z0-9]+)$/i.exec(withoutQuery)\n return match?.[1]?.toLowerCase()\n}\n\nfunction formatFromMime(mime: string | undefined): string | undefined {\n if (!mime || !mime.startsWith('audio/')) return undefined\n const subtype = mime.slice('audio/'.length).toLowerCase()\n if (subtype === 'mpeg') return 'mp3'\n if (subtype === 'x-wav' || subtype === 'wave') return 'wav'\n return subtype.replace(/^x-/, '')\n}\n\n/**\n * Creates a BytePlus Seed Speech transcription adapter with an explicit API\n * key.\n *\n * The key is the **Seed Speech** key, not the Ark key used by the chat, image\n * and video adapters.\n */\nexport function createBytePlusTranscription<\n TModel extends BytePlusTranscriptionModel = BytePlusTranscriptionModel,\n>(\n model: TModel,\n apiKey: string,\n config?: Omit<BytePlusVoiceConfig, 'apiKey'>,\n): BytePlusTranscriptionAdapter<TModel> {\n return new BytePlusTranscriptionAdapter(model, { ...config, apiKey })\n}\n\n/**\n * Creates a BytePlus Seed Speech transcription adapter, reading the API key\n * from `BYTEPLUS_VOICE_API_KEY`.\n *\n * @throws Error if `BYTEPLUS_VOICE_API_KEY` is not set.\n */\nexport function byteplusTranscription<\n TModel extends BytePlusTranscriptionModel = BytePlusTranscriptionModel,\n>(\n model: TModel,\n config?: Omit<BytePlusVoiceConfig, 'apiKey'>,\n): BytePlusTranscriptionAdapter<TModel> {\n return createBytePlusTranscription(\n model,\n getBytePlusVoiceApiKeyFromEnv(),\n config,\n )\n}\n"],"mappings":"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"}
@@ -34,9 +34,11 @@ function toTokenCount(value) {
34
34
  /**
35
35
  * Maps a finished task's usage onto `TokenUsage`.
36
36
  *
37
- * Seedance bills output only — the API documents input tokens as always 0 and
38
- * `total_tokens` as equal to `completion_tokens` — so `promptTokens` is 0 and
39
- * the completion count doubles as `unitsBilled`.
37
+ * Seedance bills output only. The API documents input tokens as always 0 and
38
+ * `total_tokens` as equal to `completion_tokens`, so `promptTokens` is 0 and
39
+ * the completion count is the billed quantity (`usage.billed` with
40
+ * `unit: 'tokens'`). The deprecated `unitsBilled` is still populated for
41
+ * backward compatibility.
40
42
  */
41
43
  function buildBytePlusVideoUsage(usage) {
42
44
  if (!usage) return void 0;
@@ -48,6 +50,10 @@ function buildBytePlusVideoUsage(usage) {
48
50
  promptTokens: 0,
49
51
  completionTokens: completion,
50
52
  totalTokens: totalTokens ?? completion,
53
+ billed: {
54
+ quantity: completion,
55
+ unit: "tokens"
56
+ },
51
57
  unitsBilled: completion
52
58
  };
53
59
  }
@@ -1 +1 @@
1
- {"version":3,"file":"video.js","names":[],"sources":["../../../src/adapters/video.ts"],"sourcesContent":["import { resolveMediaPrompt } from '@tanstack/ai'\nimport { BaseVideoAdapter, snapToDurationOption } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport {\n bytePlusArkError,\n bytePlusArkHeaders,\n bytePlusTimeoutSignal,\n getBytePlusArkApiKeyFromEnv,\n readJsonBody,\n toHeaderRecord,\n withBytePlusArkDefaults,\n} from '../utils/client'\nimport {\n getBytePlusVideoDurationOptions,\n isKnownBytePlusVideoModel,\n} from '../model-meta'\nimport {\n resolveBytePlusVideoResolution,\n resolveBytePlusVideoSize,\n supportsAudioOnlyReference,\n supportsLastFrame,\n supportsReferenceMedia,\n} from '../video/video-provider-options'\nimport type { DurationOptions } from '@tanstack/ai/adapters'\nimport type {\n AudioPart,\n ImagePart,\n MediaInputMetadata,\n TokenUsage,\n VideoGenerationOptions,\n VideoJobResult,\n VideoPart,\n VideoStatusResult,\n VideoUrlResult,\n} from '@tanstack/ai'\nimport type {\n BytePlusVideoContentPart,\n BytePlusVideoCreateRequest,\n BytePlusVideoCreateResponse,\n BytePlusVideoTask,\n BytePlusVideoTaskStatus,\n BytePlusVideoTaskUsage,\n} from '../video/wire-types'\nimport type { BytePlusVideoProviderOptions } from '../video/video-provider-options'\nimport type {\n BytePlusVideoModelOrString,\n ResolveBytePlusVideoInputModalities,\n ResolveBytePlusVideoSize,\n} from '../model-meta'\nimport type { BytePlusArkConfig } from '../utils/client'\n\n/**\n * Configuration for the BytePlus Seedance video adapter.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport interface BytePlusVideoConfig extends BytePlusArkConfig {}\n\n/** Path of the Seedance task API, relative to the Ark base URL. */\nconst TASKS_PATH = '/contents/generations/tasks'\n\n/**\n * `content.video_url` and `content.last_frame_url` are deleted 24 hours after\n * the task produces them.\n */\nconst VIDEO_URL_TTL_MS = 24 * 60 * 60 * 1000\n\n/**\n * Converts a media prompt part into the URL string Seedance's `content[]`\n * takes: public URLs pass through (BytePlus fetches them server-side), data\n * sources become base64 data URIs.\n */\nfunction mediaPartToUrl(\n part:\n | ImagePart<MediaInputMetadata>\n | VideoPart<MediaInputMetadata>\n | AudioPart<MediaInputMetadata>,\n): string {\n const { source } = part\n if (source.type === 'url') return source.value\n if (source.value.startsWith('data:')) return source.value\n return `data:${source.mimeType.toLowerCase()};base64,${source.value}`\n}\n\n/** Coerces a usage count that the API types as a string but sends as a number. */\nfunction toTokenCount(value: number | string | undefined): number | undefined {\n if (typeof value === 'number') return value\n if (typeof value === 'string') {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : undefined\n }\n return undefined\n}\n\n/**\n * Maps a finished task's usage onto `TokenUsage`.\n *\n * Seedance bills output only — the API documents input tokens as always 0 and\n * `total_tokens` as equal to `completion_tokens` — so `promptTokens` is 0 and\n * the completion count doubles as `unitsBilled`.\n */\nfunction buildBytePlusVideoUsage(\n usage: BytePlusVideoTaskUsage | undefined,\n): TokenUsage | undefined {\n if (!usage) return undefined\n\n const completionTokens = toTokenCount(usage.completion_tokens)\n const totalTokens = toTokenCount(usage.total_tokens)\n if (completionTokens === undefined && totalTokens === undefined) {\n return undefined\n }\n\n const completion = completionTokens ?? totalTokens ?? 0\n return {\n promptTokens: 0,\n completionTokens: completion,\n totalTokens: totalTokens ?? completion,\n unitsBilled: completion,\n }\n}\n\n/**\n * Formats a terminal task's error detail for a status / failure message.\n *\n * Always returns a string. Core surfaces a failed job as\n * `throw new Error(statusResult.error || 'Video generation failed')`, so\n * returning `undefined` for a failure Ark reported without an `error` block\n * would hand the caller an unattributable error. The final fallback is a\n * snapshot of the identifying fields instead.\n */\nfunction describeTaskFailure(task: BytePlusVideoTask): string {\n const { code, message } = task.error ?? {}\n if (code && message) return `${code}: ${message}`\n if (message) return message\n if (code) return code\n // `expired` and `cancelled` are terminal without an `error` block.\n if (task.status === 'expired') {\n return 'Task expired before it finished (execution_expires_after elapsed).'\n }\n if (task.status === 'cancelled') return 'Task was cancelled.'\n return (\n `Task reported status \"${task.status ?? 'unknown'}\" with no error detail ` +\n `(id=${task.id ?? 'unknown'}, model=${task.model ?? 'unknown'}).`\n )\n}\n\n/**\n * BytePlus Seedance video generation adapter.\n *\n * Drives Ark's asynchronous task API — `POST /contents/generations/tasks` to\n * submit, `GET /contents/generations/tasks/{id}` to poll and to read the\n * finished video URL. Core owns the polling loop; this adapter implements the\n * three primitives plus the duration metadata.\n *\n * Prompt parts map onto Seedance's `content[]` roles, which the API sorts into\n * mutually exclusive task types:\n *\n * - `'start_frame'` (or a single un-roled image) → `first_frame` — the frame\n * the video opens on (`i2v`).\n * - `'end_frame'` → `last_frame` — the frame it closes on (`flf2v`); Seedance\n * requires a `first_frame` alongside it, and\n * `seedance-1-0-pro-fast-251015` does not support it at all.\n * - `'reference'` / `'character'` → `reference_image`, video parts →\n * `reference_video`, audio parts → `reference_audio` — subject and style\n * references the model draws on (`r2v`, Seedance 2.5 and 2.0 family).\n * Seedance 2.5 also accepts audio-only reference input; 2.0 does not.\n *\n * Frame roles and reference roles cannot be combined in one request, so the\n * adapter rejects a mix up front rather than surfacing a raw 400.\n *\n * @experimental Video generation is an experimental feature and may change.\n *\n * @example\n * ```typescript\n * const adapter = byteplusVideo('seedance-1-0-pro-fast-251015')\n *\n * const { jobId } = await generateVideo({\n * adapter,\n * prompt: 'a guitar being played in a store',\n * size: '16:9_720p',\n * duration: 4,\n * modelOptions: { service_tier: 'flex' },\n * })\n * ```\n */\nexport class BytePlusVideoAdapter<\n TModel extends BytePlusVideoModelOrString,\n> extends BaseVideoAdapter<\n TModel,\n BytePlusVideoProviderOptions,\n Record<TModel, BytePlusVideoProviderOptions>,\n Record<TModel, ResolveBytePlusVideoSize<TModel>>,\n Record<TModel, ResolveBytePlusVideoInputModalities<TModel>>,\n Record<TModel, number>\n> {\n readonly name = 'byteplus' as const\n\n /** Config with the Ark base URL resolved and its trailing slashes trimmed. */\n private readonly clientConfig: Omit<BytePlusVideoConfig, 'baseURL'> & {\n baseURL: string\n }\n\n constructor(config: BytePlusVideoConfig, model: TModel) {\n super({}, model)\n this.clientConfig = withBytePlusArkDefaults(config)\n }\n\n private async request(\n path: string,\n init?: Omit<RequestInit, 'headers'>,\n ): Promise<{ response: Response; body: unknown }> {\n const fetchImpl = this.clientConfig.fetch ?? fetch\n const signal = bytePlusTimeoutSignal(this.clientConfig.timeout)\n const response = await fetchImpl(`${this.clientConfig.baseURL}${path}`, {\n ...init,\n ...(signal && { signal }),\n headers: bytePlusArkHeaders(\n this.clientConfig.apiKey,\n toHeaderRecord(this.clientConfig.defaultHeaders),\n ),\n })\n return { response, body: await readJsonBody(response) }\n }\n\n /**\n * Builds the `content[]` array from the resolved prompt, enforcing\n * Seedance's role vocabulary and mode exclusivity.\n */\n private buildContent(\n resolved: ReturnType<typeof resolveMediaPrompt>,\n ): Array<BytePlusVideoContentPart> {\n const model = this.model\n const content: Array<BytePlusVideoContentPart> = []\n if (resolved.text) content.push({ type: 'text', text: resolved.text })\n\n // Every rule below except the role vocabulary itself is a claim about a\n // *specific* model's capabilities, drawn from the known Seedance catalog.\n // None of it can be true of a model that does not exist yet, so for an\n // unknown id the guards stand down and Ark rules — otherwise the escape\n // hatch would block exactly the requests it exists to enable (see\n // BytePlusVideoModelOrString). 'mask' / 'control' still throw: Seedance's\n // wire format has no field to carry them on any model.\n const gated = isKnownBytePlusVideoModel(model)\n\n let firstFrames = 0\n let lastFrames = 0\n // Audio counts as a reference for the mode-exclusivity check. On Seedance\n // 2.0 it also needs a visual reference; 2.5 allows audio-only.\n let visualReferences = 0\n let audioReferences = 0\n\n for (const part of resolved.images) {\n const role = part.metadata?.role\n switch (role) {\n case 'mask':\n case 'control':\n throw new Error(\n `byteplus: Seedance has no '${role}' image input on model ${model}. ` +\n `Use 'start_frame', 'end_frame' or 'reference'.`,\n )\n case 'end_frame': {\n if (gated && !supportsLastFrame(model)) {\n throw new Error(\n `byteplus: ${model} does not support a closing frame — it does ` +\n `text-to-video and first-frame image-to-video only. Drop the ` +\n `'end_frame' image or switch to a model with first-and-last-frame support.`,\n )\n }\n lastFrames++\n content.push({\n type: 'image_url',\n image_url: { url: mediaPartToUrl(part) },\n role: 'last_frame',\n })\n break\n }\n case 'reference':\n case 'character': {\n if (gated && !supportsReferenceMedia(model)) {\n throw new Error(\n `byteplus: ${model} does not support reference images. Reference ` +\n `media is available on Seedance 2.5 and the 2.0 family; on this ` +\n `model use 'start_frame' / 'end_frame' images instead.`,\n )\n }\n visualReferences++\n content.push({\n type: 'image_url',\n image_url: { url: mediaPartToUrl(part) },\n role: 'reference_image',\n })\n break\n }\n // An un-roled image is the opening frame, matching the API's own\n // default and the fal / Veo adapters' positional convention.\n case 'start_frame':\n case undefined: {\n firstFrames++\n content.push({\n type: 'image_url',\n image_url: { url: mediaPartToUrl(part) },\n role: 'first_frame',\n })\n break\n }\n }\n }\n\n // Video and audio parts only exist in reference mode: Seedance rejects an\n // un-roled video with \"reference media mode requires video role to be\n // reference_video\", and has no frame-style role for either modality.\n for (const part of resolved.videos) {\n if (gated && !supportsReferenceMedia(model)) {\n throw new Error(\n `byteplus: ${model} does not accept video prompt parts. Reference ` +\n `video is available on Seedance 2.5 and the 2.0 family only.`,\n )\n }\n visualReferences++\n content.push({\n type: 'video_url',\n video_url: { url: mediaPartToUrl(part) },\n role: 'reference_video',\n })\n }\n\n for (const part of resolved.audios) {\n if (gated && !supportsReferenceMedia(model)) {\n throw new Error(\n `byteplus: ${model} does not accept audio prompt parts. Reference ` +\n `audio is available on Seedance 2.5 and the 2.0 family only.`,\n )\n }\n audioReferences++\n content.push({\n type: 'audio_url',\n audio_url: { url: mediaPartToUrl(part) },\n role: 'reference_audio',\n })\n }\n\n const frames = firstFrames + lastFrames\n if (gated && frames > 0 && visualReferences + audioReferences > 0) {\n throw new Error(\n `byteplus: first/last frame inputs cannot be combined with reference ` +\n `media on model ${model}. Use either frame roles ('start_frame', ` +\n `'end_frame') or reference roles ('reference', 'character', video, ` +\n `audio) — not both.`,\n )\n }\n\n if (gated && firstFrames > 1) {\n throw new Error(\n `byteplus: ${model} accepts at most one opening frame; received ` +\n `${firstFrames} un-roled or 'start_frame' images. Use metadata.role ` +\n `('end_frame', 'reference') to disambiguate the others.`,\n )\n }\n\n if (gated && lastFrames > 1) {\n throw new Error(\n `byteplus: ${model} accepts at most one closing frame; received ` +\n `${lastFrames} 'end_frame' images.`,\n )\n }\n\n // Seedance treats a closing frame as the second half of first-and-last-\n // frame mode: on its own it fails with \"last frame image content cannot be\n // mixed with first frame or reference image content\".\n if (gated && lastFrames > 0 && firstFrames === 0) {\n throw new Error(\n `byteplus: a closing frame needs an opening frame alongside it on ` +\n `model ${model}. Add a 'start_frame' image, or drop the 'end_frame' role.`,\n )\n }\n\n if (\n gated &&\n audioReferences > 0 &&\n visualReferences === 0 &&\n !supportsAudioOnlyReference(model)\n ) {\n throw new Error(\n `byteplus: a reference audio input cannot be the only reference on ` +\n `model ${model}. Pair it with a reference image or video, or use ` +\n `Seedance 2.5 which accepts audio-only reference input.`,\n )\n }\n\n if (content.length === 0) {\n throw new Error(\n `byteplus: a video prompt must carry text or at least one media input ` +\n `(model: ${model}).`,\n )\n }\n\n return content\n }\n\n async createVideoJob(\n options: VideoGenerationOptions<\n BytePlusVideoProviderOptions,\n ResolveBytePlusVideoSize<TModel>,\n number\n >,\n ): Promise<VideoJobResult> {\n const { size, modelOptions, logger } = options\n const model = this.model\n\n const content = this.buildContent(resolveMediaPrompt(options.prompt))\n\n // The generic `size` carries a \"ratio_resolution\" template and splits back\n // into Seedance's separate fields. Explicit modelOptions win below.\n const parsedSize =\n size !== undefined ? resolveBytePlusVideoSize(model, size) : undefined\n\n // Coerce the requested duration into the model's range rather than letting\n // the API reject it. `modelOptions.duration` is deliberately not snapped:\n // it is the escape hatch for `-1` (model picks the length).\n //\n // An unknown model's duration goes through verbatim. Snapping it would\n // mean clamping against the ranges today's models happen to have, so a\n // future model's legitimate 20-second request would silently become 15 —\n // corrupting the request instead of protecting it.\n const duration =\n options.duration !== undefined\n ? isKnownBytePlusVideoModel(model)\n ? this.snapDuration(options.duration)\n : options.duration\n : undefined\n\n const request: BytePlusVideoCreateRequest = {\n ...(parsedSize && {\n ratio: parsedSize.ratio,\n ...(parsedSize.resolution !== undefined && {\n resolution: parsedSize.resolution,\n }),\n }),\n ...(duration !== undefined && { duration }),\n // Explicit provider options win over everything derived above.\n ...modelOptions,\n model,\n content,\n }\n\n // Validate what actually ships, not just what `size` contributed: a\n // `modelOptions.resolution` overriding an already-checked size would\n // otherwise reach Ark unchecked.\n if (request.resolution !== undefined) {\n request.resolution = resolveBytePlusVideoResolution(\n model,\n request.resolution,\n )\n }\n\n try {\n logger.request(\n `activity=video.create provider=${this.name} model=${model} size=${size ?? 'default'} duration=${request.duration ?? 'default'}`,\n { provider: this.name, model },\n )\n\n const { response, body } = await this.request(TASKS_PATH, {\n method: 'POST',\n body: JSON.stringify(request),\n })\n if (!response.ok) {\n throw bytePlusArkError(response.status, body, 'video task creation')\n }\n\n const { id } = (body ?? {}) as BytePlusVideoCreateResponse\n if (!id) {\n throw new Error('byteplus: video task creation returned no task id.')\n }\n\n return { jobId: id, model }\n } catch (error: unknown) {\n logger.errors(`${this.name}.createVideoJob fatal`, {\n error: toRunErrorPayload(error, `${this.name}.createVideoJob failed`),\n source: `${this.name}.createVideoJob`,\n })\n throw error\n }\n }\n\n /**\n * Fetches a task, tagging the thrown error with the HTTP status.\n *\n * The 200 body is validated rather than cast. `readJsonBody` returns\n * `undefined` for an empty body and the raw text for a non-JSON one — both\n * documented failure modes of these hosts (an HTML error page from a proxy\n * in front of the API). Casting either to `BytePlusVideoTask` yields a task\n * whose `status` is `undefined`, which {@link mapStatus} would have to\n * interpret; the honest answer is that the response was not a task at all,\n * so say so while the body is still in hand.\n */\n private async retrieveTask(jobId: string): Promise<BytePlusVideoTask> {\n const { response, body } = await this.request(\n `${TASKS_PATH}/${encodeURIComponent(jobId)}`,\n )\n if (!response.ok) {\n const error = bytePlusArkError(response.status, body, 'video task lookup')\n ;(error as { status?: number }).status = response.status\n throw error\n }\n if (typeof body !== 'object' || body === null) {\n throw bytePlusArkError(\n response.status,\n body,\n `video task lookup (job ${jobId}) returned a non-object body`,\n )\n }\n return body as BytePlusVideoTask\n }\n\n async getVideoStatus(jobId: string): Promise<VideoStatusResult> {\n let task: BytePlusVideoTask\n try {\n task = await this.retrieveTask(jobId)\n } catch (error) {\n // A task record lives 7 days from creation; past that the id 404s. Keep\n // Ark's own code/message: a 404 from a wrong baseURL, a proxy, or a\n // region mismatch is not an expired job id, and collapsing them all to\n // \"Job not found\" sends the caller hunting the wrong thing.\n if ((error as { status?: number }).status === 404) {\n return {\n jobId,\n status: 'failed',\n error: `Job not found: ${jobId} (${(error as Error).message})`,\n }\n }\n throw error\n }\n\n const status = this.mapStatus(task.status)\n const failure = status === 'failed' ? describeTaskFailure(task) : undefined\n return {\n jobId,\n status,\n ...(failure !== undefined && { error: failure }),\n }\n }\n\n async getVideoUrl(jobId: string): Promise<VideoUrlResult> {\n let task: BytePlusVideoTask\n try {\n task = await this.retrieveTask(jobId)\n } catch (error) {\n // See getVideoStatus: Ark's detail distinguishes an expired id from a\n // misrouted request.\n if ((error as { status?: number }).status === 404) {\n throw new Error(\n `Video job not found: ${jobId} (${(error as Error).message})`,\n )\n }\n throw error\n }\n\n const status = this.mapStatus(task.status)\n if (status === 'failed') {\n throw new Error(\n `Video generation failed: ${describeTaskFailure(task)}. Job ID: ${jobId}`,\n )\n }\n\n const url = task.content?.video_url\n if (!url) {\n throw new Error(\n `Video is not ready for download. Check status first. Job ID: ${jobId}`,\n )\n }\n\n // The 24-hour window runs from when the output was produced, which is the\n // last status change on a succeeded task — `created_at` anchors the\n // separate 7-day retention of the task record itself, and can be far\n // earlier (a live `flex` task sat queued ~15 minutes). Corroborated by the\n // signed TOS link itself, which carries `X-Tos-Expires=86400` from an\n // `X-Tos-Date` matching `updated_at`. Fall back to `created_at` only when\n // `updated_at` is missing.\n const anchorSeconds = task.updated_at ?? task.created_at\n const expiresAt =\n anchorSeconds !== undefined\n ? new Date(anchorSeconds * 1000 + VIDEO_URL_TTL_MS)\n : undefined\n\n const usage = buildBytePlusVideoUsage(task.usage)\n return {\n jobId,\n url,\n ...(expiresAt && { expiresAt }),\n ...(usage && { usage }),\n }\n }\n\n /**\n * Maps Seedance task states onto the generic video status set. `expired`\n * (the task outlived `execution_expires_after`) and `cancelled` are\n * terminal non-successes, so both report as failed.\n *\n * An unrecognized state throws rather than defaulting to `processing`.\n * Core's poll loop treats `processing` as \"keep waiting\", so mapping an\n * unknown state — a missing `status`, or a terminal one Ark adds later such\n * as `rejected` — onto it means polling until `maxDuration` and then\n * reporting a generic timeout, with the state Ark actually sent never\n * reaching the caller. Failing here names it.\n */\n protected mapStatus(\n apiStatus: BytePlusVideoTaskStatus | string | undefined,\n ): VideoStatusResult['status'] {\n switch (apiStatus) {\n case 'queued':\n return 'pending'\n case 'running':\n return 'processing'\n case 'succeeded':\n return 'completed'\n case 'failed':\n case 'expired':\n case 'cancelled':\n return 'failed'\n case undefined:\n default:\n throw new Error(\n `byteplus: unrecognized Seedance task status ` +\n `${apiStatus === undefined ? '(missing)' : `\"${apiStatus}\"`}. ` +\n `Known states: queued, running, succeeded, failed, expired, cancelled.`,\n )\n }\n }\n\n /**\n * Seedance accepts any whole second inside a per-model range: 4–15s on the\n * 2.0 family, 4–12s on 1.5-pro, 2–12s on the 1.0 models. An unknown model\n * reports the union of those ranges as a UI hint — see\n * `BYTEPLUS_VIDEO_FALLBACK_DURATIONS`, which `createVideoJob` does not snap\n * against.\n */\n override availableDurations(): DurationOptions<number> {\n return getBytePlusVideoDurationOptions(this.model)\n }\n\n /**\n * Coerce a raw seconds value to the closest duration this model accepts\n * (clamped to its range and rounded to whole seconds).\n */\n override snapDuration(seconds: number): number | undefined {\n return snapToDurationOption(seconds, this.availableDurations())\n }\n}\n\n/**\n * Creates a BytePlus Seedance video adapter with an explicit API key.\n * Type resolution happens here at the call site.\n *\n * @experimental Video generation is an experimental feature and may change.\n *\n * @param model - The model name (e.g., 'seedance-1-0-pro-fast-251015')\n * @param apiKey - Your BytePlus Ark API key\n * @param config - Optional additional configuration\n * @returns Configured BytePlus video adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createBytePlusVideo('seedance-1-5-pro-251215', 'ark-...')\n *\n * const { jobId } = await generateVideo({\n * adapter,\n * prompt: 'a guitar being played in a store',\n * size: '16:9_1080p',\n * duration: 5,\n * })\n * ```\n */\nexport function createBytePlusVideo<TModel extends BytePlusVideoModelOrString>(\n model: TModel,\n apiKey: string,\n config?: Omit<BytePlusVideoConfig, 'apiKey'>,\n): BytePlusVideoAdapter<TModel> {\n return new BytePlusVideoAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a BytePlus Seedance video adapter, reading `ARK_API_KEY` from the\n * environment. Type resolution happens here at the call site.\n *\n * Note that Ark keys are region-isolated and Seedance is only served from the\n * Asia-Pacific endpoint — an EU key will not work here.\n *\n * @experimental Video generation is an experimental feature and may change.\n *\n * @param model - The model name (e.g., 'dreamina-seedance-2-0-260128')\n * @param config - Optional configuration (excluding apiKey, auto-detected)\n * @returns Configured BytePlus video adapter instance with resolved types\n * @throws Error if ARK_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * const adapter = byteplusVideo('dreamina-seedance-2-0-260128')\n *\n * // Image-to-video: an un-roled image is the opening frame.\n * const { jobId } = await generateVideo({\n * adapter,\n * prompt: [\n * { type: 'text', content: 'the guitarist starts playing' },\n * { type: 'image', source: { type: 'url', value: 'https://example.com/shop.jpg' } },\n * ],\n * })\n *\n * const status = await getVideoJobStatus({ adapter, jobId })\n * ```\n *\n * ## Models this package does not know yet\n *\n * `model` also accepts any string, so a Seedance id BytePlus publishes after\n * this release works without upgrading. **Seedance 2.5 is the case this exists\n * for**: `dreamina-seedance-2-5-260628` is real and reachable, but its\n * capability cells could not be probed from this repo's account (Ark answers\n * 404 `ModelNotOpen` until the model is activated in the Ark Console), so it\n * is deliberately absent from the narrowed model tables. Passing it here works\n * for an account that has activated it.\n *\n * An unknown id relaxes both halves of the adapter: the `size` type widens to\n * any string, provider options are ungated, and the runtime guards that encode\n * per-model capabilities — resolution tiers, closing-frame and reference-media\n * support, frame cardinality and mode exclusivity, duration snapping — stand\n * down so Ark decides. Known ids are unaffected. See\n * {@link BytePlusVideoModelOrString} for how to discover and probe an id.\n *\n * @example\n * ```typescript\n * // Seedance 2.5, before this package ships probe-verified metadata for it:\n * const adapter = byteplusVideo('dreamina-seedance-2-5-260628')\n * ```\n */\nexport function byteplusVideo<TModel extends BytePlusVideoModelOrString>(\n model: TModel,\n config?: Omit<BytePlusVideoConfig, 'apiKey'>,\n): BytePlusVideoAdapter<TModel> {\n const apiKey = getBytePlusArkApiKeyFromEnv()\n return createBytePlusVideo(model, apiKey, config)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"video.js","names":[],"sources":["../../../src/adapters/video.ts"],"sourcesContent":["import { resolveMediaPrompt } from '@tanstack/ai'\nimport { BaseVideoAdapter, snapToDurationOption } from '@tanstack/ai/adapters'\nimport { toRunErrorPayload } from '@tanstack/ai/adapter-internals'\nimport {\n bytePlusArkError,\n bytePlusArkHeaders,\n bytePlusTimeoutSignal,\n getBytePlusArkApiKeyFromEnv,\n readJsonBody,\n toHeaderRecord,\n withBytePlusArkDefaults,\n} from '../utils/client'\nimport {\n getBytePlusVideoDurationOptions,\n isKnownBytePlusVideoModel,\n} from '../model-meta'\nimport {\n resolveBytePlusVideoResolution,\n resolveBytePlusVideoSize,\n supportsAudioOnlyReference,\n supportsLastFrame,\n supportsReferenceMedia,\n} from '../video/video-provider-options'\nimport type { DurationOptions } from '@tanstack/ai/adapters'\nimport type {\n AudioPart,\n ImagePart,\n MediaInputMetadata,\n TokenUsage,\n VideoGenerationOptions,\n VideoJobResult,\n VideoPart,\n VideoStatusResult,\n VideoUrlResult,\n} from '@tanstack/ai'\nimport type {\n BytePlusVideoContentPart,\n BytePlusVideoCreateRequest,\n BytePlusVideoCreateResponse,\n BytePlusVideoTask,\n BytePlusVideoTaskStatus,\n BytePlusVideoTaskUsage,\n} from '../video/wire-types'\nimport type { BytePlusVideoProviderOptions } from '../video/video-provider-options'\nimport type {\n BytePlusVideoModelOrString,\n ResolveBytePlusVideoInputModalities,\n ResolveBytePlusVideoSize,\n} from '../model-meta'\nimport type { BytePlusArkConfig } from '../utils/client'\n\n/**\n * Configuration for the BytePlus Seedance video adapter.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport interface BytePlusVideoConfig extends BytePlusArkConfig {}\n\n/** Path of the Seedance task API, relative to the Ark base URL. */\nconst TASKS_PATH = '/contents/generations/tasks'\n\n/**\n * `content.video_url` and `content.last_frame_url` are deleted 24 hours after\n * the task produces them.\n */\nconst VIDEO_URL_TTL_MS = 24 * 60 * 60 * 1000\n\n/**\n * Converts a media prompt part into the URL string Seedance's `content[]`\n * takes: public URLs pass through (BytePlus fetches them server-side), data\n * sources become base64 data URIs.\n */\nfunction mediaPartToUrl(\n part:\n | ImagePart<MediaInputMetadata>\n | VideoPart<MediaInputMetadata>\n | AudioPart<MediaInputMetadata>,\n): string {\n const { source } = part\n if (source.type === 'url') return source.value\n if (source.value.startsWith('data:')) return source.value\n return `data:${source.mimeType.toLowerCase()};base64,${source.value}`\n}\n\n/** Coerces a usage count that the API types as a string but sends as a number. */\nfunction toTokenCount(value: number | string | undefined): number | undefined {\n if (typeof value === 'number') return value\n if (typeof value === 'string') {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : undefined\n }\n return undefined\n}\n\n/**\n * Maps a finished task's usage onto `TokenUsage`.\n *\n * Seedance bills output only. The API documents input tokens as always 0 and\n * `total_tokens` as equal to `completion_tokens`, so `promptTokens` is 0 and\n * the completion count is the billed quantity (`usage.billed` with\n * `unit: 'tokens'`). The deprecated `unitsBilled` is still populated for\n * backward compatibility.\n */\nfunction buildBytePlusVideoUsage(\n usage: BytePlusVideoTaskUsage | undefined,\n): TokenUsage | undefined {\n if (!usage) return undefined\n\n const completionTokens = toTokenCount(usage.completion_tokens)\n const totalTokens = toTokenCount(usage.total_tokens)\n if (completionTokens === undefined && totalTokens === undefined) {\n return undefined\n }\n\n const completion = completionTokens ?? totalTokens ?? 0\n return {\n promptTokens: 0,\n completionTokens: completion,\n totalTokens: totalTokens ?? completion,\n billed: { quantity: completion, unit: 'tokens' },\n unitsBilled: completion,\n }\n}\n\n/**\n * Formats a terminal task's error detail for a status / failure message.\n *\n * Always returns a string. Core surfaces a failed job as\n * `throw new Error(statusResult.error || 'Video generation failed')`, so\n * returning `undefined` for a failure Ark reported without an `error` block\n * would hand the caller an unattributable error. The final fallback is a\n * snapshot of the identifying fields instead.\n */\nfunction describeTaskFailure(task: BytePlusVideoTask): string {\n const { code, message } = task.error ?? {}\n if (code && message) return `${code}: ${message}`\n if (message) return message\n if (code) return code\n // `expired` and `cancelled` are terminal without an `error` block.\n if (task.status === 'expired') {\n return 'Task expired before it finished (execution_expires_after elapsed).'\n }\n if (task.status === 'cancelled') return 'Task was cancelled.'\n return (\n `Task reported status \"${task.status ?? 'unknown'}\" with no error detail ` +\n `(id=${task.id ?? 'unknown'}, model=${task.model ?? 'unknown'}).`\n )\n}\n\n/**\n * BytePlus Seedance video generation adapter.\n *\n * Drives Ark's asynchronous task API — `POST /contents/generations/tasks` to\n * submit, `GET /contents/generations/tasks/{id}` to poll and to read the\n * finished video URL. Core owns the polling loop; this adapter implements the\n * three primitives plus the duration metadata.\n *\n * Prompt parts map onto Seedance's `content[]` roles, which the API sorts into\n * mutually exclusive task types:\n *\n * - `'start_frame'` (or a single un-roled image) → `first_frame` — the frame\n * the video opens on (`i2v`).\n * - `'end_frame'` → `last_frame` — the frame it closes on (`flf2v`); Seedance\n * requires a `first_frame` alongside it, and\n * `seedance-1-0-pro-fast-251015` does not support it at all.\n * - `'reference'` / `'character'` → `reference_image`, video parts →\n * `reference_video`, audio parts → `reference_audio` — subject and style\n * references the model draws on (`r2v`, Seedance 2.5 and 2.0 family).\n * Seedance 2.5 also accepts audio-only reference input; 2.0 does not.\n *\n * Frame roles and reference roles cannot be combined in one request, so the\n * adapter rejects a mix up front rather than surfacing a raw 400.\n *\n * @experimental Video generation is an experimental feature and may change.\n *\n * @example\n * ```typescript\n * const adapter = byteplusVideo('seedance-1-0-pro-fast-251015')\n *\n * const { jobId } = await generateVideo({\n * adapter,\n * prompt: 'a guitar being played in a store',\n * size: '16:9_720p',\n * duration: 4,\n * modelOptions: { service_tier: 'flex' },\n * })\n * ```\n */\nexport class BytePlusVideoAdapter<\n TModel extends BytePlusVideoModelOrString,\n> extends BaseVideoAdapter<\n TModel,\n BytePlusVideoProviderOptions,\n Record<TModel, BytePlusVideoProviderOptions>,\n Record<TModel, ResolveBytePlusVideoSize<TModel>>,\n Record<TModel, ResolveBytePlusVideoInputModalities<TModel>>,\n Record<TModel, number>\n> {\n readonly name = 'byteplus' as const\n\n /** Config with the Ark base URL resolved and its trailing slashes trimmed. */\n private readonly clientConfig: Omit<BytePlusVideoConfig, 'baseURL'> & {\n baseURL: string\n }\n\n constructor(config: BytePlusVideoConfig, model: TModel) {\n super({}, model)\n this.clientConfig = withBytePlusArkDefaults(config)\n }\n\n private async request(\n path: string,\n init?: Omit<RequestInit, 'headers'>,\n ): Promise<{ response: Response; body: unknown }> {\n const fetchImpl = this.clientConfig.fetch ?? fetch\n const signal = bytePlusTimeoutSignal(this.clientConfig.timeout)\n const response = await fetchImpl(`${this.clientConfig.baseURL}${path}`, {\n ...init,\n ...(signal && { signal }),\n headers: bytePlusArkHeaders(\n this.clientConfig.apiKey,\n toHeaderRecord(this.clientConfig.defaultHeaders),\n ),\n })\n return { response, body: await readJsonBody(response) }\n }\n\n /**\n * Builds the `content[]` array from the resolved prompt, enforcing\n * Seedance's role vocabulary and mode exclusivity.\n */\n private buildContent(\n resolved: ReturnType<typeof resolveMediaPrompt>,\n ): Array<BytePlusVideoContentPart> {\n const model = this.model\n const content: Array<BytePlusVideoContentPart> = []\n if (resolved.text) content.push({ type: 'text', text: resolved.text })\n\n // Every rule below except the role vocabulary itself is a claim about a\n // *specific* model's capabilities, drawn from the known Seedance catalog.\n // None of it can be true of a model that does not exist yet, so for an\n // unknown id the guards stand down and Ark rules — otherwise the escape\n // hatch would block exactly the requests it exists to enable (see\n // BytePlusVideoModelOrString). 'mask' / 'control' still throw: Seedance's\n // wire format has no field to carry them on any model.\n const gated = isKnownBytePlusVideoModel(model)\n\n let firstFrames = 0\n let lastFrames = 0\n // Audio counts as a reference for the mode-exclusivity check. On Seedance\n // 2.0 it also needs a visual reference; 2.5 allows audio-only.\n let visualReferences = 0\n let audioReferences = 0\n\n for (const part of resolved.images) {\n const role = part.metadata?.role\n switch (role) {\n case 'mask':\n case 'control':\n throw new Error(\n `byteplus: Seedance has no '${role}' image input on model ${model}. ` +\n `Use 'start_frame', 'end_frame' or 'reference'.`,\n )\n case 'end_frame': {\n if (gated && !supportsLastFrame(model)) {\n throw new Error(\n `byteplus: ${model} does not support a closing frame — it does ` +\n `text-to-video and first-frame image-to-video only. Drop the ` +\n `'end_frame' image or switch to a model with first-and-last-frame support.`,\n )\n }\n lastFrames++\n content.push({\n type: 'image_url',\n image_url: { url: mediaPartToUrl(part) },\n role: 'last_frame',\n })\n break\n }\n case 'reference':\n case 'character': {\n if (gated && !supportsReferenceMedia(model)) {\n throw new Error(\n `byteplus: ${model} does not support reference images. Reference ` +\n `media is available on Seedance 2.5 and the 2.0 family; on this ` +\n `model use 'start_frame' / 'end_frame' images instead.`,\n )\n }\n visualReferences++\n content.push({\n type: 'image_url',\n image_url: { url: mediaPartToUrl(part) },\n role: 'reference_image',\n })\n break\n }\n // An un-roled image is the opening frame, matching the API's own\n // default and the fal / Veo adapters' positional convention.\n case 'start_frame':\n case undefined: {\n firstFrames++\n content.push({\n type: 'image_url',\n image_url: { url: mediaPartToUrl(part) },\n role: 'first_frame',\n })\n break\n }\n }\n }\n\n // Video and audio parts only exist in reference mode: Seedance rejects an\n // un-roled video with \"reference media mode requires video role to be\n // reference_video\", and has no frame-style role for either modality.\n for (const part of resolved.videos) {\n if (gated && !supportsReferenceMedia(model)) {\n throw new Error(\n `byteplus: ${model} does not accept video prompt parts. Reference ` +\n `video is available on Seedance 2.5 and the 2.0 family only.`,\n )\n }\n visualReferences++\n content.push({\n type: 'video_url',\n video_url: { url: mediaPartToUrl(part) },\n role: 'reference_video',\n })\n }\n\n for (const part of resolved.audios) {\n if (gated && !supportsReferenceMedia(model)) {\n throw new Error(\n `byteplus: ${model} does not accept audio prompt parts. Reference ` +\n `audio is available on Seedance 2.5 and the 2.0 family only.`,\n )\n }\n audioReferences++\n content.push({\n type: 'audio_url',\n audio_url: { url: mediaPartToUrl(part) },\n role: 'reference_audio',\n })\n }\n\n const frames = firstFrames + lastFrames\n if (gated && frames > 0 && visualReferences + audioReferences > 0) {\n throw new Error(\n `byteplus: first/last frame inputs cannot be combined with reference ` +\n `media on model ${model}. Use either frame roles ('start_frame', ` +\n `'end_frame') or reference roles ('reference', 'character', video, ` +\n `audio) — not both.`,\n )\n }\n\n if (gated && firstFrames > 1) {\n throw new Error(\n `byteplus: ${model} accepts at most one opening frame; received ` +\n `${firstFrames} un-roled or 'start_frame' images. Use metadata.role ` +\n `('end_frame', 'reference') to disambiguate the others.`,\n )\n }\n\n if (gated && lastFrames > 1) {\n throw new Error(\n `byteplus: ${model} accepts at most one closing frame; received ` +\n `${lastFrames} 'end_frame' images.`,\n )\n }\n\n // Seedance treats a closing frame as the second half of first-and-last-\n // frame mode: on its own it fails with \"last frame image content cannot be\n // mixed with first frame or reference image content\".\n if (gated && lastFrames > 0 && firstFrames === 0) {\n throw new Error(\n `byteplus: a closing frame needs an opening frame alongside it on ` +\n `model ${model}. Add a 'start_frame' image, or drop the 'end_frame' role.`,\n )\n }\n\n if (\n gated &&\n audioReferences > 0 &&\n visualReferences === 0 &&\n !supportsAudioOnlyReference(model)\n ) {\n throw new Error(\n `byteplus: a reference audio input cannot be the only reference on ` +\n `model ${model}. Pair it with a reference image or video, or use ` +\n `Seedance 2.5 which accepts audio-only reference input.`,\n )\n }\n\n if (content.length === 0) {\n throw new Error(\n `byteplus: a video prompt must carry text or at least one media input ` +\n `(model: ${model}).`,\n )\n }\n\n return content\n }\n\n async createVideoJob(\n options: VideoGenerationOptions<\n BytePlusVideoProviderOptions,\n ResolveBytePlusVideoSize<TModel>,\n number\n >,\n ): Promise<VideoJobResult> {\n const { size, modelOptions, logger } = options\n const model = this.model\n\n const content = this.buildContent(resolveMediaPrompt(options.prompt))\n\n // The generic `size` carries a \"ratio_resolution\" template and splits back\n // into Seedance's separate fields. Explicit modelOptions win below.\n const parsedSize =\n size !== undefined ? resolveBytePlusVideoSize(model, size) : undefined\n\n // Coerce the requested duration into the model's range rather than letting\n // the API reject it. `modelOptions.duration` is deliberately not snapped:\n // it is the escape hatch for `-1` (model picks the length).\n //\n // An unknown model's duration goes through verbatim. Snapping it would\n // mean clamping against the ranges today's models happen to have, so a\n // future model's legitimate 20-second request would silently become 15 —\n // corrupting the request instead of protecting it.\n const duration =\n options.duration !== undefined\n ? isKnownBytePlusVideoModel(model)\n ? this.snapDuration(options.duration)\n : options.duration\n : undefined\n\n const request: BytePlusVideoCreateRequest = {\n ...(parsedSize && {\n ratio: parsedSize.ratio,\n ...(parsedSize.resolution !== undefined && {\n resolution: parsedSize.resolution,\n }),\n }),\n ...(duration !== undefined && { duration }),\n // Explicit provider options win over everything derived above.\n ...modelOptions,\n model,\n content,\n }\n\n // Validate what actually ships, not just what `size` contributed: a\n // `modelOptions.resolution` overriding an already-checked size would\n // otherwise reach Ark unchecked.\n if (request.resolution !== undefined) {\n request.resolution = resolveBytePlusVideoResolution(\n model,\n request.resolution,\n )\n }\n\n try {\n logger.request(\n `activity=video.create provider=${this.name} model=${model} size=${size ?? 'default'} duration=${request.duration ?? 'default'}`,\n { provider: this.name, model },\n )\n\n const { response, body } = await this.request(TASKS_PATH, {\n method: 'POST',\n body: JSON.stringify(request),\n })\n if (!response.ok) {\n throw bytePlusArkError(response.status, body, 'video task creation')\n }\n\n const { id } = (body ?? {}) as BytePlusVideoCreateResponse\n if (!id) {\n throw new Error('byteplus: video task creation returned no task id.')\n }\n\n return { jobId: id, model }\n } catch (error: unknown) {\n logger.errors(`${this.name}.createVideoJob fatal`, {\n error: toRunErrorPayload(error, `${this.name}.createVideoJob failed`),\n source: `${this.name}.createVideoJob`,\n })\n throw error\n }\n }\n\n /**\n * Fetches a task, tagging the thrown error with the HTTP status.\n *\n * The 200 body is validated rather than cast. `readJsonBody` returns\n * `undefined` for an empty body and the raw text for a non-JSON one — both\n * documented failure modes of these hosts (an HTML error page from a proxy\n * in front of the API). Casting either to `BytePlusVideoTask` yields a task\n * whose `status` is `undefined`, which {@link mapStatus} would have to\n * interpret; the honest answer is that the response was not a task at all,\n * so say so while the body is still in hand.\n */\n private async retrieveTask(jobId: string): Promise<BytePlusVideoTask> {\n const { response, body } = await this.request(\n `${TASKS_PATH}/${encodeURIComponent(jobId)}`,\n )\n if (!response.ok) {\n const error = bytePlusArkError(response.status, body, 'video task lookup')\n ;(error as { status?: number }).status = response.status\n throw error\n }\n if (typeof body !== 'object' || body === null) {\n throw bytePlusArkError(\n response.status,\n body,\n `video task lookup (job ${jobId}) returned a non-object body`,\n )\n }\n return body as BytePlusVideoTask\n }\n\n async getVideoStatus(jobId: string): Promise<VideoStatusResult> {\n let task: BytePlusVideoTask\n try {\n task = await this.retrieveTask(jobId)\n } catch (error) {\n // A task record lives 7 days from creation; past that the id 404s. Keep\n // Ark's own code/message: a 404 from a wrong baseURL, a proxy, or a\n // region mismatch is not an expired job id, and collapsing them all to\n // \"Job not found\" sends the caller hunting the wrong thing.\n if ((error as { status?: number }).status === 404) {\n return {\n jobId,\n status: 'failed',\n error: `Job not found: ${jobId} (${(error as Error).message})`,\n }\n }\n throw error\n }\n\n const status = this.mapStatus(task.status)\n const failure = status === 'failed' ? describeTaskFailure(task) : undefined\n return {\n jobId,\n status,\n ...(failure !== undefined && { error: failure }),\n }\n }\n\n async getVideoUrl(jobId: string): Promise<VideoUrlResult> {\n let task: BytePlusVideoTask\n try {\n task = await this.retrieveTask(jobId)\n } catch (error) {\n // See getVideoStatus: Ark's detail distinguishes an expired id from a\n // misrouted request.\n if ((error as { status?: number }).status === 404) {\n throw new Error(\n `Video job not found: ${jobId} (${(error as Error).message})`,\n )\n }\n throw error\n }\n\n const status = this.mapStatus(task.status)\n if (status === 'failed') {\n throw new Error(\n `Video generation failed: ${describeTaskFailure(task)}. Job ID: ${jobId}`,\n )\n }\n\n const url = task.content?.video_url\n if (!url) {\n throw new Error(\n `Video is not ready for download. Check status first. Job ID: ${jobId}`,\n )\n }\n\n // The 24-hour window runs from when the output was produced, which is the\n // last status change on a succeeded task — `created_at` anchors the\n // separate 7-day retention of the task record itself, and can be far\n // earlier (a live `flex` task sat queued ~15 minutes). Corroborated by the\n // signed TOS link itself, which carries `X-Tos-Expires=86400` from an\n // `X-Tos-Date` matching `updated_at`. Fall back to `created_at` only when\n // `updated_at` is missing.\n const anchorSeconds = task.updated_at ?? task.created_at\n const expiresAt =\n anchorSeconds !== undefined\n ? new Date(anchorSeconds * 1000 + VIDEO_URL_TTL_MS)\n : undefined\n\n const usage = buildBytePlusVideoUsage(task.usage)\n return {\n jobId,\n url,\n ...(expiresAt && { expiresAt }),\n ...(usage && { usage }),\n }\n }\n\n /**\n * Maps Seedance task states onto the generic video status set. `expired`\n * (the task outlived `execution_expires_after`) and `cancelled` are\n * terminal non-successes, so both report as failed.\n *\n * An unrecognized state throws rather than defaulting to `processing`.\n * Core's poll loop treats `processing` as \"keep waiting\", so mapping an\n * unknown state — a missing `status`, or a terminal one Ark adds later such\n * as `rejected` — onto it means polling until `maxDuration` and then\n * reporting a generic timeout, with the state Ark actually sent never\n * reaching the caller. Failing here names it.\n */\n protected mapStatus(\n apiStatus: BytePlusVideoTaskStatus | string | undefined,\n ): VideoStatusResult['status'] {\n switch (apiStatus) {\n case 'queued':\n return 'pending'\n case 'running':\n return 'processing'\n case 'succeeded':\n return 'completed'\n case 'failed':\n case 'expired':\n case 'cancelled':\n return 'failed'\n case undefined:\n default:\n throw new Error(\n `byteplus: unrecognized Seedance task status ` +\n `${apiStatus === undefined ? '(missing)' : `\"${apiStatus}\"`}. ` +\n `Known states: queued, running, succeeded, failed, expired, cancelled.`,\n )\n }\n }\n\n /**\n * Seedance accepts any whole second inside a per-model range: 4–15s on the\n * 2.0 family, 4–12s on 1.5-pro, 2–12s on the 1.0 models. An unknown model\n * reports the union of those ranges as a UI hint — see\n * `BYTEPLUS_VIDEO_FALLBACK_DURATIONS`, which `createVideoJob` does not snap\n * against.\n */\n override availableDurations(): DurationOptions<number> {\n return getBytePlusVideoDurationOptions(this.model)\n }\n\n /**\n * Coerce a raw seconds value to the closest duration this model accepts\n * (clamped to its range and rounded to whole seconds).\n */\n override snapDuration(seconds: number): number | undefined {\n return snapToDurationOption(seconds, this.availableDurations())\n }\n}\n\n/**\n * Creates a BytePlus Seedance video adapter with an explicit API key.\n * Type resolution happens here at the call site.\n *\n * @experimental Video generation is an experimental feature and may change.\n *\n * @param model - The model name (e.g., 'seedance-1-0-pro-fast-251015')\n * @param apiKey - Your BytePlus Ark API key\n * @param config - Optional additional configuration\n * @returns Configured BytePlus video adapter instance with resolved types\n *\n * @example\n * ```typescript\n * const adapter = createBytePlusVideo('seedance-1-5-pro-251215', 'ark-...')\n *\n * const { jobId } = await generateVideo({\n * adapter,\n * prompt: 'a guitar being played in a store',\n * size: '16:9_1080p',\n * duration: 5,\n * })\n * ```\n */\nexport function createBytePlusVideo<TModel extends BytePlusVideoModelOrString>(\n model: TModel,\n apiKey: string,\n config?: Omit<BytePlusVideoConfig, 'apiKey'>,\n): BytePlusVideoAdapter<TModel> {\n return new BytePlusVideoAdapter({ apiKey, ...config }, model)\n}\n\n/**\n * Creates a BytePlus Seedance video adapter, reading `ARK_API_KEY` from the\n * environment. Type resolution happens here at the call site.\n *\n * Note that Ark keys are region-isolated and Seedance is only served from the\n * Asia-Pacific endpoint — an EU key will not work here.\n *\n * @experimental Video generation is an experimental feature and may change.\n *\n * @param model - The model name (e.g., 'dreamina-seedance-2-0-260128')\n * @param config - Optional configuration (excluding apiKey, auto-detected)\n * @returns Configured BytePlus video adapter instance with resolved types\n * @throws Error if ARK_API_KEY is not found in environment\n *\n * @example\n * ```typescript\n * const adapter = byteplusVideo('dreamina-seedance-2-0-260128')\n *\n * // Image-to-video: an un-roled image is the opening frame.\n * const { jobId } = await generateVideo({\n * adapter,\n * prompt: [\n * { type: 'text', content: 'the guitarist starts playing' },\n * { type: 'image', source: { type: 'url', value: 'https://example.com/shop.jpg' } },\n * ],\n * })\n *\n * const status = await getVideoJobStatus({ adapter, jobId })\n * ```\n *\n * ## Models this package does not know yet\n *\n * `model` also accepts any string, so a Seedance id BytePlus publishes after\n * this release works without upgrading. **Seedance 2.5 is the case this exists\n * for**: `dreamina-seedance-2-5-260628` is real and reachable, but its\n * capability cells could not be probed from this repo's account (Ark answers\n * 404 `ModelNotOpen` until the model is activated in the Ark Console), so it\n * is deliberately absent from the narrowed model tables. Passing it here works\n * for an account that has activated it.\n *\n * An unknown id relaxes both halves of the adapter: the `size` type widens to\n * any string, provider options are ungated, and the runtime guards that encode\n * per-model capabilities — resolution tiers, closing-frame and reference-media\n * support, frame cardinality and mode exclusivity, duration snapping — stand\n * down so Ark decides. Known ids are unaffected. See\n * {@link BytePlusVideoModelOrString} for how to discover and probe an id.\n *\n * @example\n * ```typescript\n * // Seedance 2.5, before this package ships probe-verified metadata for it:\n * const adapter = byteplusVideo('dreamina-seedance-2-5-260628')\n * ```\n */\nexport function byteplusVideo<TModel extends BytePlusVideoModelOrString>(\n model: TModel,\n config?: Omit<BytePlusVideoConfig, 'apiKey'>,\n): BytePlusVideoAdapter<TModel> {\n const apiKey = getBytePlusArkApiKeyFromEnv()\n return createBytePlusVideo(model, apiKey, config)\n}\n"],"mappings":"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"}
@@ -339,8 +339,8 @@ export type BytePlusVideoRatio = '16:9' | '9:16' | '4:3' | '3:4' | '1:1' | '21:9
339
339
  * Resolution tiers are model-specific (see
340
340
  * {@link BytePlusVideoModelResolutionByName}). Two findings that still
341
341
  * contradict older BytePlus prose: there is **no 2K tier on any Seedance
342
- * model**, and `4k` exists only on `dreamina-seedance-2-0-260128` (Seedance
343
- * 2.5 is 480p/720p only, per the live ModelArk docs).
342
+ * model**, and `4k` exists only on `dreamina-seedance-2-0-260128`. Seedance
343
+ * 2.5 accepts 480p/720p/1080p (ModelArk + fal spec, 2026-08-19).
344
344
  *
345
345
  * The API matches this field case-insensitively (`4K`, `4k` and `1080P` are
346
346
  * all accepted), so this package standardizes on the lowercase spelling.
@@ -429,12 +429,12 @@ export type BytePlusVideoModelInputModalitiesByName = {
429
429
  * Type-only map from video model name to the resolutions it accepts.
430
430
  *
431
431
  * 2.0 / 1.x cells were probe-verified on 2026-07-31; 2.5 comes from the
432
- * public ModelArk create-task docs (2026-08-07). Note
433
- * `seedance-1-0-pro-fast-251015` does accept `1080p`, despite older BytePlus
434
- * prose listing it as 480p/720p.
432
+ * public ModelArk create-task docs, refreshed 2026-08-19 when native 1080p
433
+ * shipped. Note `seedance-1-0-pro-fast-251015` does accept `1080p`, despite
434
+ * older BytePlus prose listing it as 480p/720p.
435
435
  */
436
436
  export type BytePlusVideoModelResolutionByName = {
437
- [DREAMINA_SEEDANCE_2_5.name]: '480p' | '720p';
437
+ [DREAMINA_SEEDANCE_2_5.name]: '480p' | '720p' | '1080p';
438
438
  [DREAMINA_SEEDANCE_2_0.name]: '480p' | '720p' | '1080p' | '4k';
439
439
  [DREAMINA_SEEDANCE_2_0_FAST.name]: '480p' | '720p';
440
440
  [DREAMINA_SEEDANCE_2_0_MINI.name]: '480p' | '720p';
@@ -1 +1 @@
1
- {"version":3,"file":"model-meta.js","names":[],"sources":["../../src/model-meta.ts"],"sourcesContent":["/**\n * BytePlus ModelArk model metadata.\n *\n * Every Ark model id in this file — chat, video and image — was verified live\n * against `https://ark.ap-southeast.bytepluses.com/api/v3` on 2026-07-31. The\n * two Seed Speech ids are the exception: they live on the voice host, which\n * needs a separate key that was not available, so they are docs-derived.\n * Capability metadata is a mix of probed and docs-derived facts; anything not\n * confirmed against the live API is annotated as such at its declaration.\n * BytePlus\n * deactivates model ids aggressively (the whole `seedance-1-0-lite-*` family,\n * `seed-1-6-lite-*`, `seedream-3-0-*`, and the `doubao-`/`skylark-` names are\n * all 404s internationally), so only dated, probe-confirmed ids are shipped.\n *\n * Prefix rules, also probe-confirmed:\n * - `dola-seed-2-1-turbo-260628` and `dola-seedream-5-0-pro-260628` are the\n * canonical ids; the bare forms resolve as aliases but the API echoes the\n * prefixed id back.\n * - The Seedance 2.0 family *requires* the `dreamina-` prefix.\n * - Older models reject the `dola-` prefix outright.\n */\nimport type { DurationOptions } from '@tanstack/ai/adapters'\nimport type { BytePlusTextProviderOptions } from './text/text-provider-options'\n\n/**\n * BytePlus exposes no server-side provider tools (no hosted web search, code\n * interpreter, …) on the international Ark endpoint, so every chat model\n * advertises an empty tool set. Typing it as `never` makes passing another\n * provider's `ProviderTool` to a BytePlus adapter a compile-time error.\n */\nexport type BytePlusProviderToolKind = never\n\n/**\n * Internal metadata structure describing a BytePlus model.\n */\ninterface ModelMeta {\n name: string\n supports: {\n input: ReadonlyArray<'text' | 'image' | 'audio' | 'video' | 'document'>\n output: ReadonlyArray<'text' | 'image' | 'audio' | 'video'>\n capabilities?: ReadonlyArray<\n 'reasoning' | 'tool_calling' | 'structured_outputs'\n >\n tools?: ReadonlyArray<BytePlusProviderToolKind>\n }\n context_window?: number\n max_input_tokens?: number\n max_output_tokens?: number\n}\n\n// ============================================================================\n// Chat models (Seed / GLM / DeepSeek / gpt-oss on Ark)\n// ============================================================================\n\nconst DOLA_SEED_2_1_TURBO = {\n name: 'dola-seed-2-1-turbo-260628',\n context_window: 256_000,\n max_input_tokens: 256_000,\n max_output_tokens: 256_000,\n supports: {\n input: ['text', 'image', 'video'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling', 'structured_outputs'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_2_0_LITE_260428 = {\n name: 'seed-2-0-lite-260428',\n context_window: 256_000,\n max_input_tokens: 256_000,\n max_output_tokens: 128_000,\n supports: {\n input: ['text', 'image', 'video', 'audio'],\n output: ['text'],\n // Live-probed 2026-07-31: rejects both json_schema and json_object.\n capabilities: ['reasoning', 'tool_calling'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_2_0_MINI_260428 = {\n name: 'seed-2-0-mini-260428',\n context_window: 256_000,\n max_input_tokens: 256_000,\n max_output_tokens: 128_000,\n supports: {\n input: ['text', 'image', 'video', 'audio'],\n output: ['text'],\n // Live-probed 2026-07-31: rejects both json_schema and json_object.\n capabilities: ['reasoning', 'tool_calling'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_2_0_PRO_260328 = {\n name: 'seed-2-0-pro-260328',\n context_window: 256_000,\n max_input_tokens: 224_000,\n max_output_tokens: 128_000,\n supports: {\n input: ['text', 'image', 'video'],\n output: ['text'],\n // Live-probed 2026-07-31: accepts json_schema, despite the docs table\n // saying otherwise.\n capabilities: ['reasoning', 'tool_calling', 'structured_outputs'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_2_0_LITE_260228 = {\n name: 'seed-2-0-lite-260228',\n context_window: 256_000,\n max_input_tokens: 224_000,\n max_output_tokens: 128_000,\n supports: {\n input: ['text', 'image', 'video'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling', 'structured_outputs'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_2_0_MINI_260215 = {\n name: 'seed-2-0-mini-260215',\n context_window: 256_000,\n max_input_tokens: 224_000,\n max_output_tokens: 128_000,\n supports: {\n input: ['text', 'image', 'video'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling', 'structured_outputs'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_2_0_CODE_PREVIEW_260328 = {\n name: 'seed-2-0-code-preview-260328',\n context_window: 256_000,\n max_input_tokens: 224_000,\n max_output_tokens: 128_000,\n supports: {\n input: ['text', 'image', 'video'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_1_8_251228 = {\n name: 'seed-1-8-251228',\n context_window: 256_000,\n max_input_tokens: 224_000,\n max_output_tokens: 64_000,\n supports: {\n input: ['text', 'image', 'video'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling', 'structured_outputs'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_1_6_250915 = {\n name: 'seed-1-6-250915',\n context_window: 256_000,\n max_input_tokens: 224_000,\n max_output_tokens: 32_000,\n supports: {\n input: ['text', 'image', 'video'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling', 'structured_outputs'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_1_6_250615 = {\n name: 'seed-1-6-250615',\n context_window: 256_000,\n max_input_tokens: 224_000,\n max_output_tokens: 32_000,\n supports: {\n input: ['text', 'image', 'video'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling', 'structured_outputs'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_1_6_FLASH_250715 = {\n name: 'seed-1-6-flash-250715',\n context_window: 256_000,\n max_input_tokens: 224_000,\n max_output_tokens: 32_000,\n supports: {\n input: ['text', 'image', 'video'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling', 'structured_outputs'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_1_6_FLASH_250615 = {\n name: 'seed-1-6-flash-250615',\n context_window: 256_000,\n max_input_tokens: 224_000,\n max_output_tokens: 32_000,\n supports: {\n input: ['text', 'image', 'video'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling', 'structured_outputs'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst GLM_5_2_260617 = {\n name: 'glm-5-2-260617',\n context_window: 1_024_000,\n max_input_tokens: 1_024_000,\n max_output_tokens: 128_000,\n supports: {\n input: ['text'],\n output: ['text'],\n // Live-probed 2026-07-31: accepts json_schema, despite the docs table\n // saying otherwise.\n capabilities: ['reasoning', 'tool_calling', 'structured_outputs'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst GLM_4_7_251222 = {\n name: 'glm-4-7-251222',\n context_window: 256_000,\n max_input_tokens: 224_000,\n max_output_tokens: 128_000,\n supports: {\n input: ['text'],\n output: ['text'],\n // Adherence-probed 2026-07-31: ACCEPTS a json_schema with 200 but ignores\n // it and answers in prose, so it is not a structured-output model. A\n // status-code-only probe reads this as support — see the note on\n // BYTEPLUS_STRUCTURED_OUTPUT_CHAT_MODELS.\n capabilities: ['reasoning', 'tool_calling'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst DEEPSEEK_V4_PRO_260425 = {\n name: 'deepseek-v4-pro-260425',\n context_window: 1_024_000,\n max_input_tokens: 1_024_000,\n max_output_tokens: 384_000,\n supports: {\n input: ['text'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst DEEPSEEK_V4_FLASH_260425 = {\n name: 'deepseek-v4-flash-260425',\n context_window: 1_024_000,\n max_input_tokens: 1_024_000,\n max_output_tokens: 384_000,\n supports: {\n input: ['text'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\n// The one model on Ark that defaults to `thinking: disabled`.\nconst DEEPSEEK_V3_2_251201 = {\n name: 'deepseek-v3-2-251201',\n context_window: 128_000,\n max_input_tokens: 128_000,\n max_output_tokens: 32_000,\n supports: {\n input: ['text'],\n output: ['text'],\n // Live-probed 2026-07-31: rejects both json_schema and json_object.\n capabilities: ['reasoning', 'tool_calling'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\n// The only model accepting `thinking: {type: 'auto'}`. Tool calling is\n// undocumented on Ark and unverified, so it is not advertised.\nconst GPT_OSS_120B_250805 = {\n name: 'gpt-oss-120b-250805',\n context_window: 128_000,\n max_input_tokens: 96_000,\n max_output_tokens: 64_000,\n supports: {\n input: ['text'],\n output: ['text'],\n capabilities: ['reasoning'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\n/**\n * All supported BytePlus chat model identifiers.\n */\nexport const BYTEPLUS_CHAT_MODELS = [\n DOLA_SEED_2_1_TURBO.name,\n SEED_2_0_LITE_260428.name,\n SEED_2_0_MINI_260428.name,\n SEED_2_0_PRO_260328.name,\n SEED_2_0_LITE_260228.name,\n SEED_2_0_MINI_260215.name,\n SEED_2_0_CODE_PREVIEW_260328.name,\n SEED_1_8_251228.name,\n SEED_1_6_250915.name,\n SEED_1_6_250615.name,\n SEED_1_6_FLASH_250715.name,\n SEED_1_6_FLASH_250615.name,\n GLM_5_2_260617.name,\n GLM_4_7_251222.name,\n DEEPSEEK_V4_PRO_260425.name,\n DEEPSEEK_V4_FLASH_260425.name,\n DEEPSEEK_V3_2_251201.name,\n GPT_OSS_120B_250805.name,\n] as const\n\n/**\n * Union of all supported BytePlus chat model names.\n */\nexport type BytePlusChatModel = (typeof BYTEPLUS_CHAT_MODELS)[number]\n\n/**\n * Chat models that emit a `encrypted_content` blob alongside\n * `reasoning_content` when thinking is enabled (\"thinking summary\" models).\n *\n * The blob is an opaque signature over the reasoning trace: when it is\n * present it must be echoed back verbatim on the assistant message in the\n * next turn. Live probing showed omitting it did *not* fail a simple tool\n * round-trip, so adapters preserve and replay it when present but must never\n * treat its absence as an error.\n */\nexport const BYTEPLUS_THINKING_SUMMARY_MODELS = [\n DOLA_SEED_2_1_TURBO.name,\n SEED_2_0_LITE_260428.name,\n SEED_2_0_MINI_260428.name,\n SEED_2_0_PRO_260328.name,\n] as const\n\n/**\n * Union of chat models that emit `encrypted_content`.\n */\nexport type BytePlusThinkingSummaryModel =\n (typeof BYTEPLUS_THINKING_SUMMARY_MODELS)[number]\n\nconst THINKING_SUMMARY_MODEL_SET: ReadonlySet<string> = new Set(\n BYTEPLUS_THINKING_SUMMARY_MODELS,\n)\n\n/**\n * True when the model emits `encrypted_content` that should be round-tripped\n * on subsequent turns.\n */\nexport function emitsEncryptedContent(model: string): boolean {\n return THINKING_SUMMARY_MODEL_SET.has(model)\n}\n\n/**\n * Chat models that accept `response_format: {type: 'json_schema'}`.\n *\n * Live-probed against all 18 chat models on 2026-07-31, not docs-derived — the\n * BytePlus capability tables are wrong here in both directions.\n *\n * Membership needs TWO things, because the API has both failure modes:\n * 1. The request is accepted. Seven models (`seed-2-0-lite-260428`,\n * `seed-2-0-mini-260428`, `seed-2-0-code-preview-260328`, both\n * `deepseek-v4-*`, `deepseek-v3-2-251201`, `gpt-oss-120b-250805`) answer a\n * JSON schema with 400 InvalidParameter — and reject\n * `{type: 'json_object'}` too, so there is no JSON-mode fallback.\n * 2. The schema is actually honoured. `glm-4-7-251222` accepts the request\n * with 200 and then ignores the schema, answering in prose (reproduced\n * twice by the adherence probe). A status-code-only probe wrongly reads\n * that as support, so it is excluded.\n *\n * Models that fail either check need tool-shaped extraction instead.\n *\n * Note that the default chat model `seed-2-0-lite-260428` is one of the\n * rejecting models: structured-output work needs `seed-2-0-lite-260228` or\n * `dola-seed-2-1-turbo-260628`.\n */\nexport const BYTEPLUS_STRUCTURED_OUTPUT_CHAT_MODELS = [\n DOLA_SEED_2_1_TURBO.name,\n SEED_2_0_PRO_260328.name,\n SEED_2_0_LITE_260228.name,\n SEED_2_0_MINI_260215.name,\n SEED_1_8_251228.name,\n SEED_1_6_250915.name,\n SEED_1_6_250615.name,\n SEED_1_6_FLASH_250715.name,\n SEED_1_6_FLASH_250615.name,\n GLM_5_2_260617.name,\n] as const\n\nconst STRUCTURED_OUTPUT_MODEL_SET: ReadonlySet<string> = new Set(\n BYTEPLUS_STRUCTURED_OUTPUT_CHAT_MODELS,\n)\n\n/**\n * True when the model supports native JSON-schema structured output.\n */\nexport function supportsStructuredOutput(model: string): boolean {\n return STRUCTURED_OUTPUT_MODEL_SET.has(model)\n}\n\n/**\n * Type-only map from chat model name to whether it supports native\n * JSON-schema structured output.\n */\nexport type BytePlusChatModelStructuredOutputByName = {\n [K in BytePlusChatModel]: K extends BytePlusStructuredOutputChatModel\n ? true\n : false\n}\n\n/**\n * Union of chat models supporting native JSON-schema structured output.\n */\nexport type BytePlusStructuredOutputChatModel =\n (typeof BYTEPLUS_STRUCTURED_OUTPUT_CHAT_MODELS)[number]\n\n/**\n * Type-only map from chat model name to its supported input modalities.\n * Used for type inference when constructing multimodal messages.\n */\nexport type BytePlusModelInputModalitiesByName = {\n [DOLA_SEED_2_1_TURBO.name]: typeof DOLA_SEED_2_1_TURBO.supports.input\n [SEED_2_0_LITE_260428.name]: typeof SEED_2_0_LITE_260428.supports.input\n [SEED_2_0_MINI_260428.name]: typeof SEED_2_0_MINI_260428.supports.input\n [SEED_2_0_PRO_260328.name]: typeof SEED_2_0_PRO_260328.supports.input\n [SEED_2_0_LITE_260228.name]: typeof SEED_2_0_LITE_260228.supports.input\n [SEED_2_0_MINI_260215.name]: typeof SEED_2_0_MINI_260215.supports.input\n [SEED_2_0_CODE_PREVIEW_260328.name]: typeof SEED_2_0_CODE_PREVIEW_260328.supports.input\n [SEED_1_8_251228.name]: typeof SEED_1_8_251228.supports.input\n [SEED_1_6_250915.name]: typeof SEED_1_6_250915.supports.input\n [SEED_1_6_250615.name]: typeof SEED_1_6_250615.supports.input\n [SEED_1_6_FLASH_250715.name]: typeof SEED_1_6_FLASH_250715.supports.input\n [SEED_1_6_FLASH_250615.name]: typeof SEED_1_6_FLASH_250615.supports.input\n [GLM_5_2_260617.name]: typeof GLM_5_2_260617.supports.input\n [GLM_4_7_251222.name]: typeof GLM_4_7_251222.supports.input\n [DEEPSEEK_V4_PRO_260425.name]: typeof DEEPSEEK_V4_PRO_260425.supports.input\n [DEEPSEEK_V4_FLASH_260425.name]: typeof DEEPSEEK_V4_FLASH_260425.supports.input\n [DEEPSEEK_V3_2_251201.name]: typeof DEEPSEEK_V3_2_251201.supports.input\n [GPT_OSS_120B_250805.name]: typeof GPT_OSS_120B_250805.supports.input\n}\n\n/**\n * Type-only map from chat model name to its supported provider tools.\n * BytePlus exposes no provider-tool factories, so every model gets an empty\n * tuple — passing another provider's tool is then a compile-time error.\n */\nexport type BytePlusChatModelToolCapabilitiesByName = {\n [K in BytePlusChatModel]: ReadonlyArray<BytePlusProviderToolKind>\n}\n\n/**\n * Type-only map from chat model name to its provider options type.\n */\nexport type BytePlusChatModelProviderOptionsByName = {\n [K in BytePlusChatModel]: BytePlusTextProviderOptions\n}\n\n// ============================================================================\n// Video models (Seedance, async task API)\n// ============================================================================\n\n/**\n * Output aspect ratios accepted by the Seedance task API. `adaptive` is only\n * meaningful for image-to-video, where the ratio follows the input frame.\n */\nexport type BytePlusVideoRatio =\n | '16:9'\n | '9:16'\n | '4:3'\n | '3:4'\n | '1:1'\n | '21:9'\n | 'adaptive'\n\n/**\n * Resolution tiers accepted by the Seedance task API.\n *\n * Resolution tiers are model-specific (see\n * {@link BytePlusVideoModelResolutionByName}). Two findings that still\n * contradict older BytePlus prose: there is **no 2K tier on any Seedance\n * model**, and `4k` exists only on `dreamina-seedance-2-0-260128` (Seedance\n * 2.5 is 480p/720p only, per the live ModelArk docs).\n *\n * The API matches this field case-insensitively (`4K`, `4k` and `1080P` are\n * all accepted), so this package standardizes on the lowercase spelling.\n */\nexport type BytePlusVideoResolution = '480p' | '720p' | '1080p' | '4k'\n\n/**\n * Generic `size` template for Seedance models: either a bare aspect ratio\n * (\"16:9\") or `ratio_resolution` (\"16:9_720p\"). The Seedance API takes the\n * two as separate `ratio` / `resolution` fields; the adapter splits this\n * template back apart.\n */\nexport type BytePlusVideoSize<\n TResolution extends BytePlusVideoResolution = BytePlusVideoResolution,\n> = BytePlusVideoRatio | `${BytePlusVideoRatio}_${TResolution}`\n\n// Multimodal reference-media capabilities (reference images / video / audio)\n// are docs-derived from the ModelArk create-task page. Model ids and the\n// resolution / duration tables for 2.0 were also live-probed on 2026-07-31;\n// 2.5 lands from the public docs once the model was fully opened (2026-08-07).\nconst DREAMINA_SEEDANCE_2_5 = {\n name: 'dreamina-seedance-2-5-260628',\n supports: {\n input: ['text', 'image', 'video', 'audio'],\n output: ['video', 'audio'],\n },\n} as const satisfies ModelMeta\n\nconst DREAMINA_SEEDANCE_2_0 = {\n name: 'dreamina-seedance-2-0-260128',\n supports: {\n input: ['text', 'image', 'video', 'audio'],\n output: ['video', 'audio'],\n },\n} as const satisfies ModelMeta\n\nconst DREAMINA_SEEDANCE_2_0_FAST = {\n name: 'dreamina-seedance-2-0-fast-260128',\n supports: {\n input: ['text', 'image', 'video', 'audio'],\n output: ['video', 'audio'],\n },\n} as const satisfies ModelMeta\n\nconst DREAMINA_SEEDANCE_2_0_MINI = {\n name: 'dreamina-seedance-2-0-mini-260615',\n supports: {\n input: ['text', 'image', 'video', 'audio'],\n output: ['video', 'audio'],\n },\n} as const satisfies ModelMeta\n\nconst SEEDANCE_1_5_PRO = {\n name: 'seedance-1-5-pro-251215',\n supports: {\n input: ['text', 'image'],\n output: ['video', 'audio'],\n },\n} as const satisfies ModelMeta\n\nconst SEEDANCE_1_0_PRO = {\n name: 'seedance-1-0-pro-250528',\n supports: {\n input: ['text', 'image'],\n output: ['video'],\n },\n} as const satisfies ModelMeta\n\nconst SEEDANCE_1_0_PRO_FAST = {\n name: 'seedance-1-0-pro-fast-251015',\n supports: {\n input: ['text', 'image'],\n output: ['video'],\n },\n} as const satisfies ModelMeta\n\n/**\n * All supported Seedance video model identifiers.\n */\nexport const BYTEPLUS_VIDEO_MODELS = [\n DREAMINA_SEEDANCE_2_5.name,\n DREAMINA_SEEDANCE_2_0.name,\n DREAMINA_SEEDANCE_2_0_FAST.name,\n DREAMINA_SEEDANCE_2_0_MINI.name,\n SEEDANCE_1_5_PRO.name,\n SEEDANCE_1_0_PRO.name,\n SEEDANCE_1_0_PRO_FAST.name,\n] as const\n\n/**\n * Union of all supported Seedance video model names.\n */\nexport type BytePlusVideoModel = (typeof BYTEPLUS_VIDEO_MODELS)[number]\n\n/**\n * Type-only map from video model name to the non-text prompt modalities it\n * accepts. Seedance 2.5 and the 2.0 family take multimodal references\n * (start/end frames, reference images, reference video and audio); the 1.x\n * models take start/end frames only.\n */\nexport type BytePlusVideoModelInputModalitiesByName = {\n [DREAMINA_SEEDANCE_2_5.name]: readonly ['image', 'video', 'audio']\n [DREAMINA_SEEDANCE_2_0.name]: readonly ['image', 'video', 'audio']\n [DREAMINA_SEEDANCE_2_0_FAST.name]: readonly ['image', 'video', 'audio']\n [DREAMINA_SEEDANCE_2_0_MINI.name]: readonly ['image', 'video', 'audio']\n [SEEDANCE_1_5_PRO.name]: readonly ['image']\n [SEEDANCE_1_0_PRO.name]: readonly ['image']\n [SEEDANCE_1_0_PRO_FAST.name]: readonly ['image']\n}\n\n/**\n * Type-only map from video model name to the resolutions it accepts.\n *\n * 2.0 / 1.x cells were probe-verified on 2026-07-31; 2.5 comes from the\n * public ModelArk create-task docs (2026-08-07). Note\n * `seedance-1-0-pro-fast-251015` does accept `1080p`, despite older BytePlus\n * prose listing it as 480p/720p.\n */\nexport type BytePlusVideoModelResolutionByName = {\n [DREAMINA_SEEDANCE_2_5.name]: '480p' | '720p'\n [DREAMINA_SEEDANCE_2_0.name]: '480p' | '720p' | '1080p' | '4k'\n [DREAMINA_SEEDANCE_2_0_FAST.name]: '480p' | '720p'\n [DREAMINA_SEEDANCE_2_0_MINI.name]: '480p' | '720p'\n [SEEDANCE_1_5_PRO.name]: '480p' | '720p' | '1080p'\n [SEEDANCE_1_0_PRO.name]: '480p' | '720p' | '1080p'\n [SEEDANCE_1_0_PRO_FAST.name]: '480p' | '720p' | '1080p'\n}\n\n/**\n * Type-only map from video model name to its accepted `size` strings.\n */\nexport type BytePlusVideoModelSizeByName = {\n [K in BytePlusVideoModel]: BytePlusVideoSize<\n BytePlusVideoModelResolutionByName[K]\n >\n}\n\n/**\n * A Seedance model id: one this package knows, or any other string.\n *\n * The open half is a deliberate escape hatch for models BytePlus ships between\n * releases of this package. Adding a model to {@link BYTEPLUS_VIDEO_MODELS}\n * *narrows* it — the adapter's guards switch on and reject against this\n * file's tables. For a model whose real limits are unknown that is strictly\n * worse than the open path, which lets Ark judge. So an id lands in the\n * known table only once its capability cells are documented or probed.\n *\n * Discovering ids: `GET /models` on the Ark data plane enumerates the catalog\n * (id, `task_type`, `modalities`, `status`). It is not exhaustive —\n * `seedream-5-0-lite-260128` answers requests but is missing from the\n * listing — so absence there is not evidence of absence. The ModelArk\n * release notes (https://docs.byteplus.com/en/docs/ModelArk/1159178) are the\n * other watch surface.\n *\n * To probe an id, POST `/contents/generations/tasks` with only\n * `{\"model\": \"<id>\"}`. Three outcomes, all live-verified:\n * - 400 `MissingParameter` (about `content`) — live and usable.\n * - 404 `ModelNotOpen` — real, but not activated on this account.\n * - 404 `InvalidEndpointOrModel.NotFound` — no such model.\n *\n * Unknown ids trade compile-time narrowing for reach: the full size surface is\n * accepted, provider options are ungated, and the adapter's model-specific\n * runtime guards stand down so a new model's legitimate request reaches Ark.\n * Known ids keep their documented / probe-verified narrowing.\n */\nexport type BytePlusVideoModelOrString = BytePlusVideoModel | (string & {})\n\n/**\n * Resolve the `size` type for a video model: the model's probe-verified\n * template union when known, otherwise the full template surface plus any\n * string (a future model may bring ratios or resolution tiers that do not\n * exist today).\n */\nexport type ResolveBytePlusVideoSize<TModel extends string> =\n TModel extends BytePlusVideoModel\n ? BytePlusVideoModelSizeByName[TModel]\n : BytePlusVideoSize | (string & {})\n\n/**\n * Resolve the accepted non-text prompt modalities for a video model. Unknown\n * models accept all three rather than none, so a new model's reference media\n * is not a compile error.\n */\nexport type ResolveBytePlusVideoInputModalities<TModel extends string> =\n TModel extends BytePlusVideoModel\n ? BytePlusVideoModelInputModalitiesByName[TModel]\n : readonly ['image', 'video', 'audio']\n\nconst VIDEO_MODEL_SET: ReadonlySet<string> = new Set(BYTEPLUS_VIDEO_MODELS)\n\n/**\n * True when the id is one this package has probe-verified metadata for.\n *\n * The adapter uses this to decide whether its model-specific guards apply:\n * see {@link BytePlusVideoModelOrString}.\n */\nexport function isKnownBytePlusVideoModel(\n model: string,\n): model is BytePlusVideoModel {\n return VIDEO_MODEL_SET.has(model)\n}\n\n/**\n * Per-model duration type. Seedance accepts any integer second inside the\n * model's range, so this is a continuous range expressed as `number` — a\n * literal union cannot represent it. (The API also accepts `duration: -1` on\n * Seedance 2.5, 2.0 and 1.5-pro to let the model choose; that is reachable\n * through provider options, not through the generic `duration`.)\n */\nexport type BytePlusVideoModelDurationByName = {\n [K in BytePlusVideoModel]: number\n}\n\n/**\n * Runtime duration table backing `availableDurations()` / `snapDuration()`.\n */\nexport const BYTEPLUS_VIDEO_DURATIONS: {\n readonly [TModel in BytePlusVideoModel]: DurationOptions<\n BytePlusVideoModelDurationByName[TModel]\n >\n} = {\n 'dreamina-seedance-2-5-260628': {\n kind: 'range',\n min: 4,\n max: 30,\n step: 1,\n unit: 'seconds',\n },\n 'dreamina-seedance-2-0-260128': {\n kind: 'range',\n min: 4,\n max: 15,\n step: 1,\n unit: 'seconds',\n },\n 'dreamina-seedance-2-0-fast-260128': {\n kind: 'range',\n min: 4,\n max: 15,\n step: 1,\n unit: 'seconds',\n },\n 'dreamina-seedance-2-0-mini-260615': {\n kind: 'range',\n min: 4,\n max: 15,\n step: 1,\n unit: 'seconds',\n },\n 'seedance-1-5-pro-251215': {\n kind: 'range',\n min: 4,\n max: 12,\n step: 1,\n unit: 'seconds',\n },\n 'seedance-1-0-pro-250528': {\n kind: 'range',\n min: 2,\n max: 12,\n step: 1,\n unit: 'seconds',\n },\n 'seedance-1-0-pro-fast-251015': {\n kind: 'range',\n min: 2,\n max: 12,\n step: 1,\n unit: 'seconds',\n },\n}\n\n/**\n * Duration hint for a model this package has no table for.\n *\n * Spans every range Seedance has shipped so far (2s on the 1.0 models through\n * 30s on Seedance 2.5) so `availableDurations()` can still drive a UI. It is\n * a hint, not a contract: the adapter does **not** snap an unknown model's\n * duration against it, because clamping a future model's legitimate longer\n * request down to 30 would corrupt the request rather than protect it.\n */\nexport const BYTEPLUS_VIDEO_FALLBACK_DURATIONS: DurationOptions<number> = {\n kind: 'range',\n min: 2,\n max: 30,\n step: 1,\n unit: 'seconds',\n}\n\n/**\n * Look up the duration options for a Seedance video model, falling back to\n * {@link BYTEPLUS_VIDEO_FALLBACK_DURATIONS} for an id this package does not\n * know.\n */\nexport function getBytePlusVideoDurationOptions(\n model: BytePlusVideoModelOrString,\n): DurationOptions<number> {\n return isKnownBytePlusVideoModel(model)\n ? BYTEPLUS_VIDEO_DURATIONS[model]\n : BYTEPLUS_VIDEO_FALLBACK_DURATIONS\n}\n\n// ============================================================================\n// Image models (Seedream)\n// ============================================================================\n\n/**\n * Shorthand size tokens accepted by `/images/generations`. A request uses\n * either a token or an explicit `WxH` string — never both.\n */\nexport type BytePlusImageSizeToken = '1K' | '2K' | '4K'\n\n/**\n * Accepted `size` values for Seedream models: a shorthand token or an\n * explicit pixel size such as `2048x2048`.\n */\nexport type BytePlusImageSize = BytePlusImageSizeToken | `${number}x${number}`\n\nconst DOLA_SEEDREAM_5_0_PRO = {\n name: 'dola-seedream-5-0-pro-260628',\n supports: {\n input: ['text', 'image'],\n output: ['image'],\n },\n} as const satisfies ModelMeta\n\nconst SEEDREAM_5_0 = {\n name: 'seedream-5-0-260128',\n supports: {\n input: ['text', 'image'],\n output: ['image'],\n },\n} as const satisfies ModelMeta\n\nconst SEEDREAM_5_0_LITE = {\n name: 'seedream-5-0-lite-260128',\n supports: {\n input: ['text', 'image'],\n output: ['image'],\n },\n} as const satisfies ModelMeta\n\nconst SEEDREAM_4_5 = {\n name: 'seedream-4-5-251128',\n supports: {\n input: ['text', 'image'],\n output: ['image'],\n },\n} as const satisfies ModelMeta\n\nconst SEEDREAM_4_0 = {\n name: 'seedream-4-0-250828',\n supports: {\n input: ['text', 'image'],\n output: ['image'],\n },\n} as const satisfies ModelMeta\n\n/**\n * All supported Seedream image model identifiers.\n */\nexport const BYTEPLUS_IMAGE_MODELS = [\n DOLA_SEEDREAM_5_0_PRO.name,\n SEEDREAM_5_0.name,\n SEEDREAM_5_0_LITE.name,\n SEEDREAM_4_5.name,\n SEEDREAM_4_0.name,\n] as const\n\n/**\n * Union of all supported Seedream image model names.\n */\nexport type BytePlusImageModel = (typeof BYTEPLUS_IMAGE_MODELS)[number]\n\n/**\n * Type-only map from image model name to its accepted `size` strings.\n */\nexport type BytePlusImageModelSizeByName = {\n [K in BytePlusImageModel]: BytePlusImageSize\n}\n\n/**\n * Maximum number of reference images accepted per editing request.\n * Seedream 5.0 Pro caps at 10 references; the other editing-capable models\n * accept up to 14.\n *\n * Docs-derived, not live-probed. The 14 for `seedream-5-0-260128` is weaker\n * still — the docs never state a cap for that model, so it is inferred from\n * the rest of the family.\n */\nexport const BYTEPLUS_IMAGE_MAX_REFERENCE_IMAGES: {\n readonly [K in BytePlusImageModel]: number\n} = {\n 'dola-seedream-5-0-pro-260628': 10,\n 'seedream-5-0-260128': 14,\n 'seedream-5-0-lite-260128': 14,\n 'seedream-4-5-251128': 14,\n 'seedream-4-0-250828': 14,\n}\n\n// ============================================================================\n// Seed Speech models (voice host — separate product and API key)\n// ============================================================================\n\nconst SEED_AUDIO_1_0 = {\n name: 'seed-audio-1.0',\n supports: {\n input: ['text', 'audio'],\n output: ['audio'],\n },\n} as const satisfies ModelMeta\n\n// Seed Speech ASR is endpoint-addressed: `POST /api/v3/auc/bigmodel/recognize/\n// flash` selects the model through the `X-Api-Resource-Id` header\n// (`volc.seedasr.auc_turbo`) and takes no `model` field in the body. This\n// synthetic identifier satisfies the SDK's `TranscriptionOptions.model`\n// contract and gives logging and fixture matching a stable value.\nconst SEED_ASR = {\n name: 'seed-asr',\n supports: {\n input: ['audio'],\n output: ['text'],\n },\n} as const satisfies ModelMeta\n\n/**\n * All supported Seed Speech TTS model identifiers.\n *\n * Note: TTS runs on `voice.ap-southeast-1.bytepluses.com` with an\n * `X-Api-Key` header and a *different* API key from Ark.\n */\nexport const BYTEPLUS_TTS_MODELS = [SEED_AUDIO_1_0.name] as const\n\n/**\n * All supported Seed Speech transcription model identifiers.\n */\nexport const BYTEPLUS_TRANSCRIPTION_MODELS = [SEED_ASR.name] as const\n\n/**\n * Union of all supported Seed Speech TTS model names.\n */\nexport type BytePlusTTSModel = (typeof BYTEPLUS_TTS_MODELS)[number]\n\n/**\n * Union of all supported Seed Speech transcription model names.\n */\nexport type BytePlusTranscriptionModel =\n (typeof BYTEPLUS_TRANSCRIPTION_MODELS)[number]\n\n// ============================================================================\n// Type resolution helpers\n// ============================================================================\n\n/**\n * Resolve provider options for a specific model. Models listed in the chat\n * map get their explicit options; anything else falls back to the base chat\n * options.\n */\nexport type ResolveProviderOptions<TModel extends string> =\n TModel extends keyof BytePlusChatModelProviderOptionsByName\n ? BytePlusChatModelProviderOptionsByName[TModel]\n : BytePlusTextProviderOptions\n\n/**\n * Resolve input modalities for a specific model. Models missing from the map\n * are treated as text-only.\n */\nexport type ResolveInputModalities<TModel extends string> =\n TModel extends keyof BytePlusModelInputModalitiesByName\n ? BytePlusModelInputModalitiesByName[TModel]\n : readonly ['text']\n"],"mappings":"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"}
1
+ {"version":3,"file":"model-meta.js","names":[],"sources":["../../src/model-meta.ts"],"sourcesContent":["/**\n * BytePlus ModelArk model metadata.\n *\n * Every Ark model id in this file — chat, video and image — was verified live\n * against `https://ark.ap-southeast.bytepluses.com/api/v3` on 2026-07-31. The\n * two Seed Speech ids are the exception: they live on the voice host, which\n * needs a separate key that was not available, so they are docs-derived.\n * Capability metadata is a mix of probed and docs-derived facts; anything not\n * confirmed against the live API is annotated as such at its declaration.\n * BytePlus\n * deactivates model ids aggressively (the whole `seedance-1-0-lite-*` family,\n * `seed-1-6-lite-*`, `seedream-3-0-*`, and the `doubao-`/`skylark-` names are\n * all 404s internationally), so only dated, probe-confirmed ids are shipped.\n *\n * Prefix rules, also probe-confirmed:\n * - `dola-seed-2-1-turbo-260628` and `dola-seedream-5-0-pro-260628` are the\n * canonical ids; the bare forms resolve as aliases but the API echoes the\n * prefixed id back.\n * - The Seedance 2.0 family *requires* the `dreamina-` prefix.\n * - Older models reject the `dola-` prefix outright.\n */\nimport type { DurationOptions } from '@tanstack/ai/adapters'\nimport type { BytePlusTextProviderOptions } from './text/text-provider-options'\n\n/**\n * BytePlus exposes no server-side provider tools (no hosted web search, code\n * interpreter, …) on the international Ark endpoint, so every chat model\n * advertises an empty tool set. Typing it as `never` makes passing another\n * provider's `ProviderTool` to a BytePlus adapter a compile-time error.\n */\nexport type BytePlusProviderToolKind = never\n\n/**\n * Internal metadata structure describing a BytePlus model.\n */\ninterface ModelMeta {\n name: string\n supports: {\n input: ReadonlyArray<'text' | 'image' | 'audio' | 'video' | 'document'>\n output: ReadonlyArray<'text' | 'image' | 'audio' | 'video'>\n capabilities?: ReadonlyArray<\n 'reasoning' | 'tool_calling' | 'structured_outputs'\n >\n tools?: ReadonlyArray<BytePlusProviderToolKind>\n }\n context_window?: number\n max_input_tokens?: number\n max_output_tokens?: number\n}\n\n// ============================================================================\n// Chat models (Seed / GLM / DeepSeek / gpt-oss on Ark)\n// ============================================================================\n\nconst DOLA_SEED_2_1_TURBO = {\n name: 'dola-seed-2-1-turbo-260628',\n context_window: 256_000,\n max_input_tokens: 256_000,\n max_output_tokens: 256_000,\n supports: {\n input: ['text', 'image', 'video'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling', 'structured_outputs'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_2_0_LITE_260428 = {\n name: 'seed-2-0-lite-260428',\n context_window: 256_000,\n max_input_tokens: 256_000,\n max_output_tokens: 128_000,\n supports: {\n input: ['text', 'image', 'video', 'audio'],\n output: ['text'],\n // Live-probed 2026-07-31: rejects both json_schema and json_object.\n capabilities: ['reasoning', 'tool_calling'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_2_0_MINI_260428 = {\n name: 'seed-2-0-mini-260428',\n context_window: 256_000,\n max_input_tokens: 256_000,\n max_output_tokens: 128_000,\n supports: {\n input: ['text', 'image', 'video', 'audio'],\n output: ['text'],\n // Live-probed 2026-07-31: rejects both json_schema and json_object.\n capabilities: ['reasoning', 'tool_calling'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_2_0_PRO_260328 = {\n name: 'seed-2-0-pro-260328',\n context_window: 256_000,\n max_input_tokens: 224_000,\n max_output_tokens: 128_000,\n supports: {\n input: ['text', 'image', 'video'],\n output: ['text'],\n // Live-probed 2026-07-31: accepts json_schema, despite the docs table\n // saying otherwise.\n capabilities: ['reasoning', 'tool_calling', 'structured_outputs'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_2_0_LITE_260228 = {\n name: 'seed-2-0-lite-260228',\n context_window: 256_000,\n max_input_tokens: 224_000,\n max_output_tokens: 128_000,\n supports: {\n input: ['text', 'image', 'video'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling', 'structured_outputs'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_2_0_MINI_260215 = {\n name: 'seed-2-0-mini-260215',\n context_window: 256_000,\n max_input_tokens: 224_000,\n max_output_tokens: 128_000,\n supports: {\n input: ['text', 'image', 'video'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling', 'structured_outputs'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_2_0_CODE_PREVIEW_260328 = {\n name: 'seed-2-0-code-preview-260328',\n context_window: 256_000,\n max_input_tokens: 224_000,\n max_output_tokens: 128_000,\n supports: {\n input: ['text', 'image', 'video'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_1_8_251228 = {\n name: 'seed-1-8-251228',\n context_window: 256_000,\n max_input_tokens: 224_000,\n max_output_tokens: 64_000,\n supports: {\n input: ['text', 'image', 'video'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling', 'structured_outputs'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_1_6_250915 = {\n name: 'seed-1-6-250915',\n context_window: 256_000,\n max_input_tokens: 224_000,\n max_output_tokens: 32_000,\n supports: {\n input: ['text', 'image', 'video'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling', 'structured_outputs'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_1_6_250615 = {\n name: 'seed-1-6-250615',\n context_window: 256_000,\n max_input_tokens: 224_000,\n max_output_tokens: 32_000,\n supports: {\n input: ['text', 'image', 'video'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling', 'structured_outputs'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_1_6_FLASH_250715 = {\n name: 'seed-1-6-flash-250715',\n context_window: 256_000,\n max_input_tokens: 224_000,\n max_output_tokens: 32_000,\n supports: {\n input: ['text', 'image', 'video'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling', 'structured_outputs'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst SEED_1_6_FLASH_250615 = {\n name: 'seed-1-6-flash-250615',\n context_window: 256_000,\n max_input_tokens: 224_000,\n max_output_tokens: 32_000,\n supports: {\n input: ['text', 'image', 'video'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling', 'structured_outputs'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst GLM_5_2_260617 = {\n name: 'glm-5-2-260617',\n context_window: 1_024_000,\n max_input_tokens: 1_024_000,\n max_output_tokens: 128_000,\n supports: {\n input: ['text'],\n output: ['text'],\n // Live-probed 2026-07-31: accepts json_schema, despite the docs table\n // saying otherwise.\n capabilities: ['reasoning', 'tool_calling', 'structured_outputs'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst GLM_4_7_251222 = {\n name: 'glm-4-7-251222',\n context_window: 256_000,\n max_input_tokens: 224_000,\n max_output_tokens: 128_000,\n supports: {\n input: ['text'],\n output: ['text'],\n // Adherence-probed 2026-07-31: ACCEPTS a json_schema with 200 but ignores\n // it and answers in prose, so it is not a structured-output model. A\n // status-code-only probe reads this as support — see the note on\n // BYTEPLUS_STRUCTURED_OUTPUT_CHAT_MODELS.\n capabilities: ['reasoning', 'tool_calling'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst DEEPSEEK_V4_PRO_260425 = {\n name: 'deepseek-v4-pro-260425',\n context_window: 1_024_000,\n max_input_tokens: 1_024_000,\n max_output_tokens: 384_000,\n supports: {\n input: ['text'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\nconst DEEPSEEK_V4_FLASH_260425 = {\n name: 'deepseek-v4-flash-260425',\n context_window: 1_024_000,\n max_input_tokens: 1_024_000,\n max_output_tokens: 384_000,\n supports: {\n input: ['text'],\n output: ['text'],\n capabilities: ['reasoning', 'tool_calling'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\n// The one model on Ark that defaults to `thinking: disabled`.\nconst DEEPSEEK_V3_2_251201 = {\n name: 'deepseek-v3-2-251201',\n context_window: 128_000,\n max_input_tokens: 128_000,\n max_output_tokens: 32_000,\n supports: {\n input: ['text'],\n output: ['text'],\n // Live-probed 2026-07-31: rejects both json_schema and json_object.\n capabilities: ['reasoning', 'tool_calling'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\n// The only model accepting `thinking: {type: 'auto'}`. Tool calling is\n// undocumented on Ark and unverified, so it is not advertised.\nconst GPT_OSS_120B_250805 = {\n name: 'gpt-oss-120b-250805',\n context_window: 128_000,\n max_input_tokens: 96_000,\n max_output_tokens: 64_000,\n supports: {\n input: ['text'],\n output: ['text'],\n capabilities: ['reasoning'],\n tools: [] as const,\n },\n} as const satisfies ModelMeta\n\n/**\n * All supported BytePlus chat model identifiers.\n */\nexport const BYTEPLUS_CHAT_MODELS = [\n DOLA_SEED_2_1_TURBO.name,\n SEED_2_0_LITE_260428.name,\n SEED_2_0_MINI_260428.name,\n SEED_2_0_PRO_260328.name,\n SEED_2_0_LITE_260228.name,\n SEED_2_0_MINI_260215.name,\n SEED_2_0_CODE_PREVIEW_260328.name,\n SEED_1_8_251228.name,\n SEED_1_6_250915.name,\n SEED_1_6_250615.name,\n SEED_1_6_FLASH_250715.name,\n SEED_1_6_FLASH_250615.name,\n GLM_5_2_260617.name,\n GLM_4_7_251222.name,\n DEEPSEEK_V4_PRO_260425.name,\n DEEPSEEK_V4_FLASH_260425.name,\n DEEPSEEK_V3_2_251201.name,\n GPT_OSS_120B_250805.name,\n] as const\n\n/**\n * Union of all supported BytePlus chat model names.\n */\nexport type BytePlusChatModel = (typeof BYTEPLUS_CHAT_MODELS)[number]\n\n/**\n * Chat models that emit a `encrypted_content` blob alongside\n * `reasoning_content` when thinking is enabled (\"thinking summary\" models).\n *\n * The blob is an opaque signature over the reasoning trace: when it is\n * present it must be echoed back verbatim on the assistant message in the\n * next turn. Live probing showed omitting it did *not* fail a simple tool\n * round-trip, so adapters preserve and replay it when present but must never\n * treat its absence as an error.\n */\nexport const BYTEPLUS_THINKING_SUMMARY_MODELS = [\n DOLA_SEED_2_1_TURBO.name,\n SEED_2_0_LITE_260428.name,\n SEED_2_0_MINI_260428.name,\n SEED_2_0_PRO_260328.name,\n] as const\n\n/**\n * Union of chat models that emit `encrypted_content`.\n */\nexport type BytePlusThinkingSummaryModel =\n (typeof BYTEPLUS_THINKING_SUMMARY_MODELS)[number]\n\nconst THINKING_SUMMARY_MODEL_SET: ReadonlySet<string> = new Set(\n BYTEPLUS_THINKING_SUMMARY_MODELS,\n)\n\n/**\n * True when the model emits `encrypted_content` that should be round-tripped\n * on subsequent turns.\n */\nexport function emitsEncryptedContent(model: string): boolean {\n return THINKING_SUMMARY_MODEL_SET.has(model)\n}\n\n/**\n * Chat models that accept `response_format: {type: 'json_schema'}`.\n *\n * Live-probed against all 18 chat models on 2026-07-31, not docs-derived — the\n * BytePlus capability tables are wrong here in both directions.\n *\n * Membership needs TWO things, because the API has both failure modes:\n * 1. The request is accepted. Seven models (`seed-2-0-lite-260428`,\n * `seed-2-0-mini-260428`, `seed-2-0-code-preview-260328`, both\n * `deepseek-v4-*`, `deepseek-v3-2-251201`, `gpt-oss-120b-250805`) answer a\n * JSON schema with 400 InvalidParameter — and reject\n * `{type: 'json_object'}` too, so there is no JSON-mode fallback.\n * 2. The schema is actually honoured. `glm-4-7-251222` accepts the request\n * with 200 and then ignores the schema, answering in prose (reproduced\n * twice by the adherence probe). A status-code-only probe wrongly reads\n * that as support, so it is excluded.\n *\n * Models that fail either check need tool-shaped extraction instead.\n *\n * Note that the default chat model `seed-2-0-lite-260428` is one of the\n * rejecting models: structured-output work needs `seed-2-0-lite-260228` or\n * `dola-seed-2-1-turbo-260628`.\n */\nexport const BYTEPLUS_STRUCTURED_OUTPUT_CHAT_MODELS = [\n DOLA_SEED_2_1_TURBO.name,\n SEED_2_0_PRO_260328.name,\n SEED_2_0_LITE_260228.name,\n SEED_2_0_MINI_260215.name,\n SEED_1_8_251228.name,\n SEED_1_6_250915.name,\n SEED_1_6_250615.name,\n SEED_1_6_FLASH_250715.name,\n SEED_1_6_FLASH_250615.name,\n GLM_5_2_260617.name,\n] as const\n\nconst STRUCTURED_OUTPUT_MODEL_SET: ReadonlySet<string> = new Set(\n BYTEPLUS_STRUCTURED_OUTPUT_CHAT_MODELS,\n)\n\n/**\n * True when the model supports native JSON-schema structured output.\n */\nexport function supportsStructuredOutput(model: string): boolean {\n return STRUCTURED_OUTPUT_MODEL_SET.has(model)\n}\n\n/**\n * Type-only map from chat model name to whether it supports native\n * JSON-schema structured output.\n */\nexport type BytePlusChatModelStructuredOutputByName = {\n [K in BytePlusChatModel]: K extends BytePlusStructuredOutputChatModel\n ? true\n : false\n}\n\n/**\n * Union of chat models supporting native JSON-schema structured output.\n */\nexport type BytePlusStructuredOutputChatModel =\n (typeof BYTEPLUS_STRUCTURED_OUTPUT_CHAT_MODELS)[number]\n\n/**\n * Type-only map from chat model name to its supported input modalities.\n * Used for type inference when constructing multimodal messages.\n */\nexport type BytePlusModelInputModalitiesByName = {\n [DOLA_SEED_2_1_TURBO.name]: typeof DOLA_SEED_2_1_TURBO.supports.input\n [SEED_2_0_LITE_260428.name]: typeof SEED_2_0_LITE_260428.supports.input\n [SEED_2_0_MINI_260428.name]: typeof SEED_2_0_MINI_260428.supports.input\n [SEED_2_0_PRO_260328.name]: typeof SEED_2_0_PRO_260328.supports.input\n [SEED_2_0_LITE_260228.name]: typeof SEED_2_0_LITE_260228.supports.input\n [SEED_2_0_MINI_260215.name]: typeof SEED_2_0_MINI_260215.supports.input\n [SEED_2_0_CODE_PREVIEW_260328.name]: typeof SEED_2_0_CODE_PREVIEW_260328.supports.input\n [SEED_1_8_251228.name]: typeof SEED_1_8_251228.supports.input\n [SEED_1_6_250915.name]: typeof SEED_1_6_250915.supports.input\n [SEED_1_6_250615.name]: typeof SEED_1_6_250615.supports.input\n [SEED_1_6_FLASH_250715.name]: typeof SEED_1_6_FLASH_250715.supports.input\n [SEED_1_6_FLASH_250615.name]: typeof SEED_1_6_FLASH_250615.supports.input\n [GLM_5_2_260617.name]: typeof GLM_5_2_260617.supports.input\n [GLM_4_7_251222.name]: typeof GLM_4_7_251222.supports.input\n [DEEPSEEK_V4_PRO_260425.name]: typeof DEEPSEEK_V4_PRO_260425.supports.input\n [DEEPSEEK_V4_FLASH_260425.name]: typeof DEEPSEEK_V4_FLASH_260425.supports.input\n [DEEPSEEK_V3_2_251201.name]: typeof DEEPSEEK_V3_2_251201.supports.input\n [GPT_OSS_120B_250805.name]: typeof GPT_OSS_120B_250805.supports.input\n}\n\n/**\n * Type-only map from chat model name to its supported provider tools.\n * BytePlus exposes no provider-tool factories, so every model gets an empty\n * tuple — passing another provider's tool is then a compile-time error.\n */\nexport type BytePlusChatModelToolCapabilitiesByName = {\n [K in BytePlusChatModel]: ReadonlyArray<BytePlusProviderToolKind>\n}\n\n/**\n * Type-only map from chat model name to its provider options type.\n */\nexport type BytePlusChatModelProviderOptionsByName = {\n [K in BytePlusChatModel]: BytePlusTextProviderOptions\n}\n\n// ============================================================================\n// Video models (Seedance, async task API)\n// ============================================================================\n\n/**\n * Output aspect ratios accepted by the Seedance task API. `adaptive` is only\n * meaningful for image-to-video, where the ratio follows the input frame.\n */\nexport type BytePlusVideoRatio =\n | '16:9'\n | '9:16'\n | '4:3'\n | '3:4'\n | '1:1'\n | '21:9'\n | 'adaptive'\n\n/**\n * Resolution tiers accepted by the Seedance task API.\n *\n * Resolution tiers are model-specific (see\n * {@link BytePlusVideoModelResolutionByName}). Two findings that still\n * contradict older BytePlus prose: there is **no 2K tier on any Seedance\n * model**, and `4k` exists only on `dreamina-seedance-2-0-260128`. Seedance\n * 2.5 accepts 480p/720p/1080p (ModelArk + fal spec, 2026-08-19).\n *\n * The API matches this field case-insensitively (`4K`, `4k` and `1080P` are\n * all accepted), so this package standardizes on the lowercase spelling.\n */\nexport type BytePlusVideoResolution = '480p' | '720p' | '1080p' | '4k'\n\n/**\n * Generic `size` template for Seedance models: either a bare aspect ratio\n * (\"16:9\") or `ratio_resolution` (\"16:9_720p\"). The Seedance API takes the\n * two as separate `ratio` / `resolution` fields; the adapter splits this\n * template back apart.\n */\nexport type BytePlusVideoSize<\n TResolution extends BytePlusVideoResolution = BytePlusVideoResolution,\n> = BytePlusVideoRatio | `${BytePlusVideoRatio}_${TResolution}`\n\n// Multimodal reference-media capabilities (reference images / video / audio)\n// are docs-derived from the ModelArk create-task page. Model ids and the\n// resolution / duration tables for 2.0 were also live-probed on 2026-07-31;\n// 2.5 lands from the public docs once the model was fully opened (2026-08-07).\nconst DREAMINA_SEEDANCE_2_5 = {\n name: 'dreamina-seedance-2-5-260628',\n supports: {\n input: ['text', 'image', 'video', 'audio'],\n output: ['video', 'audio'],\n },\n} as const satisfies ModelMeta\n\nconst DREAMINA_SEEDANCE_2_0 = {\n name: 'dreamina-seedance-2-0-260128',\n supports: {\n input: ['text', 'image', 'video', 'audio'],\n output: ['video', 'audio'],\n },\n} as const satisfies ModelMeta\n\nconst DREAMINA_SEEDANCE_2_0_FAST = {\n name: 'dreamina-seedance-2-0-fast-260128',\n supports: {\n input: ['text', 'image', 'video', 'audio'],\n output: ['video', 'audio'],\n },\n} as const satisfies ModelMeta\n\nconst DREAMINA_SEEDANCE_2_0_MINI = {\n name: 'dreamina-seedance-2-0-mini-260615',\n supports: {\n input: ['text', 'image', 'video', 'audio'],\n output: ['video', 'audio'],\n },\n} as const satisfies ModelMeta\n\nconst SEEDANCE_1_5_PRO = {\n name: 'seedance-1-5-pro-251215',\n supports: {\n input: ['text', 'image'],\n output: ['video', 'audio'],\n },\n} as const satisfies ModelMeta\n\nconst SEEDANCE_1_0_PRO = {\n name: 'seedance-1-0-pro-250528',\n supports: {\n input: ['text', 'image'],\n output: ['video'],\n },\n} as const satisfies ModelMeta\n\nconst SEEDANCE_1_0_PRO_FAST = {\n name: 'seedance-1-0-pro-fast-251015',\n supports: {\n input: ['text', 'image'],\n output: ['video'],\n },\n} as const satisfies ModelMeta\n\n/**\n * All supported Seedance video model identifiers.\n */\nexport const BYTEPLUS_VIDEO_MODELS = [\n DREAMINA_SEEDANCE_2_5.name,\n DREAMINA_SEEDANCE_2_0.name,\n DREAMINA_SEEDANCE_2_0_FAST.name,\n DREAMINA_SEEDANCE_2_0_MINI.name,\n SEEDANCE_1_5_PRO.name,\n SEEDANCE_1_0_PRO.name,\n SEEDANCE_1_0_PRO_FAST.name,\n] as const\n\n/**\n * Union of all supported Seedance video model names.\n */\nexport type BytePlusVideoModel = (typeof BYTEPLUS_VIDEO_MODELS)[number]\n\n/**\n * Type-only map from video model name to the non-text prompt modalities it\n * accepts. Seedance 2.5 and the 2.0 family take multimodal references\n * (start/end frames, reference images, reference video and audio); the 1.x\n * models take start/end frames only.\n */\nexport type BytePlusVideoModelInputModalitiesByName = {\n [DREAMINA_SEEDANCE_2_5.name]: readonly ['image', 'video', 'audio']\n [DREAMINA_SEEDANCE_2_0.name]: readonly ['image', 'video', 'audio']\n [DREAMINA_SEEDANCE_2_0_FAST.name]: readonly ['image', 'video', 'audio']\n [DREAMINA_SEEDANCE_2_0_MINI.name]: readonly ['image', 'video', 'audio']\n [SEEDANCE_1_5_PRO.name]: readonly ['image']\n [SEEDANCE_1_0_PRO.name]: readonly ['image']\n [SEEDANCE_1_0_PRO_FAST.name]: readonly ['image']\n}\n\n/**\n * Type-only map from video model name to the resolutions it accepts.\n *\n * 2.0 / 1.x cells were probe-verified on 2026-07-31; 2.5 comes from the\n * public ModelArk create-task docs, refreshed 2026-08-19 when native 1080p\n * shipped. Note `seedance-1-0-pro-fast-251015` does accept `1080p`, despite\n * older BytePlus prose listing it as 480p/720p.\n */\nexport type BytePlusVideoModelResolutionByName = {\n [DREAMINA_SEEDANCE_2_5.name]: '480p' | '720p' | '1080p'\n [DREAMINA_SEEDANCE_2_0.name]: '480p' | '720p' | '1080p' | '4k'\n [DREAMINA_SEEDANCE_2_0_FAST.name]: '480p' | '720p'\n [DREAMINA_SEEDANCE_2_0_MINI.name]: '480p' | '720p'\n [SEEDANCE_1_5_PRO.name]: '480p' | '720p' | '1080p'\n [SEEDANCE_1_0_PRO.name]: '480p' | '720p' | '1080p'\n [SEEDANCE_1_0_PRO_FAST.name]: '480p' | '720p' | '1080p'\n}\n\n/**\n * Type-only map from video model name to its accepted `size` strings.\n */\nexport type BytePlusVideoModelSizeByName = {\n [K in BytePlusVideoModel]: BytePlusVideoSize<\n BytePlusVideoModelResolutionByName[K]\n >\n}\n\n/**\n * A Seedance model id: one this package knows, or any other string.\n *\n * The open half is a deliberate escape hatch for models BytePlus ships between\n * releases of this package. Adding a model to {@link BYTEPLUS_VIDEO_MODELS}\n * *narrows* it — the adapter's guards switch on and reject against this\n * file's tables. For a model whose real limits are unknown that is strictly\n * worse than the open path, which lets Ark judge. So an id lands in the\n * known table only once its capability cells are documented or probed.\n *\n * Discovering ids: `GET /models` on the Ark data plane enumerates the catalog\n * (id, `task_type`, `modalities`, `status`). It is not exhaustive —\n * `seedream-5-0-lite-260128` answers requests but is missing from the\n * listing — so absence there is not evidence of absence. The ModelArk\n * release notes (https://docs.byteplus.com/en/docs/ModelArk/1159178) are the\n * other watch surface.\n *\n * To probe an id, POST `/contents/generations/tasks` with only\n * `{\"model\": \"<id>\"}`. Three outcomes, all live-verified:\n * - 400 `MissingParameter` (about `content`) — live and usable.\n * - 404 `ModelNotOpen` — real, but not activated on this account.\n * - 404 `InvalidEndpointOrModel.NotFound` — no such model.\n *\n * Unknown ids trade compile-time narrowing for reach: the full size surface is\n * accepted, provider options are ungated, and the adapter's model-specific\n * runtime guards stand down so a new model's legitimate request reaches Ark.\n * Known ids keep their documented / probe-verified narrowing.\n */\nexport type BytePlusVideoModelOrString = BytePlusVideoModel | (string & {})\n\n/**\n * Resolve the `size` type for a video model: the model's probe-verified\n * template union when known, otherwise the full template surface plus any\n * string (a future model may bring ratios or resolution tiers that do not\n * exist today).\n */\nexport type ResolveBytePlusVideoSize<TModel extends string> =\n TModel extends BytePlusVideoModel\n ? BytePlusVideoModelSizeByName[TModel]\n : BytePlusVideoSize | (string & {})\n\n/**\n * Resolve the accepted non-text prompt modalities for a video model. Unknown\n * models accept all three rather than none, so a new model's reference media\n * is not a compile error.\n */\nexport type ResolveBytePlusVideoInputModalities<TModel extends string> =\n TModel extends BytePlusVideoModel\n ? BytePlusVideoModelInputModalitiesByName[TModel]\n : readonly ['image', 'video', 'audio']\n\nconst VIDEO_MODEL_SET: ReadonlySet<string> = new Set(BYTEPLUS_VIDEO_MODELS)\n\n/**\n * True when the id is one this package has probe-verified metadata for.\n *\n * The adapter uses this to decide whether its model-specific guards apply:\n * see {@link BytePlusVideoModelOrString}.\n */\nexport function isKnownBytePlusVideoModel(\n model: string,\n): model is BytePlusVideoModel {\n return VIDEO_MODEL_SET.has(model)\n}\n\n/**\n * Per-model duration type. Seedance accepts any integer second inside the\n * model's range, so this is a continuous range expressed as `number` — a\n * literal union cannot represent it. (The API also accepts `duration: -1` on\n * Seedance 2.5, 2.0 and 1.5-pro to let the model choose; that is reachable\n * through provider options, not through the generic `duration`.)\n */\nexport type BytePlusVideoModelDurationByName = {\n [K in BytePlusVideoModel]: number\n}\n\n/**\n * Runtime duration table backing `availableDurations()` / `snapDuration()`.\n */\nexport const BYTEPLUS_VIDEO_DURATIONS: {\n readonly [TModel in BytePlusVideoModel]: DurationOptions<\n BytePlusVideoModelDurationByName[TModel]\n >\n} = {\n 'dreamina-seedance-2-5-260628': {\n kind: 'range',\n min: 4,\n max: 30,\n step: 1,\n unit: 'seconds',\n },\n 'dreamina-seedance-2-0-260128': {\n kind: 'range',\n min: 4,\n max: 15,\n step: 1,\n unit: 'seconds',\n },\n 'dreamina-seedance-2-0-fast-260128': {\n kind: 'range',\n min: 4,\n max: 15,\n step: 1,\n unit: 'seconds',\n },\n 'dreamina-seedance-2-0-mini-260615': {\n kind: 'range',\n min: 4,\n max: 15,\n step: 1,\n unit: 'seconds',\n },\n 'seedance-1-5-pro-251215': {\n kind: 'range',\n min: 4,\n max: 12,\n step: 1,\n unit: 'seconds',\n },\n 'seedance-1-0-pro-250528': {\n kind: 'range',\n min: 2,\n max: 12,\n step: 1,\n unit: 'seconds',\n },\n 'seedance-1-0-pro-fast-251015': {\n kind: 'range',\n min: 2,\n max: 12,\n step: 1,\n unit: 'seconds',\n },\n}\n\n/**\n * Duration hint for a model this package has no table for.\n *\n * Spans every range Seedance has shipped so far (2s on the 1.0 models through\n * 30s on Seedance 2.5) so `availableDurations()` can still drive a UI. It is\n * a hint, not a contract: the adapter does **not** snap an unknown model's\n * duration against it, because clamping a future model's legitimate longer\n * request down to 30 would corrupt the request rather than protect it.\n */\nexport const BYTEPLUS_VIDEO_FALLBACK_DURATIONS: DurationOptions<number> = {\n kind: 'range',\n min: 2,\n max: 30,\n step: 1,\n unit: 'seconds',\n}\n\n/**\n * Look up the duration options for a Seedance video model, falling back to\n * {@link BYTEPLUS_VIDEO_FALLBACK_DURATIONS} for an id this package does not\n * know.\n */\nexport function getBytePlusVideoDurationOptions(\n model: BytePlusVideoModelOrString,\n): DurationOptions<number> {\n return isKnownBytePlusVideoModel(model)\n ? BYTEPLUS_VIDEO_DURATIONS[model]\n : BYTEPLUS_VIDEO_FALLBACK_DURATIONS\n}\n\n// ============================================================================\n// Image models (Seedream)\n// ============================================================================\n\n/**\n * Shorthand size tokens accepted by `/images/generations`. A request uses\n * either a token or an explicit `WxH` string — never both.\n */\nexport type BytePlusImageSizeToken = '1K' | '2K' | '4K'\n\n/**\n * Accepted `size` values for Seedream models: a shorthand token or an\n * explicit pixel size such as `2048x2048`.\n */\nexport type BytePlusImageSize = BytePlusImageSizeToken | `${number}x${number}`\n\nconst DOLA_SEEDREAM_5_0_PRO = {\n name: 'dola-seedream-5-0-pro-260628',\n supports: {\n input: ['text', 'image'],\n output: ['image'],\n },\n} as const satisfies ModelMeta\n\nconst SEEDREAM_5_0 = {\n name: 'seedream-5-0-260128',\n supports: {\n input: ['text', 'image'],\n output: ['image'],\n },\n} as const satisfies ModelMeta\n\nconst SEEDREAM_5_0_LITE = {\n name: 'seedream-5-0-lite-260128',\n supports: {\n input: ['text', 'image'],\n output: ['image'],\n },\n} as const satisfies ModelMeta\n\nconst SEEDREAM_4_5 = {\n name: 'seedream-4-5-251128',\n supports: {\n input: ['text', 'image'],\n output: ['image'],\n },\n} as const satisfies ModelMeta\n\nconst SEEDREAM_4_0 = {\n name: 'seedream-4-0-250828',\n supports: {\n input: ['text', 'image'],\n output: ['image'],\n },\n} as const satisfies ModelMeta\n\n/**\n * All supported Seedream image model identifiers.\n */\nexport const BYTEPLUS_IMAGE_MODELS = [\n DOLA_SEEDREAM_5_0_PRO.name,\n SEEDREAM_5_0.name,\n SEEDREAM_5_0_LITE.name,\n SEEDREAM_4_5.name,\n SEEDREAM_4_0.name,\n] as const\n\n/**\n * Union of all supported Seedream image model names.\n */\nexport type BytePlusImageModel = (typeof BYTEPLUS_IMAGE_MODELS)[number]\n\n/**\n * Type-only map from image model name to its accepted `size` strings.\n */\nexport type BytePlusImageModelSizeByName = {\n [K in BytePlusImageModel]: BytePlusImageSize\n}\n\n/**\n * Maximum number of reference images accepted per editing request.\n * Seedream 5.0 Pro caps at 10 references; the other editing-capable models\n * accept up to 14.\n *\n * Docs-derived, not live-probed. The 14 for `seedream-5-0-260128` is weaker\n * still — the docs never state a cap for that model, so it is inferred from\n * the rest of the family.\n */\nexport const BYTEPLUS_IMAGE_MAX_REFERENCE_IMAGES: {\n readonly [K in BytePlusImageModel]: number\n} = {\n 'dola-seedream-5-0-pro-260628': 10,\n 'seedream-5-0-260128': 14,\n 'seedream-5-0-lite-260128': 14,\n 'seedream-4-5-251128': 14,\n 'seedream-4-0-250828': 14,\n}\n\n// ============================================================================\n// Seed Speech models (voice host — separate product and API key)\n// ============================================================================\n\nconst SEED_AUDIO_1_0 = {\n name: 'seed-audio-1.0',\n supports: {\n input: ['text', 'audio'],\n output: ['audio'],\n },\n} as const satisfies ModelMeta\n\n// Seed Speech ASR is endpoint-addressed: `POST /api/v3/auc/bigmodel/recognize/\n// flash` selects the model through the `X-Api-Resource-Id` header\n// (`volc.seedasr.auc_turbo`) and takes no `model` field in the body. This\n// synthetic identifier satisfies the SDK's `TranscriptionOptions.model`\n// contract and gives logging and fixture matching a stable value.\nconst SEED_ASR = {\n name: 'seed-asr',\n supports: {\n input: ['audio'],\n output: ['text'],\n },\n} as const satisfies ModelMeta\n\n/**\n * All supported Seed Speech TTS model identifiers.\n *\n * Note: TTS runs on `voice.ap-southeast-1.bytepluses.com` with an\n * `X-Api-Key` header and a *different* API key from Ark.\n */\nexport const BYTEPLUS_TTS_MODELS = [SEED_AUDIO_1_0.name] as const\n\n/**\n * All supported Seed Speech transcription model identifiers.\n */\nexport const BYTEPLUS_TRANSCRIPTION_MODELS = [SEED_ASR.name] as const\n\n/**\n * Union of all supported Seed Speech TTS model names.\n */\nexport type BytePlusTTSModel = (typeof BYTEPLUS_TTS_MODELS)[number]\n\n/**\n * Union of all supported Seed Speech transcription model names.\n */\nexport type BytePlusTranscriptionModel =\n (typeof BYTEPLUS_TRANSCRIPTION_MODELS)[number]\n\n// ============================================================================\n// Type resolution helpers\n// ============================================================================\n\n/**\n * Resolve provider options for a specific model. Models listed in the chat\n * map get their explicit options; anything else falls back to the base chat\n * options.\n */\nexport type ResolveProviderOptions<TModel extends string> =\n TModel extends keyof BytePlusChatModelProviderOptionsByName\n ? BytePlusChatModelProviderOptionsByName[TModel]\n : BytePlusTextProviderOptions\n\n/**\n * Resolve input modalities for a specific model. Models missing from the map\n * are treated as text-only.\n */\nexport type ResolveInputModalities<TModel extends string> =\n TModel extends keyof BytePlusModelInputModalitiesByName\n ? BytePlusModelInputModalitiesByName[TModel]\n : readonly ['text']\n"],"mappings":"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"}
@@ -47,8 +47,8 @@ export interface BytePlusVideoProviderOptions {
47
47
  /**
48
48
  * Output resolution tier. Overrides the resolution half of the generic
49
49
  * `size`. Matched case-insensitively by the API; this package uses
50
- * lowercase throughout. `4k` exists only on `dreamina-seedance-2-0-260128`
51
- * (Seedance 2.5 is 480p/720p only), and there is no 2K tier on any model.
50
+ * lowercase throughout. `4k` exists only on `dreamina-seedance-2-0-260128`,
51
+ * and there is no 2K tier on any model.
52
52
  */
53
53
  resolution?: BytePlusVideoResolution;
54
54
  /**
@@ -11,8 +11,8 @@ import { isKnownBytePlusVideoModel } from "../model-meta.js";
11
11
  * an error naming the field under test means "rejected", an error naming
12
12
  * `seed` means "accepted". Ark reports only one arbitrary invalid parameter
13
13
  * per request, so each cell was retried until a verdict repeated. Seedance 2.5
14
- * cells come from the public ModelArk create-task docs once the model was
15
- * fully opened (2026-08-07).
14
+ * cells come from the public ModelArk create-task docs (opened 2026-08-07);
15
+ * its resolution row was refreshed 2026-08-19 when native 1080p shipped.
16
16
  *
17
17
  * Ark rejects an inapplicable field outright — "the specified parameter
18
18
  * `draft` is not supported for model seedance-1-0-pro in t2v, must be empty" —
@@ -52,15 +52,20 @@ var BYTEPLUS_VIDEO_RATIOS = [
52
52
  /**
53
53
  * Resolutions each model accepts.
54
54
  *
55
- * 2.0 / 1.x cells were live-probed; 2.5 comes from the public ModelArk docs.
56
- * Two findings still contradict older prose: there is no 2K tier on any
57
- * Seedance model (`2k`/`2K` is rejected everywhere), and
55
+ * 2.0 / 1.x cells were live-probed; 2.5 comes from the public ModelArk docs
56
+ * and the live fal Seedance 2.5 spec (modelschemas.com, 2026-08-19). Two
57
+ * findings still contradict older prose: there is no 2K tier on any Seedance
58
+ * model (`2k`/`2K` is rejected everywhere), and
58
59
  * `seedance-1-0-pro-fast-251015` does accept `1080p` despite being documented
59
- * as 480p/720p only. Seedance 2.5 is 480p/720p only — it does **not** offer
60
+ * as 480p/720p only. Seedance 2.5 is 480p/720p/1080p — it does **not** offer
60
61
  * the 2.0 flagship's 4k tier.
61
62
  */
62
63
  var BYTEPLUS_VIDEO_RESOLUTIONS = {
63
- "dreamina-seedance-2-5-260628": ["480p", "720p"],
64
+ "dreamina-seedance-2-5-260628": [
65
+ "480p",
66
+ "720p",
67
+ "1080p"
68
+ ],
64
69
  "dreamina-seedance-2-0-260128": [
65
70
  "480p",
66
71
  "720p",
@@ -1 +1 @@
1
- {"version":3,"file":"video-provider-options.js","names":[],"sources":["../../../src/video/video-provider-options.ts"],"sourcesContent":["/**\n * Provider options and per-model capability tables for the BytePlus Seedance\n * video models.\n *\n * Applicability for Seedance 1.x / 2.0 was probed live against\n * `https://ark.ap-southeast.bytepluses.com/api/v3` on 2026-07-31. The probe\n * sent an out-of-range `seed` alongside the field under test, so requests that\n * passed validation still failed before a task was created (nothing billed):\n * an error naming the field under test means \"rejected\", an error naming\n * `seed` means \"accepted\". Ark reports only one arbitrary invalid parameter\n * per request, so each cell was retried until a verdict repeated. Seedance 2.5\n * cells come from the public ModelArk create-task docs once the model was\n * fully opened (2026-08-07).\n *\n * Ark rejects an inapplicable field outright — \"the specified parameter\n * `draft` is not supported for model seedance-1-0-pro in t2v, must be empty\" —\n * so these tables are not cosmetic: sending a field to the wrong model is a\n * 400, not a no-op.\n *\n * **Where the adapter guards, and where it doesn't** (deliberate, not an\n * oversight). Scalar applicability — `service_tier`, `draft`, `priority`,\n * `frames`, `camera_fixed`, `output_format` — is left to Ark, whose 400 names\n * the offending field and the model precisely enough to act on, and whose\n * per-model rules shift as BytePlus ships models. Duplicating that here would\n * mean a table that silently goes stale and starts rejecting requests the API\n * would have accepted. The adapter guards locally only where the API's own\n * error is misleading or arrives too late to be actionable: prompt media shape\n * (role vocabulary, frame-vs-reference exclusivity, frame cardinality,\n * audio-only reference) and the resolution tier, both of which are derived\n * from a caller's `prompt` / `size` rather than passed through verbatim.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\n\nimport { isKnownBytePlusVideoModel } from '../model-meta'\nimport type {\n BytePlusVideoModel,\n BytePlusVideoModelOrString,\n BytePlusVideoRatio,\n BytePlusVideoResolution,\n} from '../model-meta'\n\n/**\n * Inference queue for the request.\n *\n * - `default` — online inference: lower RPM and concurrency quotas, lowest\n * latency.\n * - `flex` — offline batch inference: higher daily token quotas at half the\n * price, with no latency guarantee. Task ids come back with a `cgt-batch-`\n * prefix (live-verified).\n *\n * Only the Seedance 1.x models accept this field. Seedance 2.5 and the 2.0\n * family reject it (\"service_tier is not supported … must be empty\" / \"not\n * currently supported\").\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type BytePlusVideoServiceTier = 'default' | 'flex'\n\n/**\n * Container format of the generated video.\n *\n * Seedance 2.5 documents `mp4` (default) and `mov`. Other models historically\n * return `mp4` only; scalar applicability is left to Ark (see file header).\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type BytePlusVideoOutputFormat = 'mp4' | 'mov'\n\n/**\n * Provider-specific options for Seedance video generation. These map one-to-one\n * onto the create-task request body and take precedence over the values the\n * adapter derives from the generic `size` / `duration` options.\n *\n * Fields are model-dependent; each one documents where it applies. Passing a\n * field to a model that does not accept it is a 400 from Ark, not a silent\n * ignore.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport interface BytePlusVideoProviderOptions {\n /**\n * Output aspect ratio. Overrides the ratio half of the generic `size`.\n *\n * `adaptive` (follow the input frame) is the default on Seedance 2.5, 2.0\n * and 1.5-pro but is rejected by Seedance 1.0-pro / 1.0-pro-fast for\n * text-to-video.\n */\n ratio?: BytePlusVideoRatio\n\n /**\n * Output resolution tier. Overrides the resolution half of the generic\n * `size`. Matched case-insensitively by the API; this package uses\n * lowercase throughout. `4k` exists only on `dreamina-seedance-2-0-260128`\n * (Seedance 2.5 is 480p/720p only), and there is no 2K tier on any model.\n */\n resolution?: BytePlusVideoResolution\n\n /**\n * Whole seconds of output. Overrides the generic `duration`, and unlike it\n * is sent verbatim rather than snapped into the model's range.\n *\n * `-1` asks the model to choose its own length; accepted by Seedance 2.5,\n * 2.0 and 1.5-pro only. On 2.5 video-editing tasks, `-1` is required.\n */\n duration?: number\n\n /**\n * Frame count instead of `duration`, for fractional-second output. Takes\n * precedence over `duration` server-side. Valid values are the integers of\n * the form `25 + 4n` within `[29, 289]`, at 24 fps.\n *\n * Seedance 1.0-pro and 1.0-pro-fast only.\n */\n frames?: number\n\n /**\n * Randomness seed, an integer in `[-1, 2^32-1]`. `-1` (the default) leaves\n * generation unseeded. Accepted by every Seedance model.\n */\n seed?: number\n\n /**\n * Appends a \"fix the camera\" instruction to the prompt. Best-effort — the\n * model is not constrained to obey it.\n *\n * Seedance 1.5-pro, 1.0-pro and 1.0-pro-fast only; the 2.x family rejects\n * it.\n */\n camera_fixed?: boolean\n\n /** Burn a watermark into the output. Defaults to `false`. */\n watermark?: boolean\n\n /**\n * Generate an audio track synchronized with the visuals — dialogue, effects\n * and score inferred from the prompt. Quote dialogue in the prompt for\n * better results.\n *\n * Accepted by every model at the API's validation layer, but only Seedance\n * 2.5, 2.0 and 1.5-pro actually produce audio.\n */\n generate_audio?: boolean\n\n /**\n * Inference queue. Seedance 1.x only — Seedance 2.5 and the 2.0 family have\n * no offline tier.\n */\n service_tier?: BytePlusVideoServiceTier\n\n /**\n * Also return the video's final frame as a watermark-free PNG, readable from\n * the finished task as `content.last_frame_url`. Chain it into the next\n * task's first frame to extend a shot. Accepted by every model.\n */\n return_last_frame?: boolean\n\n /**\n * Render a cheap, low-fidelity preview to sanity-check staging and camera\n * work before paying for the real thing.\n *\n * Seedance 1.5-pro only.\n */\n draft?: boolean\n\n /**\n * Queue priority, `[0, 9]`. Seedance 2.5 and the 2.0 family — 1.5-pro\n * rejects it, and the 1.0 models accept it without acting on it.\n */\n priority?: number\n\n /**\n * Container of the generated video. Seedance 2.5 documents `mp4` (default)\n * and `mov`; other models historically ship `mp4` only.\n */\n output_format?: BytePlusVideoOutputFormat\n\n /**\n * Seconds after `created_at` at which an unfinished task is abandoned and\n * marked `expired`. Documented range `[3600, 259200]`, default 172800\n * (48 hours). The floor is enforced on Seedance 1.x but not on the 2.x\n * family.\n */\n execution_expires_after?: number\n\n /**\n * URL that receives a POST with the full task payload on every status\n * change. BytePlus retries a failed delivery three times.\n */\n callback_url?: string\n\n /**\n * Stable opaque identifier for the end user driving the request, for abuse\n * attribution. Max 64 characters — hash the real identifier rather than\n * sending it.\n */\n safety_identifier?: string\n}\n\n/**\n * Type-only map from video model name to its provider options. Seedance takes\n * the same option surface across models; applicability is per-field and\n * documented on {@link BytePlusVideoProviderOptions}.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type BytePlusVideoModelProviderOptionsByName = {\n [K in BytePlusVideoModel]: BytePlusVideoProviderOptions\n}\n\n/**\n * Aspect ratios accepted by the create endpoint.\n *\n * `adaptive` is rejected by Seedance 1.0-pro / 1.0-pro-fast for text-to-video\n * but is the documented default for their image-to-video path, so it is not\n * filtered per model here.\n */\nconst BYTEPLUS_VIDEO_RATIOS: ReadonlyArray<string> = [\n '16:9',\n '9:16',\n '4:3',\n '3:4',\n '1:1',\n '21:9',\n 'adaptive',\n]\n\n/**\n * Resolutions each model accepts.\n *\n * 2.0 / 1.x cells were live-probed; 2.5 comes from the public ModelArk docs.\n * Two findings still contradict older prose: there is no 2K tier on any\n * Seedance model (`2k`/`2K` is rejected everywhere), and\n * `seedance-1-0-pro-fast-251015` does accept `1080p` despite being documented\n * as 480p/720p only. Seedance 2.5 is 480p/720p only — it does **not** offer\n * the 2.0 flagship's 4k tier.\n */\nconst BYTEPLUS_VIDEO_RESOLUTIONS: {\n readonly [K in BytePlusVideoModel]: ReadonlyArray<BytePlusVideoResolution>\n} = {\n 'dreamina-seedance-2-5-260628': ['480p', '720p'],\n 'dreamina-seedance-2-0-260128': ['480p', '720p', '1080p', '4k'],\n 'dreamina-seedance-2-0-fast-260128': ['480p', '720p'],\n 'dreamina-seedance-2-0-mini-260615': ['480p', '720p'],\n 'seedance-1-5-pro-251215': ['480p', '720p', '1080p'],\n 'seedance-1-0-pro-250528': ['480p', '720p', '1080p'],\n 'seedance-1-0-pro-fast-251015': ['480p', '720p', '1080p'],\n}\n\n/**\n * Models accepting reference-media mode (`r2v`): reference images, video and\n * audio that the output draws on without pinning specific frames. The 1.x\n * models reject it with \"the specified task_type r2v does not support model …\".\n */\nconst BYTEPLUS_VIDEO_REFERENCE_MEDIA_MODELS: ReadonlySet<string> = new Set([\n 'dreamina-seedance-2-5-260628',\n 'dreamina-seedance-2-0-260128',\n 'dreamina-seedance-2-0-fast-260128',\n 'dreamina-seedance-2-0-mini-260615',\n])\n\n/**\n * Models accepting a closing frame (`flf2v`, first-and-last-frame mode).\n * `seedance-1-0-pro-fast-251015` is the one Seedance model without it — it\n * does text-to-video and single-first-frame image-to-video only.\n */\nconst BYTEPLUS_VIDEO_LAST_FRAME_MODELS: ReadonlySet<string> = new Set([\n 'dreamina-seedance-2-5-260628',\n 'dreamina-seedance-2-0-260128',\n 'dreamina-seedance-2-0-fast-260128',\n 'dreamina-seedance-2-0-mini-260615',\n 'seedance-1-5-pro-251215',\n 'seedance-1-0-pro-250528',\n])\n\n/**\n * Models that accept a reference-audio input without a visual reference\n * alongside it. Seedance 2.5 documents audio-only reference-to-video; the 2.0\n * family rejects it with \"reference_audio cannot be the only reference input\".\n */\nconst BYTEPLUS_VIDEO_AUDIO_ONLY_REFERENCE_MODELS: ReadonlySet<string> = new Set(\n ['dreamina-seedance-2-5-260628'],\n)\n\n/**\n * True when the model is *known* to support reference-media mode (reference\n * images, video and audio). An id this package has no metadata for answers\n * `false`; callers must decide whether that means \"no\" or \"unknown\" — the\n * adapter treats it as unknown and lets Ark rule.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport function supportsReferenceMedia(model: string): boolean {\n return BYTEPLUS_VIDEO_REFERENCE_MEDIA_MODELS.has(model)\n}\n\n/**\n * True when the model is *known* to support pinning the video's closing\n * frame. Same unknown-id caveat as {@link supportsReferenceMedia}.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport function supportsLastFrame(model: string): boolean {\n return BYTEPLUS_VIDEO_LAST_FRAME_MODELS.has(model)\n}\n\n/**\n * True when the model is *known* to accept a reference-audio input without a\n * visual reference. Same unknown-id caveat as {@link supportsReferenceMedia}.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport function supportsAudioOnlyReference(model: string): boolean {\n return BYTEPLUS_VIDEO_AUDIO_ONLY_REFERENCE_MODELS.has(model)\n}\n\n/**\n * Splits a `size` template into its Seedance request fields.\n *\n * The template is either a bare aspect ratio (`'16:9'`) or\n * `ratio_resolution` (`'16:9_720p'`), mirroring the grok video adapter.\n * Returns `undefined` when the string doesn't match the template at all.\n *\n * The resolution half comes back lowercased. Ark itself matches the field\n * case-insensitively, but this package standardizes on lowercase so callers\n * can compare the result against {@link BytePlusVideoResolution} directly.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport function parseBytePlusVideoSize(\n size: string,\n): { ratio: string; resolution?: string } | undefined {\n const match = /^(\\d+:\\d+|adaptive)(?:_(.+))?$/.exec(size)\n const [, ratio, resolution] = match ?? []\n if (ratio === undefined) return undefined\n return {\n ratio,\n ...(resolution !== undefined && { resolution: resolution.toLowerCase() }),\n }\n}\n\n/**\n * Validates a resolution against a model's tiers, returning it lowercased.\n *\n * Used for both halves of the request: the resolution parsed out of the\n * generic `size`, and a `modelOptions.resolution` that overrides it. A model\n * this package has no table for is normalized but not checked — see\n * {@link BytePlusVideoModelOrString}.\n *\n * @throws Error when a known model does not offer the tier.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport function resolveBytePlusVideoResolution(\n model: BytePlusVideoModelOrString,\n resolution: string,\n): string {\n const normalized = resolution.toLowerCase()\n if (!isKnownBytePlusVideoModel(model)) return normalized\n\n const allowed = BYTEPLUS_VIDEO_RESOLUTIONS[model]\n if (!allowed.includes(normalized as BytePlusVideoResolution)) {\n throw new Error(\n `byteplus: resolution \"${resolution}\" is not supported by model ` +\n `\"${model}\". Supported resolutions: ${allowed.join(', ')}.`,\n )\n }\n return normalized\n}\n\n/**\n * Validates a `size` template against a model and returns the request fields\n * it maps onto, with the resolution lowercased.\n *\n * For an unknown model only the template's *shape* is checked — enough to\n * split it into `ratio` and `resolution` — because a future model may bring\n * ratios and tiers that do not exist today. Ark validates the values.\n *\n * @throws Error when the template is malformed, or (known models only) the\n * ratio is unknown or the resolution is not offered by this model.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport function resolveBytePlusVideoSize(\n model: BytePlusVideoModelOrString,\n size: string,\n): { ratio: string; resolution?: string } {\n const parsed = parseBytePlusVideoSize(size)\n const known = isKnownBytePlusVideoModel(model)\n if (!parsed || (known && !BYTEPLUS_VIDEO_RATIOS.includes(parsed.ratio))) {\n throw new Error(\n `byteplus: size \"${size}\" is not supported by model \"${model}\". Expected ` +\n `\"ratio\" or \"ratio_resolution\" (e.g. \"16:9_720p\") with ratio one of: ` +\n `${BYTEPLUS_VIDEO_RATIOS.join(', ')}.`,\n )\n }\n\n return {\n ratio: parsed.ratio,\n ...(parsed.resolution !== undefined && {\n resolution: resolveBytePlusVideoResolution(model, parsed.resolution),\n }),\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyNA,IAAM,wBAA+C;CACnD;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;;;;;;AAYA,IAAM,6BAEF;CACF,gCAAgC,CAAC,QAAQ,MAAM;CAC/C,gCAAgC;EAAC;EAAQ;EAAQ;EAAS;CAAI;CAC9D,qCAAqC,CAAC,QAAQ,MAAM;CACpD,qCAAqC,CAAC,QAAQ,MAAM;CACpD,2BAA2B;EAAC;EAAQ;EAAQ;CAAO;CACnD,2BAA2B;EAAC;EAAQ;EAAQ;CAAO;CACnD,gCAAgC;EAAC;EAAQ;EAAQ;CAAO;AAC1D;;;;;;AAOA,IAAM,wDAA6D,IAAI,IAAI;CACzE;CACA;CACA;CACA;AACF,CAAC;;;;;;AAOD,IAAM,mDAAwD,IAAI,IAAI;CACpE;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;;AAOD,IAAM,6DAAkE,IAAI,IAC1E,CAAC,8BAA8B,CACjC;;;;;;;;;AAUA,SAAgB,uBAAuB,OAAwB;CAC7D,OAAO,sCAAsC,IAAI,KAAK;AACxD;;;;;;;AAQA,SAAgB,kBAAkB,OAAwB;CACxD,OAAO,iCAAiC,IAAI,KAAK;AACnD;;;;;;;AAQA,SAAgB,2BAA2B,OAAwB;CACjE,OAAO,2CAA2C,IAAI,KAAK;AAC7D;;;;;;;;;;;;;;AAeA,SAAgB,uBACd,MACoD;CAEpD,MAAM,GAAG,OAAO,cADF,iCAAiC,KAAK,IACtB,KAAS,CAAC;CACxC,IAAI,UAAU,KAAA,GAAW,OAAO,KAAA;CAChC,OAAO;EACL;EACA,GAAI,eAAe,KAAA,KAAa,EAAE,YAAY,WAAW,YAAY,EAAE;CACzE;AACF;;;;;;;;;;;;;AAcA,SAAgB,+BACd,OACA,YACQ;CACR,MAAM,aAAa,WAAW,YAAY;CAC1C,IAAI,CAAC,0BAA0B,KAAK,GAAG,OAAO;CAE9C,MAAM,UAAU,2BAA2B;CAC3C,IAAI,CAAC,QAAQ,SAAS,UAAqC,GACzD,MAAM,IAAI,MACR,yBAAyB,WAAW,+BAC9B,MAAM,4BAA4B,QAAQ,KAAK,IAAI,EAAE,EAC7D;CAEF,OAAO;AACT;;;;;;;;;;;;;;AAeA,SAAgB,yBACd,OACA,MACwC;CACxC,MAAM,SAAS,uBAAuB,IAAI;CAC1C,MAAM,QAAQ,0BAA0B,KAAK;CAC7C,IAAI,CAAC,UAAW,SAAS,CAAC,sBAAsB,SAAS,OAAO,KAAK,GACnE,MAAM,IAAI,MACR,mBAAmB,KAAK,+BAA+B,MAAM,kFAExD,sBAAsB,KAAK,IAAI,EAAE,EACxC;CAGF,OAAO;EACL,OAAO,OAAO;EACd,GAAI,OAAO,eAAe,KAAA,KAAa,EACrC,YAAY,+BAA+B,OAAO,OAAO,UAAU,EACrE;CACF;AACF"}
1
+ {"version":3,"file":"video-provider-options.js","names":[],"sources":["../../../src/video/video-provider-options.ts"],"sourcesContent":["/**\n * Provider options and per-model capability tables for the BytePlus Seedance\n * video models.\n *\n * Applicability for Seedance 1.x / 2.0 was probed live against\n * `https://ark.ap-southeast.bytepluses.com/api/v3` on 2026-07-31. The probe\n * sent an out-of-range `seed` alongside the field under test, so requests that\n * passed validation still failed before a task was created (nothing billed):\n * an error naming the field under test means \"rejected\", an error naming\n * `seed` means \"accepted\". Ark reports only one arbitrary invalid parameter\n * per request, so each cell was retried until a verdict repeated. Seedance 2.5\n * cells come from the public ModelArk create-task docs (opened 2026-08-07);\n * its resolution row was refreshed 2026-08-19 when native 1080p shipped.\n *\n * Ark rejects an inapplicable field outright — \"the specified parameter\n * `draft` is not supported for model seedance-1-0-pro in t2v, must be empty\" —\n * so these tables are not cosmetic: sending a field to the wrong model is a\n * 400, not a no-op.\n *\n * **Where the adapter guards, and where it doesn't** (deliberate, not an\n * oversight). Scalar applicability — `service_tier`, `draft`, `priority`,\n * `frames`, `camera_fixed`, `output_format` — is left to Ark, whose 400 names\n * the offending field and the model precisely enough to act on, and whose\n * per-model rules shift as BytePlus ships models. Duplicating that here would\n * mean a table that silently goes stale and starts rejecting requests the API\n * would have accepted. The adapter guards locally only where the API's own\n * error is misleading or arrives too late to be actionable: prompt media shape\n * (role vocabulary, frame-vs-reference exclusivity, frame cardinality,\n * audio-only reference) and the resolution tier, both of which are derived\n * from a caller's `prompt` / `size` rather than passed through verbatim.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\n\nimport { isKnownBytePlusVideoModel } from '../model-meta'\nimport type {\n BytePlusVideoModel,\n BytePlusVideoModelOrString,\n BytePlusVideoRatio,\n BytePlusVideoResolution,\n} from '../model-meta'\n\n/**\n * Inference queue for the request.\n *\n * - `default` — online inference: lower RPM and concurrency quotas, lowest\n * latency.\n * - `flex` — offline batch inference: higher daily token quotas at half the\n * price, with no latency guarantee. Task ids come back with a `cgt-batch-`\n * prefix (live-verified).\n *\n * Only the Seedance 1.x models accept this field. Seedance 2.5 and the 2.0\n * family reject it (\"service_tier is not supported … must be empty\" / \"not\n * currently supported\").\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type BytePlusVideoServiceTier = 'default' | 'flex'\n\n/**\n * Container format of the generated video.\n *\n * Seedance 2.5 documents `mp4` (default) and `mov`. Other models historically\n * return `mp4` only; scalar applicability is left to Ark (see file header).\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type BytePlusVideoOutputFormat = 'mp4' | 'mov'\n\n/**\n * Provider-specific options for Seedance video generation. These map one-to-one\n * onto the create-task request body and take precedence over the values the\n * adapter derives from the generic `size` / `duration` options.\n *\n * Fields are model-dependent; each one documents where it applies. Passing a\n * field to a model that does not accept it is a 400 from Ark, not a silent\n * ignore.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport interface BytePlusVideoProviderOptions {\n /**\n * Output aspect ratio. Overrides the ratio half of the generic `size`.\n *\n * `adaptive` (follow the input frame) is the default on Seedance 2.5, 2.0\n * and 1.5-pro but is rejected by Seedance 1.0-pro / 1.0-pro-fast for\n * text-to-video.\n */\n ratio?: BytePlusVideoRatio\n\n /**\n * Output resolution tier. Overrides the resolution half of the generic\n * `size`. Matched case-insensitively by the API; this package uses\n * lowercase throughout. `4k` exists only on `dreamina-seedance-2-0-260128`,\n * and there is no 2K tier on any model.\n */\n resolution?: BytePlusVideoResolution\n\n /**\n * Whole seconds of output. Overrides the generic `duration`, and unlike it\n * is sent verbatim rather than snapped into the model's range.\n *\n * `-1` asks the model to choose its own length; accepted by Seedance 2.5,\n * 2.0 and 1.5-pro only. On 2.5 video-editing tasks, `-1` is required.\n */\n duration?: number\n\n /**\n * Frame count instead of `duration`, for fractional-second output. Takes\n * precedence over `duration` server-side. Valid values are the integers of\n * the form `25 + 4n` within `[29, 289]`, at 24 fps.\n *\n * Seedance 1.0-pro and 1.0-pro-fast only.\n */\n frames?: number\n\n /**\n * Randomness seed, an integer in `[-1, 2^32-1]`. `-1` (the default) leaves\n * generation unseeded. Accepted by every Seedance model.\n */\n seed?: number\n\n /**\n * Appends a \"fix the camera\" instruction to the prompt. Best-effort — the\n * model is not constrained to obey it.\n *\n * Seedance 1.5-pro, 1.0-pro and 1.0-pro-fast only; the 2.x family rejects\n * it.\n */\n camera_fixed?: boolean\n\n /** Burn a watermark into the output. Defaults to `false`. */\n watermark?: boolean\n\n /**\n * Generate an audio track synchronized with the visuals — dialogue, effects\n * and score inferred from the prompt. Quote dialogue in the prompt for\n * better results.\n *\n * Accepted by every model at the API's validation layer, but only Seedance\n * 2.5, 2.0 and 1.5-pro actually produce audio.\n */\n generate_audio?: boolean\n\n /**\n * Inference queue. Seedance 1.x only — Seedance 2.5 and the 2.0 family have\n * no offline tier.\n */\n service_tier?: BytePlusVideoServiceTier\n\n /**\n * Also return the video's final frame as a watermark-free PNG, readable from\n * the finished task as `content.last_frame_url`. Chain it into the next\n * task's first frame to extend a shot. Accepted by every model.\n */\n return_last_frame?: boolean\n\n /**\n * Render a cheap, low-fidelity preview to sanity-check staging and camera\n * work before paying for the real thing.\n *\n * Seedance 1.5-pro only.\n */\n draft?: boolean\n\n /**\n * Queue priority, `[0, 9]`. Seedance 2.5 and the 2.0 family — 1.5-pro\n * rejects it, and the 1.0 models accept it without acting on it.\n */\n priority?: number\n\n /**\n * Container of the generated video. Seedance 2.5 documents `mp4` (default)\n * and `mov`; other models historically ship `mp4` only.\n */\n output_format?: BytePlusVideoOutputFormat\n\n /**\n * Seconds after `created_at` at which an unfinished task is abandoned and\n * marked `expired`. Documented range `[3600, 259200]`, default 172800\n * (48 hours). The floor is enforced on Seedance 1.x but not on the 2.x\n * family.\n */\n execution_expires_after?: number\n\n /**\n * URL that receives a POST with the full task payload on every status\n * change. BytePlus retries a failed delivery three times.\n */\n callback_url?: string\n\n /**\n * Stable opaque identifier for the end user driving the request, for abuse\n * attribution. Max 64 characters — hash the real identifier rather than\n * sending it.\n */\n safety_identifier?: string\n}\n\n/**\n * Type-only map from video model name to its provider options. Seedance takes\n * the same option surface across models; applicability is per-field and\n * documented on {@link BytePlusVideoProviderOptions}.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport type BytePlusVideoModelProviderOptionsByName = {\n [K in BytePlusVideoModel]: BytePlusVideoProviderOptions\n}\n\n/**\n * Aspect ratios accepted by the create endpoint.\n *\n * `adaptive` is rejected by Seedance 1.0-pro / 1.0-pro-fast for text-to-video\n * but is the documented default for their image-to-video path, so it is not\n * filtered per model here.\n */\nconst BYTEPLUS_VIDEO_RATIOS: ReadonlyArray<string> = [\n '16:9',\n '9:16',\n '4:3',\n '3:4',\n '1:1',\n '21:9',\n 'adaptive',\n]\n\n/**\n * Resolutions each model accepts.\n *\n * 2.0 / 1.x cells were live-probed; 2.5 comes from the public ModelArk docs\n * and the live fal Seedance 2.5 spec (modelschemas.com, 2026-08-19). Two\n * findings still contradict older prose: there is no 2K tier on any Seedance\n * model (`2k`/`2K` is rejected everywhere), and\n * `seedance-1-0-pro-fast-251015` does accept `1080p` despite being documented\n * as 480p/720p only. Seedance 2.5 is 480p/720p/1080p — it does **not** offer\n * the 2.0 flagship's 4k tier.\n */\nconst BYTEPLUS_VIDEO_RESOLUTIONS: {\n readonly [K in BytePlusVideoModel]: ReadonlyArray<BytePlusVideoResolution>\n} = {\n 'dreamina-seedance-2-5-260628': ['480p', '720p', '1080p'],\n 'dreamina-seedance-2-0-260128': ['480p', '720p', '1080p', '4k'],\n 'dreamina-seedance-2-0-fast-260128': ['480p', '720p'],\n 'dreamina-seedance-2-0-mini-260615': ['480p', '720p'],\n 'seedance-1-5-pro-251215': ['480p', '720p', '1080p'],\n 'seedance-1-0-pro-250528': ['480p', '720p', '1080p'],\n 'seedance-1-0-pro-fast-251015': ['480p', '720p', '1080p'],\n}\n\n/**\n * Models accepting reference-media mode (`r2v`): reference images, video and\n * audio that the output draws on without pinning specific frames. The 1.x\n * models reject it with \"the specified task_type r2v does not support model …\".\n */\nconst BYTEPLUS_VIDEO_REFERENCE_MEDIA_MODELS: ReadonlySet<string> = new Set([\n 'dreamina-seedance-2-5-260628',\n 'dreamina-seedance-2-0-260128',\n 'dreamina-seedance-2-0-fast-260128',\n 'dreamina-seedance-2-0-mini-260615',\n])\n\n/**\n * Models accepting a closing frame (`flf2v`, first-and-last-frame mode).\n * `seedance-1-0-pro-fast-251015` is the one Seedance model without it — it\n * does text-to-video and single-first-frame image-to-video only.\n */\nconst BYTEPLUS_VIDEO_LAST_FRAME_MODELS: ReadonlySet<string> = new Set([\n 'dreamina-seedance-2-5-260628',\n 'dreamina-seedance-2-0-260128',\n 'dreamina-seedance-2-0-fast-260128',\n 'dreamina-seedance-2-0-mini-260615',\n 'seedance-1-5-pro-251215',\n 'seedance-1-0-pro-250528',\n])\n\n/**\n * Models that accept a reference-audio input without a visual reference\n * alongside it. Seedance 2.5 documents audio-only reference-to-video; the 2.0\n * family rejects it with \"reference_audio cannot be the only reference input\".\n */\nconst BYTEPLUS_VIDEO_AUDIO_ONLY_REFERENCE_MODELS: ReadonlySet<string> = new Set(\n ['dreamina-seedance-2-5-260628'],\n)\n\n/**\n * True when the model is *known* to support reference-media mode (reference\n * images, video and audio). An id this package has no metadata for answers\n * `false`; callers must decide whether that means \"no\" or \"unknown\" — the\n * adapter treats it as unknown and lets Ark rule.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport function supportsReferenceMedia(model: string): boolean {\n return BYTEPLUS_VIDEO_REFERENCE_MEDIA_MODELS.has(model)\n}\n\n/**\n * True when the model is *known* to support pinning the video's closing\n * frame. Same unknown-id caveat as {@link supportsReferenceMedia}.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport function supportsLastFrame(model: string): boolean {\n return BYTEPLUS_VIDEO_LAST_FRAME_MODELS.has(model)\n}\n\n/**\n * True when the model is *known* to accept a reference-audio input without a\n * visual reference. Same unknown-id caveat as {@link supportsReferenceMedia}.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport function supportsAudioOnlyReference(model: string): boolean {\n return BYTEPLUS_VIDEO_AUDIO_ONLY_REFERENCE_MODELS.has(model)\n}\n\n/**\n * Splits a `size` template into its Seedance request fields.\n *\n * The template is either a bare aspect ratio (`'16:9'`) or\n * `ratio_resolution` (`'16:9_720p'`), mirroring the grok video adapter.\n * Returns `undefined` when the string doesn't match the template at all.\n *\n * The resolution half comes back lowercased. Ark itself matches the field\n * case-insensitively, but this package standardizes on lowercase so callers\n * can compare the result against {@link BytePlusVideoResolution} directly.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport function parseBytePlusVideoSize(\n size: string,\n): { ratio: string; resolution?: string } | undefined {\n const match = /^(\\d+:\\d+|adaptive)(?:_(.+))?$/.exec(size)\n const [, ratio, resolution] = match ?? []\n if (ratio === undefined) return undefined\n return {\n ratio,\n ...(resolution !== undefined && { resolution: resolution.toLowerCase() }),\n }\n}\n\n/**\n * Validates a resolution against a model's tiers, returning it lowercased.\n *\n * Used for both halves of the request: the resolution parsed out of the\n * generic `size`, and a `modelOptions.resolution` that overrides it. A model\n * this package has no table for is normalized but not checked — see\n * {@link BytePlusVideoModelOrString}.\n *\n * @throws Error when a known model does not offer the tier.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport function resolveBytePlusVideoResolution(\n model: BytePlusVideoModelOrString,\n resolution: string,\n): string {\n const normalized = resolution.toLowerCase()\n if (!isKnownBytePlusVideoModel(model)) return normalized\n\n const allowed = BYTEPLUS_VIDEO_RESOLUTIONS[model]\n if (!allowed.includes(normalized as BytePlusVideoResolution)) {\n throw new Error(\n `byteplus: resolution \"${resolution}\" is not supported by model ` +\n `\"${model}\". Supported resolutions: ${allowed.join(', ')}.`,\n )\n }\n return normalized\n}\n\n/**\n * Validates a `size` template against a model and returns the request fields\n * it maps onto, with the resolution lowercased.\n *\n * For an unknown model only the template's *shape* is checked — enough to\n * split it into `ratio` and `resolution` — because a future model may bring\n * ratios and tiers that do not exist today. Ark validates the values.\n *\n * @throws Error when the template is malformed, or (known models only) the\n * ratio is unknown or the resolution is not offered by this model.\n *\n * @experimental Video generation is an experimental feature and may change.\n */\nexport function resolveBytePlusVideoSize(\n model: BytePlusVideoModelOrString,\n size: string,\n): { ratio: string; resolution?: string } {\n const parsed = parseBytePlusVideoSize(size)\n const known = isKnownBytePlusVideoModel(model)\n if (!parsed || (known && !BYTEPLUS_VIDEO_RATIOS.includes(parsed.ratio))) {\n throw new Error(\n `byteplus: size \"${size}\" is not supported by model \"${model}\". Expected ` +\n `\"ratio\" or \"ratio_resolution\" (e.g. \"16:9_720p\") with ratio one of: ` +\n `${BYTEPLUS_VIDEO_RATIOS.join(', ')}.`,\n )\n }\n\n return {\n ratio: parsed.ratio,\n ...(parsed.resolution !== undefined && {\n resolution: resolveBytePlusVideoResolution(model, parsed.resolution),\n }),\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyNA,IAAM,wBAA+C;CACnD;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;;;;;;;AAaA,IAAM,6BAEF;CACF,gCAAgC;EAAC;EAAQ;EAAQ;CAAO;CACxD,gCAAgC;EAAC;EAAQ;EAAQ;EAAS;CAAI;CAC9D,qCAAqC,CAAC,QAAQ,MAAM;CACpD,qCAAqC,CAAC,QAAQ,MAAM;CACpD,2BAA2B;EAAC;EAAQ;EAAQ;CAAO;CACnD,2BAA2B;EAAC;EAAQ;EAAQ;CAAO;CACnD,gCAAgC;EAAC;EAAQ;EAAQ;CAAO;AAC1D;;;;;;AAOA,IAAM,wDAA6D,IAAI,IAAI;CACzE;CACA;CACA;CACA;AACF,CAAC;;;;;;AAOD,IAAM,mDAAwD,IAAI,IAAI;CACpE;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;;AAOD,IAAM,6DAAkE,IAAI,IAC1E,CAAC,8BAA8B,CACjC;;;;;;;;;AAUA,SAAgB,uBAAuB,OAAwB;CAC7D,OAAO,sCAAsC,IAAI,KAAK;AACxD;;;;;;;AAQA,SAAgB,kBAAkB,OAAwB;CACxD,OAAO,iCAAiC,IAAI,KAAK;AACnD;;;;;;;AAQA,SAAgB,2BAA2B,OAAwB;CACjE,OAAO,2CAA2C,IAAI,KAAK;AAC7D;;;;;;;;;;;;;;AAeA,SAAgB,uBACd,MACoD;CAEpD,MAAM,GAAG,OAAO,cADF,iCAAiC,KAAK,IACtB,KAAS,CAAC;CACxC,IAAI,UAAU,KAAA,GAAW,OAAO,KAAA;CAChC,OAAO;EACL;EACA,GAAI,eAAe,KAAA,KAAa,EAAE,YAAY,WAAW,YAAY,EAAE;CACzE;AACF;;;;;;;;;;;;;AAcA,SAAgB,+BACd,OACA,YACQ;CACR,MAAM,aAAa,WAAW,YAAY;CAC1C,IAAI,CAAC,0BAA0B,KAAK,GAAG,OAAO;CAE9C,MAAM,UAAU,2BAA2B;CAC3C,IAAI,CAAC,QAAQ,SAAS,UAAqC,GACzD,MAAM,IAAI,MACR,yBAAyB,WAAW,+BAC9B,MAAM,4BAA4B,QAAQ,KAAK,IAAI,EAAE,EAC7D;CAEF,OAAO;AACT;;;;;;;;;;;;;;AAeA,SAAgB,yBACd,OACA,MACwC;CACxC,MAAM,SAAS,uBAAuB,IAAI;CAC1C,MAAM,QAAQ,0BAA0B,KAAK;CAC7C,IAAI,CAAC,UAAW,SAAS,CAAC,sBAAsB,SAAS,OAAO,KAAK,GACnE,MAAM,IAAI,MACR,mBAAmB,KAAK,+BAA+B,MAAM,kFAExD,sBAAsB,KAAK,IAAI,EAAE,EACxC;CAGF,OAAO;EACL,OAAO,OAAO;EACd,GAAI,OAAO,eAAe,KAAA,KAAa,EACrC,YAAY,+BAA+B,OAAO,OAAO,UAAU,EACrE;CACF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai-byteplus",
3
- "version": "0.1.2",
3
+ "version": "0.2.2",
4
4
  "description": "BytePlus ModelArk adapter for TanStack AI: Seed LLM chat, Seedance video, Seedream image, and Seed Speech TTS/ASR.",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -50,16 +50,16 @@
50
50
  "devDependencies": {
51
51
  "@vitest/coverage-v8": "4.1.10",
52
52
  "vite": "^8.2.1",
53
- "@tanstack/ai": "0.45.0"
53
+ "@tanstack/ai": "0.47.1"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "zod": "^4.0.0",
57
- "@tanstack/ai": "^0.45.0"
57
+ "@tanstack/ai": "^0.47.1"
58
58
  },
59
59
  "dependencies": {
60
60
  "openai": "^6.41.0",
61
61
  "@tanstack/ai-utils": "^0.4.0",
62
- "@tanstack/openai-base": "^0.9.13"
62
+ "@tanstack/openai-base": "^0.10.0"
63
63
  },
64
64
  "scripts": {
65
65
  "build": "vite build",
@@ -94,7 +94,9 @@ function describeFailures(
94
94
  *
95
95
  * BytePlus bills per generated image and does not count input tokens, so
96
96
  * `promptTokens` is always 0 and `generated_images` is surfaced as
97
- * `unitsBilled` — the count the price is applied to.
97
+ * `usage.billed` (`{ quantity, unit: 'images' }`) — the count the price is
98
+ * applied to. The deprecated `unitsBilled` is still populated for
99
+ * backward compatibility.
98
100
  */
99
101
  function buildBytePlusImageUsage(
100
102
  usage: BytePlusImageUsage | undefined,
@@ -107,6 +109,7 @@ function buildBytePlusImageUsage(
107
109
  completionTokens,
108
110
  totalTokens: usage.total_tokens ?? completionTokens,
109
111
  ...(usage.generated_images !== undefined && {
112
+ billed: { quantity: usage.generated_images, unit: 'images' },
110
113
  unitsBilled: usage.generated_images,
111
114
  }),
112
115
  }
@@ -316,13 +316,15 @@ export function mapRecognizeResponse(
316
316
 
317
317
  // Seed ASR is duration-billed and reports no token counts, so `usage`
318
318
  // carries only the audio length — the same shape the Grok and OpenAI
319
- // whisper paths use.
319
+ // whisper paths use. `durationSeconds` is deprecated but still populated
320
+ // alongside the self-describing `billed` pair.
320
321
  const usage: TokenUsage | undefined =
321
322
  duration !== undefined
322
323
  ? {
323
324
  promptTokens: 0,
324
325
  completionTokens: 0,
325
326
  totalTokens: 0,
327
+ billed: { quantity: duration, unit: 'seconds' },
326
328
  durationSeconds: duration,
327
329
  }
328
330
  : undefined
@@ -95,9 +95,11 @@ function toTokenCount(value: number | string | undefined): number | undefined {
95
95
  /**
96
96
  * Maps a finished task's usage onto `TokenUsage`.
97
97
  *
98
- * Seedance bills output only — the API documents input tokens as always 0 and
99
- * `total_tokens` as equal to `completion_tokens` — so `promptTokens` is 0 and
100
- * the completion count doubles as `unitsBilled`.
98
+ * Seedance bills output only. The API documents input tokens as always 0 and
99
+ * `total_tokens` as equal to `completion_tokens`, so `promptTokens` is 0 and
100
+ * the completion count is the billed quantity (`usage.billed` with
101
+ * `unit: 'tokens'`). The deprecated `unitsBilled` is still populated for
102
+ * backward compatibility.
101
103
  */
102
104
  function buildBytePlusVideoUsage(
103
105
  usage: BytePlusVideoTaskUsage | undefined,
@@ -115,6 +117,7 @@ function buildBytePlusVideoUsage(
115
117
  promptTokens: 0,
116
118
  completionTokens: completion,
117
119
  totalTokens: totalTokens ?? completion,
120
+ billed: { quantity: completion, unit: 'tokens' },
118
121
  unitsBilled: completion,
119
122
  }
120
123
  }
package/src/model-meta.ts CHANGED
@@ -491,8 +491,8 @@ export type BytePlusVideoRatio =
491
491
  * Resolution tiers are model-specific (see
492
492
  * {@link BytePlusVideoModelResolutionByName}). Two findings that still
493
493
  * contradict older BytePlus prose: there is **no 2K tier on any Seedance
494
- * model**, and `4k` exists only on `dreamina-seedance-2-0-260128` (Seedance
495
- * 2.5 is 480p/720p only, per the live ModelArk docs).
494
+ * model**, and `4k` exists only on `dreamina-seedance-2-0-260128`. Seedance
495
+ * 2.5 accepts 480p/720p/1080p (ModelArk + fal spec, 2026-08-19).
496
496
  *
497
497
  * The API matches this field case-insensitively (`4K`, `4k` and `1080P` are
498
498
  * all accepted), so this package standardizes on the lowercase spelling.
@@ -607,12 +607,12 @@ export type BytePlusVideoModelInputModalitiesByName = {
607
607
  * Type-only map from video model name to the resolutions it accepts.
608
608
  *
609
609
  * 2.0 / 1.x cells were probe-verified on 2026-07-31; 2.5 comes from the
610
- * public ModelArk create-task docs (2026-08-07). Note
611
- * `seedance-1-0-pro-fast-251015` does accept `1080p`, despite older BytePlus
612
- * prose listing it as 480p/720p.
610
+ * public ModelArk create-task docs, refreshed 2026-08-19 when native 1080p
611
+ * shipped. Note `seedance-1-0-pro-fast-251015` does accept `1080p`, despite
612
+ * older BytePlus prose listing it as 480p/720p.
613
613
  */
614
614
  export type BytePlusVideoModelResolutionByName = {
615
- [DREAMINA_SEEDANCE_2_5.name]: '480p' | '720p'
615
+ [DREAMINA_SEEDANCE_2_5.name]: '480p' | '720p' | '1080p'
616
616
  [DREAMINA_SEEDANCE_2_0.name]: '480p' | '720p' | '1080p' | '4k'
617
617
  [DREAMINA_SEEDANCE_2_0_FAST.name]: '480p' | '720p'
618
618
  [DREAMINA_SEEDANCE_2_0_MINI.name]: '480p' | '720p'
@@ -9,8 +9,8 @@
9
9
  * an error naming the field under test means "rejected", an error naming
10
10
  * `seed` means "accepted". Ark reports only one arbitrary invalid parameter
11
11
  * per request, so each cell was retried until a verdict repeated. Seedance 2.5
12
- * cells come from the public ModelArk create-task docs once the model was
13
- * fully opened (2026-08-07).
12
+ * cells come from the public ModelArk create-task docs (opened 2026-08-07);
13
+ * its resolution row was refreshed 2026-08-19 when native 1080p shipped.
14
14
  *
15
15
  * Ark rejects an inapplicable field outright — "the specified parameter
16
16
  * `draft` is not supported for model seedance-1-0-pro in t2v, must be empty" —
@@ -91,8 +91,8 @@ export interface BytePlusVideoProviderOptions {
91
91
  /**
92
92
  * Output resolution tier. Overrides the resolution half of the generic
93
93
  * `size`. Matched case-insensitively by the API; this package uses
94
- * lowercase throughout. `4k` exists only on `dreamina-seedance-2-0-260128`
95
- * (Seedance 2.5 is 480p/720p only), and there is no 2K tier on any model.
94
+ * lowercase throughout. `4k` exists only on `dreamina-seedance-2-0-260128`,
95
+ * and there is no 2K tier on any model.
96
96
  */
97
97
  resolution?: BytePlusVideoResolution
98
98
 
@@ -228,17 +228,18 @@ const BYTEPLUS_VIDEO_RATIOS: ReadonlyArray<string> = [
228
228
  /**
229
229
  * Resolutions each model accepts.
230
230
  *
231
- * 2.0 / 1.x cells were live-probed; 2.5 comes from the public ModelArk docs.
232
- * Two findings still contradict older prose: there is no 2K tier on any
233
- * Seedance model (`2k`/`2K` is rejected everywhere), and
231
+ * 2.0 / 1.x cells were live-probed; 2.5 comes from the public ModelArk docs
232
+ * and the live fal Seedance 2.5 spec (modelschemas.com, 2026-08-19). Two
233
+ * findings still contradict older prose: there is no 2K tier on any Seedance
234
+ * model (`2k`/`2K` is rejected everywhere), and
234
235
  * `seedance-1-0-pro-fast-251015` does accept `1080p` despite being documented
235
- * as 480p/720p only. Seedance 2.5 is 480p/720p only — it does **not** offer
236
+ * as 480p/720p only. Seedance 2.5 is 480p/720p/1080p — it does **not** offer
236
237
  * the 2.0 flagship's 4k tier.
237
238
  */
238
239
  const BYTEPLUS_VIDEO_RESOLUTIONS: {
239
240
  readonly [K in BytePlusVideoModel]: ReadonlyArray<BytePlusVideoResolution>
240
241
  } = {
241
- 'dreamina-seedance-2-5-260628': ['480p', '720p'],
242
+ 'dreamina-seedance-2-5-260628': ['480p', '720p', '1080p'],
242
243
  'dreamina-seedance-2-0-260128': ['480p', '720p', '1080p', '4k'],
243
244
  'dreamina-seedance-2-0-fast-260128': ['480p', '720p'],
244
245
  'dreamina-seedance-2-0-mini-260615': ['480p', '720p'],