@ai-sdk/black-forest-labs 2.0.20 → 2.0.21

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.
@@ -2,12 +2,10 @@ import type { ImageModelV4, SharedV4Warning } from '@ai-sdk/provider';
2
2
  import {
3
3
  combineHeaders,
4
4
  createBinaryResponseHandler,
5
- createJsonErrorResponseHandler,
6
5
  createJsonResponseHandler,
7
6
  createStatusCodeErrorResponseHandler,
8
7
  delay,
9
8
  getFromApi,
10
- isSameOrigin,
11
9
  parseProviderOptions,
12
10
  postJsonToApi,
13
11
  resolve,
@@ -18,6 +16,10 @@ import {
18
16
  type FetchFunction,
19
17
  } from '@ai-sdk/provider-utils';
20
18
  import { z } from 'zod/v4';
19
+ import {
20
+ bflFailedResponseHandler,
21
+ isTrustedUrl,
22
+ } from './black-forest-labs-api';
21
23
  import { blackForestLabsImageModelOptionsSchema } from './black-forest-labs-image-model-options';
22
24
  import type {
23
25
  BlackForestLabsAspectRatio,
@@ -362,29 +364,6 @@ export class BlackForestLabsImageModel implements ImageModelV4 {
362
364
  }
363
365
  }
364
366
 
365
- /**
366
- * Black Forest Labs returns response-supplied URLs (polling and delivery) on
367
- * sibling cluster hosts of the API origin (e.g. `api.us1.bfl.ai` for a base
368
- * URL on `api.bfl.ai`), so a strict same-origin check against the configured
369
- * base URL is not enough. Credentials may also be sent to any https host under
370
- * the official `bfl.ai` domain.
371
- */
372
- function isTrustedUrl(url: string, baseUrl: string): boolean {
373
- if (isSameOrigin(url, baseUrl)) {
374
- return true;
375
- }
376
-
377
- try {
378
- const { protocol, hostname } = new URL(url);
379
- return (
380
- protocol === 'https:' &&
381
- (hostname === 'bfl.ai' || hostname.endsWith('.bfl.ai'))
382
- );
383
- } catch {
384
- return false;
385
- }
386
- }
387
-
388
367
  function convertSizeToAspectRatio(
389
368
  size: string,
390
369
  ): BlackForestLabsAspectRatio | undefined {
@@ -452,29 +431,3 @@ const bflPollSchema = z
452
431
  status: (v.status ?? v.state)!,
453
432
  result: v.result,
454
433
  }));
455
-
456
- const bflErrorSchema = z.object({
457
- message: z.string().optional(),
458
- detail: z.any().optional(),
459
- });
460
-
461
- const bflFailedResponseHandler = createJsonErrorResponseHandler({
462
- errorSchema: bflErrorSchema,
463
- errorToMessage: error =>
464
- bflErrorToMessage(error) ?? 'Unknown Black Forest Labs error',
465
- });
466
-
467
- function bflErrorToMessage(error: unknown): string | undefined {
468
- const parsed = bflErrorSchema.safeParse(error);
469
- if (!parsed.success) return undefined;
470
- const { message, detail } = parsed.data;
471
- if (typeof detail === 'string') return detail;
472
- if (detail != null) {
473
- try {
474
- return JSON.stringify(detail);
475
- } catch {
476
- // ignore
477
- }
478
- }
479
- return message;
480
- }
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  NoSuchModelError,
3
+ type Experimental_VideoModelV4,
3
4
  type ImageModelV4,
4
5
  type ProviderV4,
5
6
  } from '@ai-sdk/provider';
@@ -11,6 +12,8 @@ import {
11
12
  } from '@ai-sdk/provider-utils';
12
13
  import { BlackForestLabsImageModel } from './black-forest-labs-image-model';
13
14
  import type { BlackForestLabsImageModelId } from './black-forest-labs-image-settings';
15
+ import { BlackForestLabsVideoModel } from './black-forest-labs-video-model';
16
+ import type { BlackForestLabsVideoModelId } from './black-forest-labs-video-settings';
14
17
  import { VERSION } from './version';
15
18
 
16
19
  export interface BlackForestLabsProviderSettings {
@@ -36,12 +39,14 @@ export interface BlackForestLabsProviderSettings {
36
39
  fetch?: FetchFunction;
37
40
 
38
41
  /**
39
- * Poll interval in milliseconds between status checks. Defaults to 500ms.
42
+ * Poll interval in milliseconds between status checks. Defaults to 500ms for
43
+ * images and 2s for video.
40
44
  */
41
45
  pollIntervalMillis?: number;
42
46
 
43
47
  /**
44
- * Overall timeout in milliseconds for polling before giving up. Defaults to 60s.
48
+ * Overall timeout in milliseconds for polling before giving up. Defaults to
49
+ * 60s for images and 10 minutes for video.
45
50
  */
46
51
  pollTimeoutMillis?: number;
47
52
  }
@@ -57,6 +62,16 @@ export interface BlackForestLabsProvider extends ProviderV4 {
57
62
  */
58
63
  imageModel(modelId: BlackForestLabsImageModelId): ImageModelV4;
59
64
 
65
+ /**
66
+ * Creates a model for video generation.
67
+ */
68
+ video(modelId: BlackForestLabsVideoModelId): Experimental_VideoModelV4;
69
+
70
+ /**
71
+ * Creates a model for video generation.
72
+ */
73
+ videoModel(modelId: BlackForestLabsVideoModelId): Experimental_VideoModelV4;
74
+
60
75
  /**
61
76
  * @deprecated Use `embeddingModel` instead.
62
77
  */
@@ -92,6 +107,16 @@ export function createBlackForestLabs(
92
107
  pollTimeoutMillis: options.pollTimeoutMillis,
93
108
  });
94
109
 
110
+ const createVideoModel = (modelId: BlackForestLabsVideoModelId) =>
111
+ new BlackForestLabsVideoModel(modelId, {
112
+ provider: 'black-forest-labs.video',
113
+ baseURL: baseURL ?? defaultBaseURL,
114
+ headers: getHeaders,
115
+ fetch: options.fetch,
116
+ pollIntervalMillis: options.pollIntervalMillis,
117
+ pollTimeoutMillis: options.pollTimeoutMillis,
118
+ });
119
+
95
120
  const embeddingModel = (modelId: string) => {
96
121
  throw new NoSuchModelError({
97
122
  modelId,
@@ -103,6 +128,8 @@ export function createBlackForestLabs(
103
128
  specificationVersion: 'v4',
104
129
  imageModel: createImageModel,
105
130
  image: createImageModel,
131
+ videoModel: createVideoModel,
132
+ video: createVideoModel,
106
133
  languageModel: (modelId: string) => {
107
134
  throw new NoSuchModelError({
108
135
  modelId,
@@ -0,0 +1,92 @@
1
+ import {
2
+ lazySchema,
3
+ zodSchema,
4
+ type InferSchema,
5
+ } from '@ai-sdk/provider-utils';
6
+ import { z } from 'zod/v4';
7
+ import {
8
+ blackForestLabsVideoAspectRatios,
9
+ blackForestLabsVideoResolutions,
10
+ } from './black-forest-labs-video-settings';
11
+
12
+ const blackForestLabsTimedVideoKeyframeSchema = z.tuple([
13
+ z.number().min(0).max(20),
14
+ z.string(),
15
+ ]);
16
+
17
+ export const blackForestLabsVideoKeyframesSchema = z.union([
18
+ z.array(z.string()).min(1).max(10),
19
+ z
20
+ .array(blackForestLabsTimedVideoKeyframeSchema)
21
+ .min(1)
22
+ .max(10)
23
+ .refine(
24
+ keyframes =>
25
+ keyframes.every(
26
+ (keyframe, index) =>
27
+ index === 0 || keyframe[0] > keyframes[index - 1][0],
28
+ ),
29
+ { message: 'Timed keyframes must be in chronological order.' },
30
+ ),
31
+ ]);
32
+
33
+ export const blackForestLabsVideoProviderOptions = z.object({
34
+ /**
35
+ * Output resolution tier. Takes precedence over the top-level `resolution`,
36
+ * which is expressed as `{width}x{height}` and has to be mapped onto a tier.
37
+ */
38
+ resolution: z.enum(blackForestLabsVideoResolutions).optional(),
39
+
40
+ /**
41
+ * Aspect ratio of the generated video. Takes precedence over the top-level
42
+ * `aspectRatio`, and unlike it can be set to `auto`.
43
+ */
44
+ aspectRatio: z.enum(blackForestLabsVideoAspectRatios).optional(),
45
+
46
+ /**
47
+ * Keyframes for image-to-video generation, for the shapes the top-level
48
+ * `image` and `frameImages` options cannot express: more than two images, or
49
+ * images pinned to a specific second. Takes precedence over both.
50
+ *
51
+ * One image opens the clip, two open and close it, and with more the extras
52
+ * are spaced evenly in between. Three or more plain images require an
53
+ * explicit `duration`.
54
+ */
55
+ keyframes: blackForestLabsVideoKeyframesSchema.optional(),
56
+
57
+ /**
58
+ * Moderation strictness from 0 (strictest) to 4. Defaults to 2. Sexual
59
+ * content is capped at 3 and hate content at 2 regardless of the request,
60
+ * and any request carrying conditioning media is capped at 2.
61
+ */
62
+ safetyTolerance: z.number().int().min(0).max(4).optional(),
63
+
64
+ /**
65
+ * Render a fast, lower-quality preview instead of the finished video.
66
+ * Defaults to `false`.
67
+ */
68
+ draft: z.boolean().optional(),
69
+
70
+ /**
71
+ * Encrypted draft-cache bundle from a prior `draft` generation, which
72
+ * switches the request to draft-enhance mode: the bundle is replayed at full
73
+ * quality.
74
+ *
75
+ * Either the base64-encoded `.bin` downloaded from the draft's `draftCache`
76
+ * URL, or that URL itself while it is still in its expiry window.
77
+ */
78
+ draftCache: z.string().optional(),
79
+
80
+ /**
81
+ * Model version to pin. Only `latest` is available today.
82
+ */
83
+ version: z.literal('latest').optional(),
84
+ });
85
+
86
+ export const blackForestLabsVideoModelOptionsSchema = lazySchema(() =>
87
+ zodSchema(blackForestLabsVideoProviderOptions),
88
+ );
89
+
90
+ export type BlackForestLabsVideoModelOptions = InferSchema<
91
+ typeof blackForestLabsVideoModelOptionsSchema
92
+ >;