@ai-sdk/black-forest-labs 2.0.52 → 2.0.54
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.
- package/CHANGELOG.md +18 -0
- package/dist/index.d.ts +100 -94
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +897 -1124
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/dist/index.js
CHANGED
|
@@ -1,1159 +1,932 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
NoSuchModelError
|
|
4
|
-
} from "@ai-sdk/provider";
|
|
5
|
-
import {
|
|
6
|
-
loadApiKey,
|
|
7
|
-
withoutTrailingSlash,
|
|
8
|
-
withUserAgentSuffix
|
|
9
|
-
} from "@ai-sdk/provider-utils";
|
|
10
|
-
|
|
11
|
-
// src/black-forest-labs-image-model.ts
|
|
12
|
-
import {
|
|
13
|
-
combineHeaders,
|
|
14
|
-
createBinaryResponseHandler,
|
|
15
|
-
createJsonResponseHandler,
|
|
16
|
-
createStatusCodeErrorResponseHandler,
|
|
17
|
-
delay,
|
|
18
|
-
getFromApi,
|
|
19
|
-
parseProviderOptions,
|
|
20
|
-
postJsonToApi,
|
|
21
|
-
resolve,
|
|
22
|
-
serializeModelOptions,
|
|
23
|
-
WORKFLOW_SERIALIZE,
|
|
24
|
-
WORKFLOW_DESERIALIZE
|
|
25
|
-
} from "@ai-sdk/provider-utils";
|
|
26
|
-
import { z as z3 } from "zod/v4";
|
|
27
|
-
|
|
28
|
-
// src/black-forest-labs-api.ts
|
|
29
|
-
import {
|
|
30
|
-
createJsonErrorResponseHandler,
|
|
31
|
-
isSameOrigin
|
|
32
|
-
} from "@ai-sdk/provider-utils";
|
|
1
|
+
import { AISDKError, InvalidArgumentError, NoSuchModelError } from "@ai-sdk/provider";
|
|
2
|
+
import { WORKFLOW_DESERIALIZE, WORKFLOW_SERIALIZE, combineHeaders, createBinaryResponseHandler, createJsonErrorResponseHandler, createJsonResponseHandler, createStatusCodeErrorResponseHandler, delay, getFromApi, getTopLevelMediaType, isSameOrigin, lazySchema, loadApiKey, parseProviderOptions, postJsonToApi, resolve, serializeModelOptions, withUserAgentSuffix, withoutTrailingSlash, zodSchema } from "@ai-sdk/provider-utils";
|
|
33
3
|
import { z } from "zod/v4";
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
4
|
+
//#region src/black-forest-labs-api.ts
|
|
5
|
+
/**
|
|
6
|
+
* Transport concerns shared by every Black Forest Labs model
|
|
7
|
+
*/
|
|
8
|
+
const bflErrorSchema = z.object({
|
|
9
|
+
message: z.string().optional(),
|
|
10
|
+
detail: z.any().optional()
|
|
37
11
|
});
|
|
38
12
|
function bflErrorToMessage(error) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return message;
|
|
13
|
+
const parsed = bflErrorSchema.safeParse(error);
|
|
14
|
+
if (!parsed.success) return void 0;
|
|
15
|
+
const { message, detail } = parsed.data;
|
|
16
|
+
if (typeof detail === "string") return detail;
|
|
17
|
+
if (detail != null) try {
|
|
18
|
+
return JSON.stringify(detail);
|
|
19
|
+
} catch {}
|
|
20
|
+
return message;
|
|
50
21
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
22
|
+
const bflFailedResponseHandler = createJsonErrorResponseHandler({
|
|
23
|
+
errorSchema: bflErrorSchema,
|
|
24
|
+
errorToMessage: (error) => bflErrorToMessage(error) ?? "Unknown Black Forest Labs error"
|
|
54
25
|
});
|
|
26
|
+
/**
|
|
27
|
+
* Black Forest Labs returns response-supplied URLs (polling and delivery) on
|
|
28
|
+
* sibling cluster hosts of the API origin (e.g. `api.us1.bfl.ai` for a base
|
|
29
|
+
* URL on `api.bfl.ai`), so a strict same-origin check against the configured
|
|
30
|
+
* base URL is not enough. Credentials may also be sent to any https host under
|
|
31
|
+
* the official `bfl.ai` domain.
|
|
32
|
+
*/
|
|
55
33
|
function isTrustedUrl(url, baseUrl) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
34
|
+
if (isSameOrigin(url, baseUrl)) return true;
|
|
35
|
+
try {
|
|
36
|
+
const { protocol, hostname } = new URL(url);
|
|
37
|
+
return protocol === "https:" && (hostname === "bfl.ai" || hostname.endsWith(".bfl.ai"));
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
65
41
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
...resultSeed != null && { seed: resultSeed },
|
|
310
|
-
...resultStartTime != null && { start_time: resultStartTime },
|
|
311
|
-
...resultEndTime != null && { end_time: resultEndTime },
|
|
312
|
-
...resultDuration != null && { duration: resultDuration },
|
|
313
|
-
...submit.value.cost != null && { cost: submit.value.cost },
|
|
314
|
-
...submit.value.input_mp != null && {
|
|
315
|
-
inputMegapixels: submit.value.input_mp
|
|
316
|
-
},
|
|
317
|
-
...submit.value.output_mp != null && {
|
|
318
|
-
outputMegapixels: submit.value.output_mp
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
]
|
|
322
|
-
}
|
|
323
|
-
},
|
|
324
|
-
response: {
|
|
325
|
-
modelId: this.modelId,
|
|
326
|
-
timestamp: currentDate,
|
|
327
|
-
headers: responseHeaders
|
|
328
|
-
}
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
async pollForImageUrl({
|
|
332
|
-
pollUrl,
|
|
333
|
-
requestId,
|
|
334
|
-
headers,
|
|
335
|
-
abortSignal,
|
|
336
|
-
pollOverrides
|
|
337
|
-
}) {
|
|
338
|
-
const pollIntervalMillis = pollOverrides?.pollIntervalMillis ?? this.config.pollIntervalMillis ?? DEFAULT_POLL_INTERVAL_MILLIS;
|
|
339
|
-
const pollTimeoutMillis = pollOverrides?.pollTimeoutMillis ?? this.config.pollTimeoutMillis ?? DEFAULT_POLL_TIMEOUT_MILLIS;
|
|
340
|
-
const url = new URL(pollUrl);
|
|
341
|
-
if (!url.searchParams.has("id")) {
|
|
342
|
-
url.searchParams.set("id", requestId);
|
|
343
|
-
}
|
|
344
|
-
const timeoutController = new AbortController();
|
|
345
|
-
let didTimeout = false;
|
|
346
|
-
const timeoutId = setTimeout(() => {
|
|
347
|
-
didTimeout = true;
|
|
348
|
-
timeoutController.abort();
|
|
349
|
-
}, pollTimeoutMillis);
|
|
350
|
-
const pollingAbortSignal = abortSignal == null ? timeoutController.signal : AbortSignal.any([abortSignal, timeoutController.signal]);
|
|
351
|
-
try {
|
|
352
|
-
while (true) {
|
|
353
|
-
const { value } = await getFromApi({
|
|
354
|
-
url: url.toString(),
|
|
355
|
-
// The polling URL comes from the provider response; validate it.
|
|
356
|
-
validateUrl: true,
|
|
357
|
-
trustedOrigin: this.config.baseURL,
|
|
358
|
-
// Only send credentials when it stays on a trusted provider host.
|
|
359
|
-
headers: isTrustedUrl(url.toString(), this.config.baseURL) ? headers : void 0,
|
|
360
|
-
failedResponseHandler: bflFailedResponseHandler,
|
|
361
|
-
successfulResponseHandler: createJsonResponseHandler(bflPollSchema),
|
|
362
|
-
abortSignal: pollingAbortSignal,
|
|
363
|
-
fetch: this.config.fetch
|
|
364
|
-
});
|
|
365
|
-
const status = value.status;
|
|
366
|
-
if (status === "Ready") {
|
|
367
|
-
if (typeof value.result?.sample === "string") {
|
|
368
|
-
return {
|
|
369
|
-
imageUrl: value.result.sample,
|
|
370
|
-
seed: value.result.seed ?? void 0,
|
|
371
|
-
start_time: value.result.start_time ?? void 0,
|
|
372
|
-
end_time: value.result.end_time ?? void 0,
|
|
373
|
-
duration: value.result.duration ?? void 0
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
throw new Error(
|
|
377
|
-
"Black Forest Labs poll response is Ready but missing result.sample"
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
if (status === "Error" || status === "Failed") {
|
|
381
|
-
throw new Error("Black Forest Labs generation failed.");
|
|
382
|
-
}
|
|
383
|
-
await delay(pollIntervalMillis, {
|
|
384
|
-
abortSignal: pollingAbortSignal
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
} catch (error) {
|
|
388
|
-
if (didTimeout) {
|
|
389
|
-
throw new Error("Black Forest Labs generation timed out.");
|
|
390
|
-
}
|
|
391
|
-
throw error;
|
|
392
|
-
} finally {
|
|
393
|
-
clearTimeout(timeoutId);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/black-forest-labs-image-model-options.ts
|
|
44
|
+
const blackForestLabsImageModelOptionsSchema = lazySchema(() => zodSchema(z.object({
|
|
45
|
+
imagePrompt: z.string().optional(),
|
|
46
|
+
imagePromptStrength: z.number().min(0).max(1).optional(),
|
|
47
|
+
/** @deprecated use prompt.images instead */
|
|
48
|
+
inputImage: z.string().optional(),
|
|
49
|
+
/** @deprecated use prompt.images instead */
|
|
50
|
+
inputImage2: z.string().optional(),
|
|
51
|
+
/** @deprecated use prompt.images instead */
|
|
52
|
+
inputImage3: z.string().optional(),
|
|
53
|
+
/** @deprecated use prompt.images instead */
|
|
54
|
+
inputImage4: z.string().optional(),
|
|
55
|
+
/** @deprecated use prompt.images instead */
|
|
56
|
+
inputImage5: z.string().optional(),
|
|
57
|
+
/** @deprecated use prompt.images instead */
|
|
58
|
+
inputImage6: z.string().optional(),
|
|
59
|
+
/** @deprecated use prompt.images instead */
|
|
60
|
+
inputImage7: z.string().optional(),
|
|
61
|
+
/** @deprecated use prompt.images instead */
|
|
62
|
+
inputImage8: z.string().optional(),
|
|
63
|
+
/** @deprecated use prompt.images instead */
|
|
64
|
+
inputImage9: z.string().optional(),
|
|
65
|
+
/** @deprecated use prompt.images instead */
|
|
66
|
+
inputImage10: z.string().optional(),
|
|
67
|
+
steps: z.number().int().positive().optional(),
|
|
68
|
+
guidance: z.number().min(0).optional(),
|
|
69
|
+
width: z.number().int().min(256).max(1920).optional(),
|
|
70
|
+
height: z.number().int().min(256).max(1920).optional(),
|
|
71
|
+
outputFormat: z.enum(["jpeg", "png"]).optional(),
|
|
72
|
+
promptUpsampling: z.boolean().optional(),
|
|
73
|
+
raw: z.boolean().optional(),
|
|
74
|
+
safetyTolerance: z.number().int().min(0).max(6).optional(),
|
|
75
|
+
webhookSecret: z.string().optional(),
|
|
76
|
+
webhookUrl: z.url().optional(),
|
|
77
|
+
pollIntervalMillis: z.number().int().positive().optional(),
|
|
78
|
+
pollTimeoutMillis: z.number().int().positive().optional()
|
|
79
|
+
})));
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/black-forest-labs-image-model.ts
|
|
82
|
+
const DEFAULT_POLL_INTERVAL_MILLIS$1 = 500;
|
|
83
|
+
const DEFAULT_POLL_TIMEOUT_MILLIS$1 = 6e4;
|
|
84
|
+
var BlackForestLabsImageModel = class BlackForestLabsImageModel {
|
|
85
|
+
get supportsFileInputs() {
|
|
86
|
+
if ([
|
|
87
|
+
"flux-kontext-pro",
|
|
88
|
+
"flux-kontext-max",
|
|
89
|
+
"flux-pro-1.0-fill"
|
|
90
|
+
].includes(this.modelId)) return true;
|
|
91
|
+
if (["flux-pro-1.1-ultra", "flux-pro-1.1"].includes(this.modelId)) return false;
|
|
92
|
+
}
|
|
93
|
+
get supportsMaskInputs() {
|
|
94
|
+
if (this.modelId === "flux-pro-1.0-fill") return true;
|
|
95
|
+
return this.supportsFileInputs == null ? void 0 : false;
|
|
96
|
+
}
|
|
97
|
+
get provider() {
|
|
98
|
+
return this.config.provider;
|
|
99
|
+
}
|
|
100
|
+
static [WORKFLOW_SERIALIZE](model) {
|
|
101
|
+
return serializeModelOptions({
|
|
102
|
+
modelId: model.modelId,
|
|
103
|
+
config: model.config
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
static [WORKFLOW_DESERIALIZE](options) {
|
|
107
|
+
return new BlackForestLabsImageModel(options.modelId, options.config);
|
|
108
|
+
}
|
|
109
|
+
constructor(modelId, config) {
|
|
110
|
+
this.modelId = modelId;
|
|
111
|
+
this.config = config;
|
|
112
|
+
this.specificationVersion = "v4";
|
|
113
|
+
this.maxImagesPerCall = 1;
|
|
114
|
+
}
|
|
115
|
+
async getArgs({ prompt, files, mask, size, aspectRatio, seed, providerOptions }) {
|
|
116
|
+
const warnings = [];
|
|
117
|
+
const finalAspectRatio = aspectRatio ?? (size ? convertSizeToAspectRatio(size) : void 0);
|
|
118
|
+
if (size && !aspectRatio) warnings.push({
|
|
119
|
+
type: "unsupported",
|
|
120
|
+
feature: "size",
|
|
121
|
+
details: "Deriving aspect_ratio from size. Use the width and height provider options to specify dimensions for models that support them."
|
|
122
|
+
});
|
|
123
|
+
else if (size && aspectRatio) warnings.push({
|
|
124
|
+
type: "unsupported",
|
|
125
|
+
feature: "size",
|
|
126
|
+
details: "Black Forest Labs ignores size when aspectRatio is provided. Use the width and height provider options to specify dimensions for models that support them"
|
|
127
|
+
});
|
|
128
|
+
const bflOptions = await parseProviderOptions({
|
|
129
|
+
provider: "blackForestLabs",
|
|
130
|
+
providerOptions,
|
|
131
|
+
schema: blackForestLabsImageModelOptionsSchema
|
|
132
|
+
});
|
|
133
|
+
const [widthStr, heightStr] = size?.split("x") ?? [];
|
|
134
|
+
const inputImages = files?.map((file) => {
|
|
135
|
+
if (file.type === "url") return file.url;
|
|
136
|
+
if (typeof file.data === "string") return file.data;
|
|
137
|
+
return Buffer.from(file.data).toString("base64");
|
|
138
|
+
}) || [];
|
|
139
|
+
if (inputImages.length > 10) throw new Error("Black Forest Labs supports up to 10 input images.");
|
|
140
|
+
const inputImageField = this.modelId === "flux-pro-1.0-fill" ? "image" : "input_image";
|
|
141
|
+
const inputImagesObj = inputImages.reduce((acc, img, index) => {
|
|
142
|
+
acc[`${inputImageField}${index === 0 ? "" : `_${index + 1}`}`] = img;
|
|
143
|
+
return acc;
|
|
144
|
+
}, {});
|
|
145
|
+
let maskValue;
|
|
146
|
+
if (mask) {
|
|
147
|
+
if (mask.type === "url") maskValue = mask.url;
|
|
148
|
+
else if (typeof mask.data === "string") maskValue = mask.data;
|
|
149
|
+
else maskValue = Buffer.from(mask.data).toString("base64");
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
body: {
|
|
153
|
+
prompt,
|
|
154
|
+
seed,
|
|
155
|
+
aspect_ratio: finalAspectRatio,
|
|
156
|
+
width: bflOptions?.width ?? (size ? Number(widthStr) : void 0),
|
|
157
|
+
height: bflOptions?.height ?? (size ? Number(heightStr) : void 0),
|
|
158
|
+
steps: bflOptions?.steps,
|
|
159
|
+
guidance: bflOptions?.guidance,
|
|
160
|
+
image_prompt_strength: bflOptions?.imagePromptStrength,
|
|
161
|
+
image_prompt: bflOptions?.imagePrompt,
|
|
162
|
+
...inputImagesObj,
|
|
163
|
+
mask: maskValue,
|
|
164
|
+
output_format: bflOptions?.outputFormat,
|
|
165
|
+
prompt_upsampling: bflOptions?.promptUpsampling,
|
|
166
|
+
raw: bflOptions?.raw,
|
|
167
|
+
safety_tolerance: bflOptions?.safetyTolerance,
|
|
168
|
+
webhook_secret: bflOptions?.webhookSecret,
|
|
169
|
+
webhook_url: bflOptions?.webhookUrl
|
|
170
|
+
},
|
|
171
|
+
warnings,
|
|
172
|
+
bflOptions
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
async doGenerate({ prompt, files, mask, size, aspectRatio, seed, providerOptions, headers, abortSignal }) {
|
|
176
|
+
const { body, warnings, bflOptions } = await this.getArgs({
|
|
177
|
+
prompt,
|
|
178
|
+
files,
|
|
179
|
+
mask,
|
|
180
|
+
size,
|
|
181
|
+
aspectRatio,
|
|
182
|
+
seed,
|
|
183
|
+
providerOptions,
|
|
184
|
+
n: 1,
|
|
185
|
+
headers,
|
|
186
|
+
abortSignal
|
|
187
|
+
});
|
|
188
|
+
const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
|
|
189
|
+
const combinedHeaders = combineHeaders(await resolve(this.config.headers), headers);
|
|
190
|
+
const submit = await postJsonToApi({
|
|
191
|
+
url: `${this.config.baseURL}/${this.modelId}`,
|
|
192
|
+
headers: combinedHeaders,
|
|
193
|
+
body,
|
|
194
|
+
failedResponseHandler: bflFailedResponseHandler,
|
|
195
|
+
successfulResponseHandler: createJsonResponseHandler(bflSubmitSchema),
|
|
196
|
+
abortSignal,
|
|
197
|
+
fetch: this.config.fetch
|
|
198
|
+
});
|
|
199
|
+
const pollUrl = submit.value.polling_url;
|
|
200
|
+
const requestId = submit.value.id;
|
|
201
|
+
const { imageUrl, seed: resultSeed, start_time: resultStartTime, end_time: resultEndTime, duration: resultDuration } = await this.pollForImageUrl({
|
|
202
|
+
pollUrl,
|
|
203
|
+
requestId,
|
|
204
|
+
headers: combinedHeaders,
|
|
205
|
+
abortSignal,
|
|
206
|
+
pollOverrides: {
|
|
207
|
+
pollIntervalMillis: bflOptions?.pollIntervalMillis,
|
|
208
|
+
pollTimeoutMillis: bflOptions?.pollTimeoutMillis
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
const { value: imageBytes, responseHeaders } = await getFromApi({
|
|
212
|
+
url: imageUrl,
|
|
213
|
+
validateUrl: true,
|
|
214
|
+
trustedOrigin: this.config.baseURL,
|
|
215
|
+
headers: isTrustedUrl(imageUrl, this.config.baseURL) ? combinedHeaders : void 0,
|
|
216
|
+
abortSignal,
|
|
217
|
+
failedResponseHandler: createStatusCodeErrorResponseHandler(),
|
|
218
|
+
successfulResponseHandler: createBinaryResponseHandler(),
|
|
219
|
+
fetch: this.config.fetch
|
|
220
|
+
});
|
|
221
|
+
return {
|
|
222
|
+
images: [imageBytes],
|
|
223
|
+
warnings,
|
|
224
|
+
providerMetadata: { blackForestLabs: { images: [{
|
|
225
|
+
...resultSeed != null && { seed: resultSeed },
|
|
226
|
+
...resultStartTime != null && { start_time: resultStartTime },
|
|
227
|
+
...resultEndTime != null && { end_time: resultEndTime },
|
|
228
|
+
...resultDuration != null && { duration: resultDuration },
|
|
229
|
+
...submit.value.cost != null && { cost: submit.value.cost },
|
|
230
|
+
...submit.value.input_mp != null && { inputMegapixels: submit.value.input_mp },
|
|
231
|
+
...submit.value.output_mp != null && { outputMegapixels: submit.value.output_mp }
|
|
232
|
+
}] } },
|
|
233
|
+
response: {
|
|
234
|
+
modelId: this.modelId,
|
|
235
|
+
timestamp: currentDate,
|
|
236
|
+
headers: responseHeaders
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
async pollForImageUrl({ pollUrl, requestId, headers, abortSignal, pollOverrides }) {
|
|
241
|
+
const pollIntervalMillis = pollOverrides?.pollIntervalMillis ?? this.config.pollIntervalMillis ?? DEFAULT_POLL_INTERVAL_MILLIS$1;
|
|
242
|
+
const pollTimeoutMillis = pollOverrides?.pollTimeoutMillis ?? this.config.pollTimeoutMillis ?? DEFAULT_POLL_TIMEOUT_MILLIS$1;
|
|
243
|
+
const url = new URL(pollUrl);
|
|
244
|
+
if (!url.searchParams.has("id")) url.searchParams.set("id", requestId);
|
|
245
|
+
const timeoutController = new AbortController();
|
|
246
|
+
let didTimeout = false;
|
|
247
|
+
const timeoutId = setTimeout(() => {
|
|
248
|
+
didTimeout = true;
|
|
249
|
+
timeoutController.abort();
|
|
250
|
+
}, pollTimeoutMillis);
|
|
251
|
+
const pollingAbortSignal = abortSignal == null ? timeoutController.signal : AbortSignal.any([abortSignal, timeoutController.signal]);
|
|
252
|
+
try {
|
|
253
|
+
while (true) {
|
|
254
|
+
const { value } = await getFromApi({
|
|
255
|
+
url: url.toString(),
|
|
256
|
+
validateUrl: true,
|
|
257
|
+
trustedOrigin: this.config.baseURL,
|
|
258
|
+
headers: isTrustedUrl(url.toString(), this.config.baseURL) ? headers : void 0,
|
|
259
|
+
failedResponseHandler: bflFailedResponseHandler,
|
|
260
|
+
successfulResponseHandler: createJsonResponseHandler(bflPollSchema),
|
|
261
|
+
abortSignal: pollingAbortSignal,
|
|
262
|
+
fetch: this.config.fetch
|
|
263
|
+
});
|
|
264
|
+
const status = value.status;
|
|
265
|
+
if (status === "Ready") {
|
|
266
|
+
if (typeof value.result?.sample === "string") return {
|
|
267
|
+
imageUrl: value.result.sample,
|
|
268
|
+
seed: value.result.seed ?? void 0,
|
|
269
|
+
start_time: value.result.start_time ?? void 0,
|
|
270
|
+
end_time: value.result.end_time ?? void 0,
|
|
271
|
+
duration: value.result.duration ?? void 0
|
|
272
|
+
};
|
|
273
|
+
throw new Error("Black Forest Labs poll response is Ready but missing result.sample");
|
|
274
|
+
}
|
|
275
|
+
if (status === "Error" || status === "Failed") throw new Error("Black Forest Labs generation failed.");
|
|
276
|
+
await delay(pollIntervalMillis, { abortSignal: pollingAbortSignal });
|
|
277
|
+
}
|
|
278
|
+
} catch (error) {
|
|
279
|
+
if (didTimeout) throw new Error("Black Forest Labs generation timed out.");
|
|
280
|
+
throw error;
|
|
281
|
+
} finally {
|
|
282
|
+
clearTimeout(timeoutId);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
396
285
|
};
|
|
397
286
|
function convertSizeToAspectRatio(size) {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const g = gcd(width, height);
|
|
405
|
-
return `${Math.round(width / g)}:${Math.round(height / g)}`;
|
|
287
|
+
const [wStr, hStr] = size.split("x");
|
|
288
|
+
const width = Number(wStr);
|
|
289
|
+
const height = Number(hStr);
|
|
290
|
+
if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return;
|
|
291
|
+
const g = gcd(width, height);
|
|
292
|
+
return `${Math.round(width / g)}:${Math.round(height / g)}`;
|
|
406
293
|
}
|
|
407
294
|
function gcd(a, b) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
295
|
+
let x = Math.abs(a);
|
|
296
|
+
let y = Math.abs(b);
|
|
297
|
+
while (y !== 0) {
|
|
298
|
+
const t = y;
|
|
299
|
+
y = x % y;
|
|
300
|
+
x = t;
|
|
301
|
+
}
|
|
302
|
+
return x;
|
|
416
303
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
304
|
+
const bflSubmitSchema = z.object({
|
|
305
|
+
id: z.string(),
|
|
306
|
+
polling_url: z.url(),
|
|
307
|
+
cost: z.number().nullish(),
|
|
308
|
+
input_mp: z.number().nullish(),
|
|
309
|
+
output_mp: z.number().nullish()
|
|
423
310
|
});
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
311
|
+
const bflStatus = z.union([
|
|
312
|
+
z.literal("Pending"),
|
|
313
|
+
z.literal("Ready"),
|
|
314
|
+
z.literal("Error"),
|
|
315
|
+
z.literal("Failed"),
|
|
316
|
+
z.literal("Request Moderated")
|
|
430
317
|
]);
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
}).refine((v) => v.status != null || v.state != null, {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
status: v.status ?? v.state,
|
|
446
|
-
result: v.result
|
|
318
|
+
const bflPollSchema = z.object({
|
|
319
|
+
status: bflStatus.optional(),
|
|
320
|
+
state: bflStatus.optional(),
|
|
321
|
+
details: z.unknown().optional(),
|
|
322
|
+
result: z.object({
|
|
323
|
+
sample: z.url(),
|
|
324
|
+
seed: z.number().optional(),
|
|
325
|
+
start_time: z.number().optional(),
|
|
326
|
+
end_time: z.number().optional(),
|
|
327
|
+
duration: z.number().optional()
|
|
328
|
+
}).nullish()
|
|
329
|
+
}).refine((v) => v.status != null || v.state != null, { message: "Missing status in Black Forest Labs poll response" }).transform((v) => ({
|
|
330
|
+
status: v.status ?? v.state,
|
|
331
|
+
result: v.result
|
|
447
332
|
}));
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
serializeModelOptions as serializeModelOptions2,
|
|
464
|
-
WORKFLOW_DESERIALIZE as WORKFLOW_DESERIALIZE2,
|
|
465
|
-
WORKFLOW_SERIALIZE as WORKFLOW_SERIALIZE2
|
|
466
|
-
} from "@ai-sdk/provider-utils";
|
|
467
|
-
import { z as z5 } from "zod/v4";
|
|
468
|
-
|
|
469
|
-
// src/black-forest-labs-video-model-options.ts
|
|
470
|
-
import {
|
|
471
|
-
lazySchema as lazySchema2,
|
|
472
|
-
zodSchema as zodSchema2
|
|
473
|
-
} from "@ai-sdk/provider-utils";
|
|
474
|
-
import { z as z4 } from "zod/v4";
|
|
475
|
-
|
|
476
|
-
// src/black-forest-labs-video-settings.ts
|
|
477
|
-
var blackForestLabsVideoAspectRatios = [
|
|
478
|
-
"21:9",
|
|
479
|
-
"2:1",
|
|
480
|
-
"16:9",
|
|
481
|
-
"4:3",
|
|
482
|
-
"1:1",
|
|
483
|
-
"3:4",
|
|
484
|
-
"9:16",
|
|
485
|
-
"auto"
|
|
333
|
+
//#endregion
|
|
334
|
+
//#region src/black-forest-labs-video-settings.ts
|
|
335
|
+
/**
|
|
336
|
+
* Aspect ratios FLUX 3 video accepts. `auto` lets the model infer the ratio
|
|
337
|
+
* from the prompt and any conditioning media, and is the API default.
|
|
338
|
+
*/
|
|
339
|
+
const blackForestLabsVideoAspectRatios = [
|
|
340
|
+
"21:9",
|
|
341
|
+
"2:1",
|
|
342
|
+
"16:9",
|
|
343
|
+
"4:3",
|
|
344
|
+
"1:1",
|
|
345
|
+
"3:4",
|
|
346
|
+
"9:16",
|
|
347
|
+
"auto"
|
|
486
348
|
];
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
]);
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
* Either the base64-encoded `.bin` downloaded from the draft's `draftCache`
|
|
541
|
-
* URL, or that URL itself while it is still in its expiry window.
|
|
542
|
-
*/
|
|
543
|
-
draftCache: z4.string().optional(),
|
|
544
|
-
/**
|
|
545
|
-
* Model version to pin. Only `latest` is available today.
|
|
546
|
-
*/
|
|
547
|
-
version: z4.literal("latest").optional()
|
|
349
|
+
/**
|
|
350
|
+
* Output resolution tiers FLUX 3 video accepts.
|
|
351
|
+
*/
|
|
352
|
+
const blackForestLabsVideoResolutions = ["hd", "fhd"];
|
|
353
|
+
//#endregion
|
|
354
|
+
//#region src/black-forest-labs-video-model-options.ts
|
|
355
|
+
const blackForestLabsTimedVideoKeyframeSchema = z.tuple([z.number().min(0).max(20), z.string()]);
|
|
356
|
+
const blackForestLabsVideoKeyframesSchema = z.union([z.array(z.string()).min(1).max(10), z.array(blackForestLabsTimedVideoKeyframeSchema).min(1).max(10).refine((keyframes) => keyframes.every((keyframe, index) => index === 0 || keyframe[0] > keyframes[index - 1][0]), { message: "Timed keyframes must be in chronological order." })]);
|
|
357
|
+
const blackForestLabsVideoProviderOptions = z.object({
|
|
358
|
+
/**
|
|
359
|
+
* Output resolution tier. Takes precedence over the top-level `resolution`,
|
|
360
|
+
* which is expressed as `{width}x{height}` and has to be mapped onto a tier.
|
|
361
|
+
*/
|
|
362
|
+
resolution: z.enum(blackForestLabsVideoResolutions).optional(),
|
|
363
|
+
/**
|
|
364
|
+
* Aspect ratio of the generated video. Takes precedence over the top-level
|
|
365
|
+
* `aspectRatio`, and unlike it can be set to `auto`.
|
|
366
|
+
*/
|
|
367
|
+
aspectRatio: z.enum(blackForestLabsVideoAspectRatios).optional(),
|
|
368
|
+
/**
|
|
369
|
+
* Keyframes for image-to-video generation, for the shapes the top-level
|
|
370
|
+
* `image` and `frameImages` options cannot express: more than two images, or
|
|
371
|
+
* images pinned to a specific second. Takes precedence over both.
|
|
372
|
+
*
|
|
373
|
+
* One image opens the clip, two open and close it, and with more the extras
|
|
374
|
+
* are spaced evenly in between. Three or more plain images require an
|
|
375
|
+
* explicit `duration`.
|
|
376
|
+
*/
|
|
377
|
+
keyframes: blackForestLabsVideoKeyframesSchema.optional(),
|
|
378
|
+
/**
|
|
379
|
+
* Moderation strictness from 0 (strictest) to 4. Defaults to 2. Sexual
|
|
380
|
+
* content is capped at 3 and hate content at 2 regardless of the request,
|
|
381
|
+
* and any request carrying conditioning media is capped at 2.
|
|
382
|
+
*/
|
|
383
|
+
safetyTolerance: z.number().int().min(0).max(4).optional(),
|
|
384
|
+
/**
|
|
385
|
+
* Render a fast, lower-quality preview instead of the finished video.
|
|
386
|
+
* Defaults to `false`.
|
|
387
|
+
*/
|
|
388
|
+
draft: z.boolean().optional(),
|
|
389
|
+
/**
|
|
390
|
+
* Encrypted draft-cache bundle from a prior `draft` generation, which
|
|
391
|
+
* switches the request to draft-enhance mode: the bundle is replayed at full
|
|
392
|
+
* quality.
|
|
393
|
+
*
|
|
394
|
+
* Either the base64-encoded `.bin` downloaded from the draft's `draftCache`
|
|
395
|
+
* URL, or that URL itself while it is still in its expiry window.
|
|
396
|
+
*/
|
|
397
|
+
draftCache: z.string().optional(),
|
|
398
|
+
/**
|
|
399
|
+
* Model version to pin. Only `latest` is available today.
|
|
400
|
+
*/
|
|
401
|
+
version: z.literal("latest").optional()
|
|
548
402
|
});
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
403
|
+
const blackForestLabsVideoModelOptionsSchema = lazySchema(() => zodSchema(blackForestLabsVideoProviderOptions));
|
|
404
|
+
//#endregion
|
|
405
|
+
//#region src/black-forest-labs-video-model.ts
|
|
406
|
+
const DEFAULT_POLL_INTERVAL_MILLIS = 2e3;
|
|
407
|
+
const DEFAULT_POLL_TIMEOUT_MILLIS = 6e5;
|
|
408
|
+
const MIN_DURATION_SECONDS = 5;
|
|
409
|
+
const MAX_DURATION_SECONDS = 20;
|
|
410
|
+
/** Keyframe counts above this need an explicit duration when untimed. */
|
|
411
|
+
const UNTIMED_KEYFRAMES_NEEDING_DURATION = 3;
|
|
412
|
+
const allowedAspectRatios = new Set(blackForestLabsVideoAspectRatios);
|
|
413
|
+
const allowedResolutions = new Set(blackForestLabsVideoResolutions);
|
|
414
|
+
/**
|
|
415
|
+
* Statuses that end the poll without a video.
|
|
416
|
+
*/
|
|
417
|
+
const TERMINAL_FAILURE_STATUSES = /* @__PURE__ */ new Set([
|
|
418
|
+
"Error",
|
|
419
|
+
"Failed",
|
|
420
|
+
"Request Moderated",
|
|
421
|
+
"Content Moderated",
|
|
422
|
+
"Task not found"
|
|
567
423
|
]);
|
|
424
|
+
/**
|
|
425
|
+
* The API takes a named tier (`hd`/`fhd`) while the top-level `resolution` is
|
|
426
|
+
* `{width}x{height}`, so a caller may reasonably pass either.
|
|
427
|
+
*/
|
|
568
428
|
function resolveTopLevelResolution(resolution) {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
};
|
|
429
|
+
const named = resolution.toLowerCase();
|
|
430
|
+
if (allowedResolutions.has(named)) return {
|
|
431
|
+
tier: named,
|
|
432
|
+
derived: false
|
|
433
|
+
};
|
|
434
|
+
const match = resolution.match(/^(\d+)x(\d+)$/);
|
|
435
|
+
if (match == null) return;
|
|
436
|
+
const shorterSide = Math.min(Number(match[1]), Number(match[2]));
|
|
437
|
+
return {
|
|
438
|
+
tier: shorterSide <= 720 ? "hd" : "fhd",
|
|
439
|
+
derived: shorterSide !== 720 && shorterSide !== 1080
|
|
440
|
+
};
|
|
582
441
|
}
|
|
583
442
|
function nonImageFrameMediaType(file) {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
const topLevelMediaType = getTopLevelMediaType(file.mediaType);
|
|
588
|
-
return topLevelMediaType === "image" ? void 0 : topLevelMediaType;
|
|
443
|
+
if (file.mediaType == null) return;
|
|
444
|
+
const topLevelMediaType = getTopLevelMediaType(file.mediaType);
|
|
445
|
+
return topLevelMediaType === "image" ? void 0 : topLevelMediaType;
|
|
589
446
|
}
|
|
447
|
+
/**
|
|
448
|
+
* Draft-enhance replays an encrypted bundle from a prior `draft` generation at
|
|
449
|
+
* full quality. The bundle pins the original mode, prompt, seed, and
|
|
450
|
+
* conditioning media, and the API accepts nothing alongside it but
|
|
451
|
+
* `safety_tolerance` — so this is a separate body rather than one more field,
|
|
452
|
+
* and everything else the caller set is reported as dropped.
|
|
453
|
+
*/
|
|
590
454
|
function getDraftEnhanceArgs(options, bflOptions) {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
details: "FLUX 3 video generates a single video per call. Only 1 video will be generated."
|
|
630
|
-
});
|
|
631
|
-
}
|
|
632
|
-
return {
|
|
633
|
-
body: {
|
|
634
|
-
mode: "draft_enhance",
|
|
635
|
-
draft_cache: bflOptions.draftCache,
|
|
636
|
-
safety_tolerance: bflOptions.safetyTolerance
|
|
637
|
-
},
|
|
638
|
-
warnings
|
|
639
|
-
};
|
|
455
|
+
const warnings = [];
|
|
456
|
+
const pinnedByBundle = [
|
|
457
|
+
["prompt", (options.prompt?.trim().length ?? 0) > 0],
|
|
458
|
+
["aspectRatio", options.aspectRatio != null || bflOptions.aspectRatio != null],
|
|
459
|
+
["resolution", options.resolution != null || bflOptions.resolution != null],
|
|
460
|
+
["duration", options.duration != null],
|
|
461
|
+
["fps", options.fps != null],
|
|
462
|
+
["seed", options.seed != null],
|
|
463
|
+
["generateAudio", options.generateAudio != null],
|
|
464
|
+
["image", options.image != null],
|
|
465
|
+
["frameImages", (options.frameImages?.length ?? 0) > 0],
|
|
466
|
+
["inputReferences", (options.inputReferences?.length ?? 0) > 0],
|
|
467
|
+
["keyframes", (bflOptions.keyframes?.length ?? 0) > 0],
|
|
468
|
+
["version", bflOptions.version != null]
|
|
469
|
+
];
|
|
470
|
+
for (const [feature, isSet] of pinnedByBundle) if (isSet) warnings.push({
|
|
471
|
+
type: "unsupported",
|
|
472
|
+
feature,
|
|
473
|
+
details: `FLUX 3 draft enhance replays the draft bundle as it was generated, so "${feature}" was ignored. Set it on the original draft request instead.`
|
|
474
|
+
});
|
|
475
|
+
if (bflOptions.draft != null) warnings.push({
|
|
476
|
+
type: "unsupported",
|
|
477
|
+
feature: "draft",
|
|
478
|
+
details: "FLUX 3 draft enhance always renders at full quality. The draft option was ignored."
|
|
479
|
+
});
|
|
480
|
+
if (options.n != null && options.n > 1) warnings.push({
|
|
481
|
+
type: "unsupported",
|
|
482
|
+
feature: "n",
|
|
483
|
+
details: "FLUX 3 video generates a single video per call. Only 1 video will be generated."
|
|
484
|
+
});
|
|
485
|
+
return {
|
|
486
|
+
body: {
|
|
487
|
+
mode: "draft_enhance",
|
|
488
|
+
draft_cache: bflOptions.draftCache,
|
|
489
|
+
safety_tolerance: bflOptions.safetyTolerance
|
|
490
|
+
},
|
|
491
|
+
warnings
|
|
492
|
+
};
|
|
640
493
|
}
|
|
494
|
+
/**
|
|
495
|
+
* A failed poll carries a `details` payload that is a plain string for
|
|
496
|
+
* moderation refusals and an object for everything else.
|
|
497
|
+
*/
|
|
641
498
|
function describePollDetails(details) {
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
return JSON.stringify(details);
|
|
650
|
-
} catch {
|
|
651
|
-
return void 0;
|
|
652
|
-
}
|
|
499
|
+
if (details == null) return;
|
|
500
|
+
if (typeof details === "string") return details;
|
|
501
|
+
try {
|
|
502
|
+
return JSON.stringify(details);
|
|
503
|
+
} catch {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
653
506
|
}
|
|
507
|
+
/**
|
|
508
|
+
* The API takes conditioning media as an http(s) URL or a bare base64 string —
|
|
509
|
+
* not a data URI.
|
|
510
|
+
*/
|
|
654
511
|
function toBlackForestLabsFile(file) {
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
if (typeof file.data === "string") {
|
|
659
|
-
return file.data;
|
|
660
|
-
}
|
|
661
|
-
return Buffer.from(file.data).toString("base64");
|
|
512
|
+
if (file.type === "url") return file.url;
|
|
513
|
+
if (typeof file.data === "string") return file.data;
|
|
514
|
+
return Buffer.from(file.data).toString("base64");
|
|
662
515
|
}
|
|
663
|
-
var BlackForestLabsVideoModel = class
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
},
|
|
994
|
-
...parsed.data.end_time != null && {
|
|
995
|
-
end_time: parsed.data.end_time
|
|
996
|
-
},
|
|
997
|
-
...parsed.data.duration != null && {
|
|
998
|
-
duration: parsed.data.duration
|
|
999
|
-
},
|
|
1000
|
-
...parsed.data.draft_cache != null && {
|
|
1001
|
-
draftCache: parsed.data.draft_cache
|
|
1002
|
-
},
|
|
1003
|
-
...cost != null && { cost },
|
|
1004
|
-
...operation.inputMegapixels != null && {
|
|
1005
|
-
inputMegapixels: operation.inputMegapixels
|
|
1006
|
-
},
|
|
1007
|
-
...operation.outputMegapixels != null && {
|
|
1008
|
-
outputMegapixels: operation.outputMegapixels
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
]
|
|
1012
|
-
}
|
|
1013
|
-
},
|
|
1014
|
-
response
|
|
1015
|
-
};
|
|
1016
|
-
}
|
|
1017
|
-
if (TERMINAL_FAILURE_STATUSES.has(status)) {
|
|
1018
|
-
const detail = describePollDetails(value.details);
|
|
1019
|
-
return {
|
|
1020
|
-
status: "error",
|
|
1021
|
-
error: `Black Forest Labs video generation failed with status "${status}"${detail != null ? `: ${detail}` : ""}. Request id: ${operation.requestId}`,
|
|
1022
|
-
response
|
|
1023
|
-
};
|
|
1024
|
-
}
|
|
1025
|
-
return { status: "pending", response };
|
|
1026
|
-
}
|
|
1027
|
-
async doGenerate(options) {
|
|
1028
|
-
const startResult = await this.doStart(options);
|
|
1029
|
-
const operation = startResult.operation;
|
|
1030
|
-
const pollIntervalMillis = this.config.pollIntervalMillis ?? DEFAULT_POLL_INTERVAL_MILLIS2;
|
|
1031
|
-
const pollTimeoutMillis = this.config.pollTimeoutMillis ?? DEFAULT_POLL_TIMEOUT_MILLIS2;
|
|
1032
|
-
const startTime = Date.now();
|
|
1033
|
-
while (true) {
|
|
1034
|
-
await delay2(pollIntervalMillis, { abortSignal: options.abortSignal });
|
|
1035
|
-
if (Date.now() - startTime > pollTimeoutMillis) {
|
|
1036
|
-
throw new AISDKError({
|
|
1037
|
-
name: "BLACK_FOREST_LABS_VIDEO_GENERATION_TIMEOUT",
|
|
1038
|
-
message: `Black Forest Labs video generation timed out after ${pollTimeoutMillis}ms. Request id: ${operation.requestId}`
|
|
1039
|
-
});
|
|
1040
|
-
}
|
|
1041
|
-
const statusResult = await this.doStatus({
|
|
1042
|
-
operation,
|
|
1043
|
-
headers: options.headers,
|
|
1044
|
-
abortSignal: options.abortSignal
|
|
1045
|
-
});
|
|
1046
|
-
if (statusResult.status === "pending") {
|
|
1047
|
-
continue;
|
|
1048
|
-
}
|
|
1049
|
-
if (statusResult.status === "error") {
|
|
1050
|
-
throw new AISDKError({
|
|
1051
|
-
name: "BLACK_FOREST_LABS_VIDEO_GENERATION_FAILED",
|
|
1052
|
-
message: statusResult.error
|
|
1053
|
-
});
|
|
1054
|
-
}
|
|
1055
|
-
return {
|
|
1056
|
-
videos: statusResult.videos,
|
|
1057
|
-
warnings: [...startResult.warnings, ...statusResult.warnings],
|
|
1058
|
-
providerMetadata: statusResult.providerMetadata,
|
|
1059
|
-
response: statusResult.response
|
|
1060
|
-
};
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
516
|
+
var BlackForestLabsVideoModel = class BlackForestLabsVideoModel {
|
|
517
|
+
get provider() {
|
|
518
|
+
return this.config.provider;
|
|
519
|
+
}
|
|
520
|
+
static [WORKFLOW_SERIALIZE](model) {
|
|
521
|
+
return serializeModelOptions({
|
|
522
|
+
modelId: model.modelId,
|
|
523
|
+
config: model.config
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
static [WORKFLOW_DESERIALIZE](options) {
|
|
527
|
+
return new BlackForestLabsVideoModel(options.modelId, options.config);
|
|
528
|
+
}
|
|
529
|
+
constructor(modelId, config) {
|
|
530
|
+
this.modelId = modelId;
|
|
531
|
+
this.config = config;
|
|
532
|
+
this.specificationVersion = "v4";
|
|
533
|
+
this.maxVideosPerCall = 1;
|
|
534
|
+
}
|
|
535
|
+
async getArgs(options) {
|
|
536
|
+
const warnings = [];
|
|
537
|
+
const bflOptions = await parseProviderOptions({
|
|
538
|
+
provider: "blackForestLabs",
|
|
539
|
+
providerOptions: options.providerOptions,
|
|
540
|
+
schema: blackForestLabsVideoModelOptionsSchema
|
|
541
|
+
});
|
|
542
|
+
if (bflOptions?.draftCache != null) return getDraftEnhanceArgs(options, bflOptions);
|
|
543
|
+
if (options.fps != null) warnings.push({
|
|
544
|
+
type: "unsupported",
|
|
545
|
+
feature: "fps",
|
|
546
|
+
details: "FLUX 3 video does not support a custom frame rate."
|
|
547
|
+
});
|
|
548
|
+
if (options.seed != null) warnings.push({
|
|
549
|
+
type: "unsupported",
|
|
550
|
+
feature: "seed",
|
|
551
|
+
details: "FLUX 3 video does not accept a seed."
|
|
552
|
+
});
|
|
553
|
+
if (options.n != null && options.n > 1) warnings.push({
|
|
554
|
+
type: "unsupported",
|
|
555
|
+
feature: "n",
|
|
556
|
+
details: "FLUX 3 video generates a single video per call. Only 1 video will be generated."
|
|
557
|
+
});
|
|
558
|
+
let resolution = bflOptions?.resolution;
|
|
559
|
+
if (options.resolution != null) {
|
|
560
|
+
const resolved = resolveTopLevelResolution(options.resolution);
|
|
561
|
+
if (resolution != null) {
|
|
562
|
+
if (resolved == null) warnings.push({
|
|
563
|
+
type: "unsupported",
|
|
564
|
+
feature: "resolution",
|
|
565
|
+
details: `Unrecognized resolution "${options.resolution}". FLUX 3 video supports "hd" and "fhd", so providerOptions.blackForestLabs.resolution ("${resolution}") was used instead.`
|
|
566
|
+
});
|
|
567
|
+
} else if (resolved == null) warnings.push({
|
|
568
|
+
type: "unsupported",
|
|
569
|
+
feature: "resolution",
|
|
570
|
+
details: `Unrecognized resolution "${options.resolution}". FLUX 3 video supports "hd" and "fhd", or a {width}x{height} value to map onto one.`
|
|
571
|
+
});
|
|
572
|
+
else {
|
|
573
|
+
resolution = resolved.tier;
|
|
574
|
+
if (resolved.derived) warnings.push({
|
|
575
|
+
type: "compatibility",
|
|
576
|
+
feature: "resolution",
|
|
577
|
+
details: `FLUX 3 video renders at "hd" or "fhd"; the requested resolution "${options.resolution}" was mapped to "${resolved.tier}".`
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
let aspectRatio = bflOptions?.aspectRatio;
|
|
582
|
+
if (aspectRatio == null && options.aspectRatio != null) {
|
|
583
|
+
if (allowedAspectRatios.has(options.aspectRatio)) aspectRatio = options.aspectRatio;
|
|
584
|
+
else warnings.push({
|
|
585
|
+
type: "unsupported",
|
|
586
|
+
feature: "aspectRatio",
|
|
587
|
+
details: `FLUX 3 video does not support the aspect ratio "${options.aspectRatio}". Using the provider default (auto).`
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
const firstFrameImage = options.frameImages?.find((frame) => frame.frameType === "first_frame")?.image;
|
|
591
|
+
let firstFrame = firstFrameImage ?? options.image;
|
|
592
|
+
let lastFrame = options.frameImages?.find((frame) => frame.frameType === "last_frame")?.image;
|
|
593
|
+
const firstFrameMediaType = firstFrame != null ? nonImageFrameMediaType(firstFrame) : void 0;
|
|
594
|
+
if (firstFrame != null && firstFrameMediaType != null) {
|
|
595
|
+
warnings.push({
|
|
596
|
+
type: "unsupported",
|
|
597
|
+
feature: firstFrameImage != null ? "frameImages" : "image",
|
|
598
|
+
details: firstFrameMediaType === "video" ? "FLUX 3 video does not accept a video as a keyframe. Pass it as an inputReference to continue from it instead." : `FLUX 3 video only accepts an image as a keyframe; the "${firstFrame.mediaType}" file was ignored.`
|
|
599
|
+
});
|
|
600
|
+
firstFrame = void 0;
|
|
601
|
+
}
|
|
602
|
+
if (lastFrame != null) {
|
|
603
|
+
if (firstFrame == null) {
|
|
604
|
+
warnings.push({
|
|
605
|
+
type: "unsupported",
|
|
606
|
+
feature: "frameImages",
|
|
607
|
+
details: "FLUX 3 video requires a first_frame when a last_frame is provided. The last_frame was ignored."
|
|
608
|
+
});
|
|
609
|
+
lastFrame = void 0;
|
|
610
|
+
} else {
|
|
611
|
+
const lastFrameMediaType = nonImageFrameMediaType(lastFrame);
|
|
612
|
+
if (lastFrameMediaType != null) {
|
|
613
|
+
warnings.push({
|
|
614
|
+
type: "unsupported",
|
|
615
|
+
feature: "frameImages",
|
|
616
|
+
details: lastFrameMediaType === "video" ? "FLUX 3 video does not accept a video as a keyframe. The last_frame video was ignored." : `FLUX 3 video only accepts an image as a keyframe; the "${lastFrame.mediaType}" last_frame was ignored.`
|
|
617
|
+
});
|
|
618
|
+
lastFrame = void 0;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
let keyframes = bflOptions?.keyframes != null && bflOptions.keyframes.length > 0 ? [...bflOptions.keyframes] : void 0;
|
|
623
|
+
if (keyframes != null) {
|
|
624
|
+
if (firstFrame != null || lastFrame != null) warnings.push({
|
|
625
|
+
type: "unsupported",
|
|
626
|
+
feature: options.frameImages != null ? "frameImages" : "image",
|
|
627
|
+
details: "FLUX 3 video takes a single keyframe list. providerOptions.blackForestLabs.keyframes was used and the top-level frame images were ignored."
|
|
628
|
+
});
|
|
629
|
+
} else if (firstFrame != null) {
|
|
630
|
+
keyframes = [toBlackForestLabsFile(firstFrame)];
|
|
631
|
+
if (lastFrame != null) keyframes.push(toBlackForestLabsFile(lastFrame));
|
|
632
|
+
}
|
|
633
|
+
let startVideo;
|
|
634
|
+
const referenceFiles = options.inputReferences ?? [];
|
|
635
|
+
const referenceVideos = [];
|
|
636
|
+
for (const file of referenceFiles) {
|
|
637
|
+
const topLevelMediaType = file.mediaType != null ? getTopLevelMediaType(file.mediaType) : void 0;
|
|
638
|
+
if (topLevelMediaType === "video") referenceVideos.push(file);
|
|
639
|
+
else if (topLevelMediaType == null) {
|
|
640
|
+
warnings.push({
|
|
641
|
+
type: "compatibility",
|
|
642
|
+
feature: "inputReferences",
|
|
643
|
+
details: "FLUX 3 video only accepts a video reference, so the reference with no mediaType was treated as the video to continue from. Pass { url, mediaType: \"video/mp4\" } to be explicit."
|
|
644
|
+
});
|
|
645
|
+
referenceVideos.push(file);
|
|
646
|
+
} else if (topLevelMediaType === "image") warnings.push({
|
|
647
|
+
type: "unsupported",
|
|
648
|
+
feature: "inputReferences",
|
|
649
|
+
details: "FLUX 3 video has no reference-image input. Pass images as `image`, `frameImages`, or providerOptions.blackForestLabs.keyframes instead. The reference was ignored."
|
|
650
|
+
});
|
|
651
|
+
else warnings.push({
|
|
652
|
+
type: "unsupported",
|
|
653
|
+
feature: "inputReferences",
|
|
654
|
+
details: `FLUX 3 video only accepts a video reference; the "${file.mediaType}" reference was ignored.`
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
if (referenceVideos.length > 0) {
|
|
658
|
+
if (keyframes != null) warnings.push({
|
|
659
|
+
type: "unsupported",
|
|
660
|
+
feature: "inputReferences",
|
|
661
|
+
details: "FLUX 3 video cannot combine keyframes with a video to continue from. The video reference was ignored."
|
|
662
|
+
});
|
|
663
|
+
else {
|
|
664
|
+
startVideo = toBlackForestLabsFile(referenceVideos[0]);
|
|
665
|
+
if (referenceVideos.length > 1) warnings.push({
|
|
666
|
+
type: "unsupported",
|
|
667
|
+
feature: "inputReferences",
|
|
668
|
+
details: "FLUX 3 video continues from a single video. Only the first video reference was used."
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
let duration = options.duration;
|
|
673
|
+
if (duration != null) {
|
|
674
|
+
if (!Number.isInteger(duration)) {
|
|
675
|
+
const rounded = Math.round(duration);
|
|
676
|
+
warnings.push({
|
|
677
|
+
type: "unsupported",
|
|
678
|
+
feature: "duration",
|
|
679
|
+
details: `FLUX 3 video requires a whole number of seconds. The requested duration of ${duration} was rounded to ${rounded}.`
|
|
680
|
+
});
|
|
681
|
+
duration = rounded;
|
|
682
|
+
}
|
|
683
|
+
if (duration > MAX_DURATION_SECONDS) {
|
|
684
|
+
warnings.push({
|
|
685
|
+
type: "unsupported",
|
|
686
|
+
feature: "duration",
|
|
687
|
+
details: `FLUX 3 video supports at most ${MAX_DURATION_SECONDS} seconds. The requested duration of ${options.duration} was clamped to ${MAX_DURATION_SECONDS}.`
|
|
688
|
+
});
|
|
689
|
+
duration = MAX_DURATION_SECONDS;
|
|
690
|
+
} else if (duration < MIN_DURATION_SECONDS) {
|
|
691
|
+
warnings.push({
|
|
692
|
+
type: "unsupported",
|
|
693
|
+
feature: "duration",
|
|
694
|
+
details: `FLUX 3 video requires at least ${MIN_DURATION_SECONDS} seconds. The requested duration of ${options.duration} was clamped to ${MIN_DURATION_SECONDS}.`
|
|
695
|
+
});
|
|
696
|
+
duration = MIN_DURATION_SECONDS;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
const untimedKeyframeCount = keyframes?.filter((keyframe) => typeof keyframe === "string").length ?? 0;
|
|
700
|
+
if (duration == null && untimedKeyframeCount >= UNTIMED_KEYFRAMES_NEEDING_DURATION) throw new InvalidArgumentError({
|
|
701
|
+
argument: "duration",
|
|
702
|
+
message: `FLUX 3 video requires an explicit duration when ${UNTIMED_KEYFRAMES_NEEDING_DURATION} or more keyframes are sent without a timestamp.`
|
|
703
|
+
});
|
|
704
|
+
const mode = keyframes != null ? "i2v" : startVideo != null ? "v2v" : "t2v";
|
|
705
|
+
return {
|
|
706
|
+
body: {
|
|
707
|
+
mode,
|
|
708
|
+
prompt: options.prompt ?? "",
|
|
709
|
+
aspect_ratio: aspectRatio,
|
|
710
|
+
duration,
|
|
711
|
+
resolution,
|
|
712
|
+
version: bflOptions?.version,
|
|
713
|
+
generate_audio: options.generateAudio,
|
|
714
|
+
safety_tolerance: bflOptions?.safetyTolerance,
|
|
715
|
+
draft: bflOptions?.draft,
|
|
716
|
+
...mode === "i2v" && { keyframes },
|
|
717
|
+
...mode === "v2v" && { start_video: startVideo }
|
|
718
|
+
},
|
|
719
|
+
warnings
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
async doStart(options) {
|
|
723
|
+
const { body, warnings } = await this.getArgs(options);
|
|
724
|
+
const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
|
|
725
|
+
const combinedHeaders = combineHeaders(await resolve(this.config.headers), options.headers);
|
|
726
|
+
const { value: submit, responseHeaders } = await postJsonToApi({
|
|
727
|
+
url: `${this.config.baseURL}/${this.modelId}`,
|
|
728
|
+
headers: combinedHeaders,
|
|
729
|
+
body,
|
|
730
|
+
failedResponseHandler: bflFailedResponseHandler,
|
|
731
|
+
successfulResponseHandler: createJsonResponseHandler(bflVideoSubmitSchema),
|
|
732
|
+
abortSignal: options.abortSignal,
|
|
733
|
+
fetch: this.config.fetch
|
|
734
|
+
});
|
|
735
|
+
return {
|
|
736
|
+
operation: {
|
|
737
|
+
requestId: submit.id,
|
|
738
|
+
pollingUrl: submit.polling_url,
|
|
739
|
+
...submit.cost != null && { cost: submit.cost },
|
|
740
|
+
...submit.input_mp != null && { inputMegapixels: submit.input_mp },
|
|
741
|
+
...submit.output_mp != null && { outputMegapixels: submit.output_mp }
|
|
742
|
+
},
|
|
743
|
+
warnings,
|
|
744
|
+
response: {
|
|
745
|
+
modelId: this.modelId,
|
|
746
|
+
timestamp: currentDate,
|
|
747
|
+
headers: responseHeaders
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
async doStatus(options) {
|
|
752
|
+
const currentDate = this.config._internal?.currentDate?.() ?? /* @__PURE__ */ new Date();
|
|
753
|
+
const operation = options.operation;
|
|
754
|
+
const url = new URL(operation.pollingUrl);
|
|
755
|
+
if (!url.searchParams.has("id")) url.searchParams.set("id", operation.requestId);
|
|
756
|
+
const combinedHeaders = combineHeaders(await resolve(this.config.headers), options.headers);
|
|
757
|
+
const { value, responseHeaders } = await getFromApi({
|
|
758
|
+
url: url.toString(),
|
|
759
|
+
validateUrl: true,
|
|
760
|
+
trustedOrigin: this.config.baseURL,
|
|
761
|
+
headers: isTrustedUrl(url.toString(), this.config.baseURL) ? combinedHeaders : void 0,
|
|
762
|
+
failedResponseHandler: bflFailedResponseHandler,
|
|
763
|
+
successfulResponseHandler: createJsonResponseHandler(bflVideoPollSchema),
|
|
764
|
+
abortSignal: options.abortSignal,
|
|
765
|
+
fetch: this.config.fetch
|
|
766
|
+
});
|
|
767
|
+
const response = {
|
|
768
|
+
modelId: this.modelId,
|
|
769
|
+
timestamp: currentDate,
|
|
770
|
+
headers: responseHeaders
|
|
771
|
+
};
|
|
772
|
+
const { status, result } = value;
|
|
773
|
+
const cost = value.cost ?? operation.cost;
|
|
774
|
+
if (status === "Ready") {
|
|
775
|
+
const parsed = bflVideoResultSchema.safeParse(result);
|
|
776
|
+
if (!parsed.success) throw new AISDKError({
|
|
777
|
+
name: "BLACK_FOREST_LABS_VIDEO_GENERATION_ERROR",
|
|
778
|
+
message: `Black Forest Labs reported the video as Ready but returned no result.sample URL. Request id: ${operation.requestId}`
|
|
779
|
+
});
|
|
780
|
+
return {
|
|
781
|
+
status: "completed",
|
|
782
|
+
videos: [{
|
|
783
|
+
type: "url",
|
|
784
|
+
url: parsed.data.sample,
|
|
785
|
+
mediaType: "video/mp4"
|
|
786
|
+
}],
|
|
787
|
+
warnings: [],
|
|
788
|
+
providerMetadata: { blackForestLabs: { videos: [{
|
|
789
|
+
id: operation.requestId,
|
|
790
|
+
videoUrl: parsed.data.sample,
|
|
791
|
+
...parsed.data.seed != null && { seed: parsed.data.seed },
|
|
792
|
+
...parsed.data.start_time != null && { start_time: parsed.data.start_time },
|
|
793
|
+
...parsed.data.end_time != null && { end_time: parsed.data.end_time },
|
|
794
|
+
...parsed.data.duration != null && { duration: parsed.data.duration },
|
|
795
|
+
...parsed.data.draft_cache != null && { draftCache: parsed.data.draft_cache },
|
|
796
|
+
...cost != null && { cost },
|
|
797
|
+
...operation.inputMegapixels != null && { inputMegapixels: operation.inputMegapixels },
|
|
798
|
+
...operation.outputMegapixels != null && { outputMegapixels: operation.outputMegapixels }
|
|
799
|
+
}] } },
|
|
800
|
+
response
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
if (TERMINAL_FAILURE_STATUSES.has(status)) {
|
|
804
|
+
const detail = describePollDetails(value.details);
|
|
805
|
+
return {
|
|
806
|
+
status: "error",
|
|
807
|
+
error: `Black Forest Labs video generation failed with status "${status}"${detail != null ? `: ${detail}` : ""}. Request id: ${operation.requestId}`,
|
|
808
|
+
response
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
return {
|
|
812
|
+
status: "pending",
|
|
813
|
+
response
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
async doGenerate(options) {
|
|
817
|
+
const startResult = await this.doStart(options);
|
|
818
|
+
const operation = startResult.operation;
|
|
819
|
+
const pollIntervalMillis = this.config.pollIntervalMillis ?? DEFAULT_POLL_INTERVAL_MILLIS;
|
|
820
|
+
const pollTimeoutMillis = this.config.pollTimeoutMillis ?? DEFAULT_POLL_TIMEOUT_MILLIS;
|
|
821
|
+
const startTime = Date.now();
|
|
822
|
+
while (true) {
|
|
823
|
+
await delay(pollIntervalMillis, { abortSignal: options.abortSignal });
|
|
824
|
+
if (Date.now() - startTime > pollTimeoutMillis) throw new AISDKError({
|
|
825
|
+
name: "BLACK_FOREST_LABS_VIDEO_GENERATION_TIMEOUT",
|
|
826
|
+
message: `Black Forest Labs video generation timed out after ${pollTimeoutMillis}ms. Request id: ${operation.requestId}`
|
|
827
|
+
});
|
|
828
|
+
const statusResult = await this.doStatus({
|
|
829
|
+
operation,
|
|
830
|
+
headers: options.headers,
|
|
831
|
+
abortSignal: options.abortSignal
|
|
832
|
+
});
|
|
833
|
+
if (statusResult.status === "pending") continue;
|
|
834
|
+
if (statusResult.status === "error") throw new AISDKError({
|
|
835
|
+
name: "BLACK_FOREST_LABS_VIDEO_GENERATION_FAILED",
|
|
836
|
+
message: statusResult.error
|
|
837
|
+
});
|
|
838
|
+
return {
|
|
839
|
+
videos: statusResult.videos,
|
|
840
|
+
warnings: [...startResult.warnings, ...statusResult.warnings],
|
|
841
|
+
providerMetadata: statusResult.providerMetadata,
|
|
842
|
+
response: statusResult.response
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
}
|
|
1063
846
|
};
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
847
|
+
const bflVideoSubmitSchema = z.object({
|
|
848
|
+
id: z.string(),
|
|
849
|
+
polling_url: z.url(),
|
|
850
|
+
cost: z.number().nullish(),
|
|
851
|
+
input_mp: z.number().nullish(),
|
|
852
|
+
output_mp: z.number().nullish()
|
|
1070
853
|
});
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
854
|
+
const bflVideoResultSchema = z.object({
|
|
855
|
+
sample: z.url(),
|
|
856
|
+
seed: z.number().nullish(),
|
|
857
|
+
start_time: z.number().nullish(),
|
|
858
|
+
end_time: z.number().nullish(),
|
|
859
|
+
duration: z.number().nullish(),
|
|
860
|
+
draft_cache: z.string().nullish()
|
|
1078
861
|
});
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
}).transform((v) => ({
|
|
1091
|
-
status: v.status ?? v.state,
|
|
1092
|
-
details: v.details,
|
|
1093
|
-
result: v.result,
|
|
1094
|
-
cost: v.cost
|
|
862
|
+
const bflVideoPollSchema = z.object({
|
|
863
|
+
status: z.string().optional(),
|
|
864
|
+
state: z.string().optional(),
|
|
865
|
+
details: z.unknown().optional(),
|
|
866
|
+
result: z.unknown().optional(),
|
|
867
|
+
cost: z.number().nullish()
|
|
868
|
+
}).refine((v) => v.status != null || v.state != null, { message: "Missing status in Black Forest Labs poll response" }).transform((v) => ({
|
|
869
|
+
status: v.status ?? v.state,
|
|
870
|
+
details: v.details,
|
|
871
|
+
result: v.result,
|
|
872
|
+
cost: v.cost
|
|
1095
873
|
}));
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
874
|
+
//#endregion
|
|
875
|
+
//#region src/version.ts
|
|
876
|
+
const VERSION = "2.0.54";
|
|
877
|
+
//#endregion
|
|
878
|
+
//#region src/black-forest-labs-provider.ts
|
|
879
|
+
const defaultBaseURL = "https://api.bfl.ai/v1";
|
|
1102
880
|
function createBlackForestLabs(options = {}) {
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
embeddingModel,
|
|
1150
|
-
textEmbeddingModel: embeddingModel
|
|
1151
|
-
};
|
|
881
|
+
const baseURL = withoutTrailingSlash(options.baseURL ?? defaultBaseURL);
|
|
882
|
+
const getHeaders = () => withUserAgentSuffix({
|
|
883
|
+
"x-key": loadApiKey({
|
|
884
|
+
apiKey: options.apiKey,
|
|
885
|
+
environmentVariableName: "BFL_API_KEY",
|
|
886
|
+
description: "Black Forest Labs"
|
|
887
|
+
}),
|
|
888
|
+
...options.headers
|
|
889
|
+
}, `ai-sdk-black-forest-labs/${VERSION}`);
|
|
890
|
+
const createImageModel = (modelId) => new BlackForestLabsImageModel(modelId, {
|
|
891
|
+
provider: "black-forest-labs.image",
|
|
892
|
+
baseURL: baseURL ?? defaultBaseURL,
|
|
893
|
+
headers: getHeaders,
|
|
894
|
+
fetch: options.fetch,
|
|
895
|
+
pollIntervalMillis: options.pollIntervalMillis,
|
|
896
|
+
pollTimeoutMillis: options.pollTimeoutMillis
|
|
897
|
+
});
|
|
898
|
+
const createVideoModel = (modelId) => new BlackForestLabsVideoModel(modelId, {
|
|
899
|
+
provider: "black-forest-labs.video",
|
|
900
|
+
baseURL: baseURL ?? defaultBaseURL,
|
|
901
|
+
headers: getHeaders,
|
|
902
|
+
fetch: options.fetch,
|
|
903
|
+
pollIntervalMillis: options.pollIntervalMillis,
|
|
904
|
+
pollTimeoutMillis: options.pollTimeoutMillis
|
|
905
|
+
});
|
|
906
|
+
const embeddingModel = (modelId) => {
|
|
907
|
+
throw new NoSuchModelError({
|
|
908
|
+
modelId,
|
|
909
|
+
modelType: "embeddingModel"
|
|
910
|
+
});
|
|
911
|
+
};
|
|
912
|
+
return {
|
|
913
|
+
specificationVersion: "v4",
|
|
914
|
+
imageModel: createImageModel,
|
|
915
|
+
image: createImageModel,
|
|
916
|
+
videoModel: createVideoModel,
|
|
917
|
+
video: createVideoModel,
|
|
918
|
+
languageModel: (modelId) => {
|
|
919
|
+
throw new NoSuchModelError({
|
|
920
|
+
modelId,
|
|
921
|
+
modelType: "languageModel"
|
|
922
|
+
});
|
|
923
|
+
},
|
|
924
|
+
embeddingModel,
|
|
925
|
+
textEmbeddingModel: embeddingModel
|
|
926
|
+
};
|
|
1152
927
|
}
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
createBlackForestLabs
|
|
1158
|
-
};
|
|
928
|
+
const blackForestLabs = createBlackForestLabs();
|
|
929
|
+
//#endregion
|
|
930
|
+
export { VERSION, blackForestLabs, createBlackForestLabs };
|
|
931
|
+
|
|
1159
932
|
//# sourceMappingURL=index.js.map
|