@ai-sdk/black-forest-labs 0.0.0-1c33ba03-20260114162300 → 0.0.0-4115c213-20260122152721
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 +32 -3
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/docs/12-black-forest-labs.mdx +200 -0
- package/package.json +14 -5
- package/src/black-forest-labs-image-model.ts +473 -0
- package/src/black-forest-labs-image-settings.ts +9 -0
- package/src/black-forest-labs-provider.ts +113 -0
- package/src/index.ts +14 -0
- package/src/version.ts +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,40 @@
|
|
|
1
1
|
# @ai-sdk/black-forest-labs
|
|
2
2
|
|
|
3
|
-
## 0.0.0-
|
|
3
|
+
## 0.0.0-4115c213-20260122152721
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
|
|
7
|
+
- 4caafb2: chore: excluded tests from src folder in npm package
|
|
8
|
+
- Updated dependencies [4caafb2]
|
|
9
|
+
- @ai-sdk/provider@0.0.0-4115c213-20260122152721
|
|
10
|
+
- @ai-sdk/provider-utils@0.0.0-4115c213-20260122152721
|
|
11
|
+
|
|
12
|
+
## 1.0.10
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 2b8369d: chore: add docs to package dist
|
|
17
|
+
|
|
18
|
+
## 1.0.9
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- 8dc54db: chore: add src folders to package bundle
|
|
23
|
+
|
|
24
|
+
## 1.0.8
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- Updated dependencies [5c090e7]
|
|
29
|
+
- @ai-sdk/provider@3.0.4
|
|
30
|
+
- @ai-sdk/provider-utils@4.0.8
|
|
31
|
+
|
|
32
|
+
## 1.0.7
|
|
33
|
+
|
|
34
|
+
### Patch Changes
|
|
35
|
+
|
|
36
|
+
- Updated dependencies [46f46e4]
|
|
37
|
+
- @ai-sdk/provider-utils@4.0.7
|
|
9
38
|
|
|
10
39
|
## 1.0.6
|
|
11
40
|
|
package/dist/index.js
CHANGED
|
@@ -392,7 +392,7 @@ function bflErrorToMessage(error) {
|
|
|
392
392
|
}
|
|
393
393
|
|
|
394
394
|
// src/version.ts
|
|
395
|
-
var VERSION = true ? "0.0.0-
|
|
395
|
+
var VERSION = true ? "0.0.0-4115c213-20260122152721" : "0.0.0-test";
|
|
396
396
|
|
|
397
397
|
// src/black-forest-labs-provider.ts
|
|
398
398
|
var defaultBaseURL = "https://api.bfl.ai/v1";
|
package/dist/index.mjs
CHANGED
|
@@ -381,7 +381,7 @@ function bflErrorToMessage(error) {
|
|
|
381
381
|
}
|
|
382
382
|
|
|
383
383
|
// src/version.ts
|
|
384
|
-
var VERSION = true ? "0.0.0-
|
|
384
|
+
var VERSION = true ? "0.0.0-4115c213-20260122152721" : "0.0.0-test";
|
|
385
385
|
|
|
386
386
|
// src/black-forest-labs-provider.ts
|
|
387
387
|
var defaultBaseURL = "https://api.bfl.ai/v1";
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Black Forest Labs
|
|
3
|
+
description: Learn how to use Black Forest Labs models with the AI SDK.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Black Forest Labs Provider
|
|
7
|
+
|
|
8
|
+
[Black Forest Labs](https://bfl.ai/) provides a generative image platform for developers with FLUX-based models. Their platform offers fast, high quality, and in-context image generation and editing with precise and coherent results.
|
|
9
|
+
|
|
10
|
+
## Setup
|
|
11
|
+
|
|
12
|
+
The Black Forest Labs provider is available via the `@ai-sdk/black-forest-labs` module. You can install it with
|
|
13
|
+
|
|
14
|
+
<Tabs items={['pnpm', 'npm', 'yarn', 'bun']}>
|
|
15
|
+
<Tab>
|
|
16
|
+
<Snippet text="pnpm add @ai-sdk/black-forest-labs" dark />
|
|
17
|
+
</Tab>
|
|
18
|
+
<Tab>
|
|
19
|
+
<Snippet text="npm install @ai-sdk/black-forest-labs" dark />
|
|
20
|
+
</Tab>
|
|
21
|
+
<Tab>
|
|
22
|
+
<Snippet text="yarn add @ai-sdk/black-forest-labs" dark />
|
|
23
|
+
</Tab>
|
|
24
|
+
|
|
25
|
+
<Tab>
|
|
26
|
+
<Snippet text="bun add @ai-sdk/black-forest-labs" dark />
|
|
27
|
+
</Tab>
|
|
28
|
+
</Tabs>
|
|
29
|
+
|
|
30
|
+
## Provider Instance
|
|
31
|
+
|
|
32
|
+
You can import the default provider instance `blackForestLabs` from `@ai-sdk/black-forest-labs`:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { blackForestLabs } from '@ai-sdk/black-forest-labs';
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
If you need a customized setup, you can import `createBlackForestLabs` and create a provider instance with your settings:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { createBlackForestLabs } from '@ai-sdk/black-forest-labs';
|
|
42
|
+
|
|
43
|
+
const blackForestLabs = createBlackForestLabs({
|
|
44
|
+
apiKey: 'your-api-key', // optional, defaults to BFL_API_KEY environment variable
|
|
45
|
+
baseURL: 'custom-url', // optional
|
|
46
|
+
headers: {
|
|
47
|
+
/* custom headers */
|
|
48
|
+
}, // optional
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
You can use the following optional settings to customize the Black Forest Labs provider instance:
|
|
53
|
+
|
|
54
|
+
- **baseURL** _string_
|
|
55
|
+
|
|
56
|
+
Use a different URL prefix for API calls, e.g. to use a regional endpoint.
|
|
57
|
+
The default prefix is `https://api.bfl.ai/v1`.
|
|
58
|
+
|
|
59
|
+
- **apiKey** _string_
|
|
60
|
+
|
|
61
|
+
API key that is being sent using the `x-key` header.
|
|
62
|
+
It defaults to the `BFL_API_KEY` environment variable.
|
|
63
|
+
|
|
64
|
+
- **headers** _Record<string,string>_
|
|
65
|
+
|
|
66
|
+
Custom headers to include in the requests.
|
|
67
|
+
|
|
68
|
+
- **fetch** _(input: RequestInfo, init?: RequestInit) => Promise<Response>_
|
|
69
|
+
|
|
70
|
+
Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
|
|
71
|
+
You can use it as a middleware to intercept requests,
|
|
72
|
+
or to provide a custom fetch implementation for e.g. testing.
|
|
73
|
+
|
|
74
|
+
## Image Models
|
|
75
|
+
|
|
76
|
+
You can create Black Forest Labs image models using the `.image()` factory method.
|
|
77
|
+
For more on image generation with the AI SDK see [generateImage()](/docs/reference/ai-sdk-core/generate-image).
|
|
78
|
+
|
|
79
|
+
### Basic Usage
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { writeFileSync } from 'node:fs';
|
|
83
|
+
import { blackForestLabs } from '@ai-sdk/black-forest-labs';
|
|
84
|
+
import { generateImage } from 'ai';
|
|
85
|
+
|
|
86
|
+
const { image, providerMetadata } = await generateImage({
|
|
87
|
+
model: blackForestLabs.image('flux-pro-1.1'),
|
|
88
|
+
prompt: 'A serene mountain landscape at sunset',
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const filename = `image-${Date.now()}.png`;
|
|
92
|
+
writeFileSync(filename, image.uint8Array);
|
|
93
|
+
console.log(`Image saved to ${filename}`);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Model Capabilities
|
|
97
|
+
|
|
98
|
+
Black Forest Labs offers many models optimized for different use cases. Here are a few popular examples. For a full list of models, see the [Black Forest Labs Models Page](https://bfl.ai/models).
|
|
99
|
+
|
|
100
|
+
| Model | Description |
|
|
101
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
102
|
+
| `flux-kontext-pro` | FLUX.1 Kontext [pro] handles both text and reference images as inputs, enabling targeted edits and complex transformations |
|
|
103
|
+
| `flux-kontext-max` | FLUX.1 Kontext [max] with improved prompt adherence and typography generation |
|
|
104
|
+
| `flux-pro-1.1-ultra` | Ultra-fast, ultra high-resolution image creation |
|
|
105
|
+
| `flux-pro-1.1` | Fast, high-quality image generation from text. |
|
|
106
|
+
|
|
107
|
+
Black Forest Labs models support aspect ratios from 3:7 (portrait) to 7:3 (landscape).
|
|
108
|
+
|
|
109
|
+
### Image Editing
|
|
110
|
+
|
|
111
|
+
Black Forest Labs Kontext models support powerful image editing capabilities using reference images. Pass input images via `prompt.images` to transform, combine, or edit existing images.
|
|
112
|
+
|
|
113
|
+
#### Single Image Editing
|
|
114
|
+
|
|
115
|
+
Transform an existing image using text prompts:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import {
|
|
119
|
+
blackForestLabs,
|
|
120
|
+
BlackForestLabsImageProviderOptions,
|
|
121
|
+
} from '@ai-sdk/black-forest-labs';
|
|
122
|
+
import { generateImage } from 'ai';
|
|
123
|
+
|
|
124
|
+
const { images } = await generateImage({
|
|
125
|
+
model: blackForestLabs.image('flux-kontext-pro'),
|
|
126
|
+
prompt: {
|
|
127
|
+
text: 'A baby elephant with a shirt that has the logo from the input image.',
|
|
128
|
+
images: [
|
|
129
|
+
'https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png',
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
providerOptions: {
|
|
133
|
+
blackForestLabs: {
|
|
134
|
+
width: 1024,
|
|
135
|
+
height: 768,
|
|
136
|
+
} satisfies BlackForestLabsImageProviderOptions,
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### Multi-Reference Editing
|
|
142
|
+
|
|
143
|
+
Combine multiple reference images for complex transformations. Black Forest Labs supports up to 10 input images:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import { blackForestLabs } from '@ai-sdk/black-forest-labs';
|
|
147
|
+
import { generateImage } from 'ai';
|
|
148
|
+
|
|
149
|
+
const { images } = await generateImage({
|
|
150
|
+
model: blackForestLabs.image('flux-kontext-pro'),
|
|
151
|
+
prompt: {
|
|
152
|
+
text: 'Combine the style of image 1 with the subject of image 2',
|
|
153
|
+
images: [
|
|
154
|
+
'https://example.com/style-reference.jpg',
|
|
155
|
+
'https://example.com/subject-reference.jpg',
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
<Note>
|
|
162
|
+
Input images can be provided as URLs or base64-encoded strings. They support
|
|
163
|
+
up to 20MB or 20 megapixels per image.
|
|
164
|
+
</Note>
|
|
165
|
+
|
|
166
|
+
### Provider Options
|
|
167
|
+
|
|
168
|
+
Black Forest Labs image models support flexible provider options through the `providerOptions.blackForestLabs` object. The supported parameters depend on the used model ID:
|
|
169
|
+
|
|
170
|
+
- **width** _number_ - Output width in pixels (256–1920). When set, this overrides any width derived from `size`.
|
|
171
|
+
- **height** _number_ - Output height in pixels (256–1920). When set, this overrides any height derived from `size`.
|
|
172
|
+
- **outputFormat** _string_ - Desired format of the output image (`"jpeg"` or `"png"`).
|
|
173
|
+
- **steps** _number_ - Number of inference steps. Higher values may improve quality but increase generation time.
|
|
174
|
+
- **guidance** _number_ - Guidance scale for generation. Higher values follow the prompt more closely.
|
|
175
|
+
- **imagePrompt** _string_ - Base64-encoded image to use as additional visual context for generation.
|
|
176
|
+
- **imagePromptStrength** _number_ - Strength of the image prompt influence on generation (0.0 to 1.0).
|
|
177
|
+
- **promptUpsampling** _boolean_ - If true, performs upsampling on the prompt.
|
|
178
|
+
- **raw** _boolean_ - Enable raw mode for more natural, authentic aesthetics.
|
|
179
|
+
- **safetyTolerance** _number_ - Moderation level for inputs and outputs (0 = most strict, 6 = more permissive).
|
|
180
|
+
- **pollIntervalMillis** _number_ - Interval in milliseconds between polling attempts (default 500ms).
|
|
181
|
+
- **pollTimeoutMillis** _number_ - Overall timeout in milliseconds for polling before timing out (default 60s).
|
|
182
|
+
- **webhookUrl** _string_ - URL for asynchronous completion notification. Must be a valid HTTP/HTTPS URL.
|
|
183
|
+
- **webhookSecret** _string_ - Secret for webhook signature verification, sent in the `X-Webhook-Secret` header.
|
|
184
|
+
|
|
185
|
+
<Note>
|
|
186
|
+
To pass reference images for editing, use `prompt.images` instead of provider
|
|
187
|
+
options. This supports up to 10 images as URLs or base64-encoded strings.
|
|
188
|
+
</Note>
|
|
189
|
+
|
|
190
|
+
### Regional Endpoints
|
|
191
|
+
|
|
192
|
+
By default, requests are sent to `https://api.bfl.ai/v1`. You can select a [regional endpoint](https://docs.bfl.ai/api_integration/integration_guidelines#regional-endpoints) by setting `baseURL` when creating the provider instance:
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
import { createBlackForestLabs } from '@ai-sdk/black-forest-labs';
|
|
196
|
+
|
|
197
|
+
const blackForestLabs = createBlackForestLabs({
|
|
198
|
+
baseURL: 'https://api.eu.bfl.ai/v1', // or https://api.us.bfl.ai/v1
|
|
199
|
+
});
|
|
200
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/black-forest-labs",
|
|
3
|
-
"version": "0.0.0-
|
|
3
|
+
"version": "0.0.0-4115c213-20260122152721",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -8,8 +8,17 @@
|
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"files": [
|
|
10
10
|
"dist/**/*",
|
|
11
|
+
"docs/**/*",
|
|
12
|
+
"src",
|
|
13
|
+
"!src/**/*.test.ts",
|
|
14
|
+
"!src/**/*.test-d.ts",
|
|
15
|
+
"!src/**/__snapshots__",
|
|
16
|
+
"!src/**/__fixtures__",
|
|
11
17
|
"CHANGELOG.md"
|
|
12
18
|
],
|
|
19
|
+
"directories": {
|
|
20
|
+
"doc": "./docs"
|
|
21
|
+
},
|
|
13
22
|
"exports": {
|
|
14
23
|
"./package.json": "./package.json",
|
|
15
24
|
".": {
|
|
@@ -19,15 +28,15 @@
|
|
|
19
28
|
}
|
|
20
29
|
},
|
|
21
30
|
"dependencies": {
|
|
22
|
-
"@ai-sdk/provider": "
|
|
23
|
-
"@ai-sdk/provider-utils": "0.0.0-
|
|
31
|
+
"@ai-sdk/provider": "0.0.0-4115c213-20260122152721",
|
|
32
|
+
"@ai-sdk/provider-utils": "0.0.0-4115c213-20260122152721"
|
|
24
33
|
},
|
|
25
34
|
"devDependencies": {
|
|
26
35
|
"@types/node": "20.17.24",
|
|
27
36
|
"tsup": "^8",
|
|
28
37
|
"typescript": "5.8.3",
|
|
29
38
|
"zod": "3.25.76",
|
|
30
|
-
"@ai-sdk/test-server": "
|
|
39
|
+
"@ai-sdk/test-server": "0.0.0-4115c213-20260122152721",
|
|
31
40
|
"@vercel/ai-tsconfig": "0.0.0"
|
|
32
41
|
},
|
|
33
42
|
"peerDependencies": {
|
|
@@ -53,7 +62,7 @@
|
|
|
53
62
|
"scripts": {
|
|
54
63
|
"build": "pnpm clean && tsup --tsconfig tsconfig.build.json",
|
|
55
64
|
"build:watch": "pnpm clean && tsup --watch",
|
|
56
|
-
"clean": "del-cli dist *.tsbuildinfo",
|
|
65
|
+
"clean": "del-cli dist docs *.tsbuildinfo",
|
|
57
66
|
"lint": "eslint \"./**/*.ts*\"",
|
|
58
67
|
"type-check": "tsc --build",
|
|
59
68
|
"prettier-check": "prettier --check \"./**/*.ts*\"",
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import type { ImageModelV3, SharedV3Warning } from '@ai-sdk/provider';
|
|
2
|
+
import type { InferSchema, Resolvable } from '@ai-sdk/provider-utils';
|
|
3
|
+
import {
|
|
4
|
+
FetchFunction,
|
|
5
|
+
combineHeaders,
|
|
6
|
+
createBinaryResponseHandler,
|
|
7
|
+
createJsonErrorResponseHandler,
|
|
8
|
+
createJsonResponseHandler,
|
|
9
|
+
createStatusCodeErrorResponseHandler,
|
|
10
|
+
delay,
|
|
11
|
+
getFromApi,
|
|
12
|
+
lazySchema,
|
|
13
|
+
parseProviderOptions,
|
|
14
|
+
postJsonToApi,
|
|
15
|
+
resolve,
|
|
16
|
+
zodSchema,
|
|
17
|
+
} from '@ai-sdk/provider-utils';
|
|
18
|
+
import { z } from 'zod/v4';
|
|
19
|
+
import type { BlackForestLabsAspectRatio } from './black-forest-labs-image-settings';
|
|
20
|
+
import { BlackForestLabsImageModelId } from './black-forest-labs-image-settings';
|
|
21
|
+
|
|
22
|
+
const DEFAULT_POLL_INTERVAL_MILLIS = 500;
|
|
23
|
+
const DEFAULT_POLL_TIMEOUT_MILLIS = 60000;
|
|
24
|
+
|
|
25
|
+
interface BlackForestLabsImageModelConfig {
|
|
26
|
+
provider: string;
|
|
27
|
+
baseURL: string;
|
|
28
|
+
headers?: Resolvable<Record<string, string | undefined>>;
|
|
29
|
+
fetch?: FetchFunction;
|
|
30
|
+
/**
|
|
31
|
+
Poll interval in milliseconds between status checks. Defaults to 500ms.
|
|
32
|
+
*/
|
|
33
|
+
pollIntervalMillis?: number;
|
|
34
|
+
/**
|
|
35
|
+
Overall timeout in milliseconds for polling before giving up. Defaults to 60s.
|
|
36
|
+
*/
|
|
37
|
+
pollTimeoutMillis?: number;
|
|
38
|
+
_internal?: {
|
|
39
|
+
currentDate?: () => Date;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class BlackForestLabsImageModel implements ImageModelV3 {
|
|
44
|
+
readonly specificationVersion = 'v3';
|
|
45
|
+
readonly maxImagesPerCall = 1;
|
|
46
|
+
|
|
47
|
+
get provider(): string {
|
|
48
|
+
return this.config.provider;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
constructor(
|
|
52
|
+
readonly modelId: BlackForestLabsImageModelId,
|
|
53
|
+
private readonly config: BlackForestLabsImageModelConfig,
|
|
54
|
+
) {}
|
|
55
|
+
|
|
56
|
+
private async getArgs({
|
|
57
|
+
prompt,
|
|
58
|
+
files,
|
|
59
|
+
mask,
|
|
60
|
+
size,
|
|
61
|
+
aspectRatio,
|
|
62
|
+
seed,
|
|
63
|
+
providerOptions,
|
|
64
|
+
}: Parameters<ImageModelV3['doGenerate']>[0]) {
|
|
65
|
+
const warnings: Array<SharedV3Warning> = [];
|
|
66
|
+
|
|
67
|
+
const finalAspectRatio =
|
|
68
|
+
aspectRatio ?? (size ? convertSizeToAspectRatio(size) : undefined);
|
|
69
|
+
|
|
70
|
+
if (size && !aspectRatio) {
|
|
71
|
+
warnings.push({
|
|
72
|
+
type: 'unsupported',
|
|
73
|
+
feature: 'size',
|
|
74
|
+
details:
|
|
75
|
+
'Deriving aspect_ratio from size. Use the width and height provider options to specify dimensions for models that support them.',
|
|
76
|
+
});
|
|
77
|
+
} else if (size && aspectRatio) {
|
|
78
|
+
warnings.push({
|
|
79
|
+
type: 'unsupported',
|
|
80
|
+
feature: 'size',
|
|
81
|
+
details:
|
|
82
|
+
'Black Forest Labs ignores size when aspectRatio is provided. Use the width and height provider options to specify dimensions for models that support them',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const bflOptions = await parseProviderOptions({
|
|
87
|
+
provider: 'blackForestLabs',
|
|
88
|
+
providerOptions,
|
|
89
|
+
schema: blackForestLabsImageProviderOptionsSchema,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const [widthStr, heightStr] = size?.split('x') ?? [];
|
|
93
|
+
|
|
94
|
+
const inputImages: string[] =
|
|
95
|
+
files?.map(file => {
|
|
96
|
+
if (file.type === 'url') {
|
|
97
|
+
return file.url;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (typeof file.data === 'string') {
|
|
101
|
+
return file.data;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return Buffer.from(file.data).toString('base64');
|
|
105
|
+
}) || [];
|
|
106
|
+
|
|
107
|
+
if (inputImages.length > 10) {
|
|
108
|
+
throw new Error('Black Forest Labs supports up to 10 input images.');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const inputImagesObj: Record<string, string> = inputImages.reduce<
|
|
112
|
+
Record<string, string>
|
|
113
|
+
>((acc, img, index) => {
|
|
114
|
+
acc[`input_image${index === 0 ? '' : `_${index + 1}`}`] = img;
|
|
115
|
+
return acc;
|
|
116
|
+
}, {});
|
|
117
|
+
|
|
118
|
+
let maskValue: string | undefined;
|
|
119
|
+
if (mask) {
|
|
120
|
+
if (mask.type === 'url') {
|
|
121
|
+
maskValue = mask.url;
|
|
122
|
+
} else {
|
|
123
|
+
if (typeof mask.data === 'string') {
|
|
124
|
+
maskValue = mask.data;
|
|
125
|
+
} else {
|
|
126
|
+
maskValue = Buffer.from(mask.data).toString('base64');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const body: Record<string, unknown> = {
|
|
132
|
+
prompt,
|
|
133
|
+
seed,
|
|
134
|
+
aspect_ratio: finalAspectRatio,
|
|
135
|
+
width: bflOptions?.width ?? (size ? Number(widthStr) : undefined),
|
|
136
|
+
height: bflOptions?.height ?? (size ? Number(heightStr) : undefined),
|
|
137
|
+
steps: bflOptions?.steps,
|
|
138
|
+
guidance: bflOptions?.guidance,
|
|
139
|
+
image_prompt_strength: bflOptions?.imagePromptStrength,
|
|
140
|
+
image_prompt: bflOptions?.imagePrompt,
|
|
141
|
+
...inputImagesObj,
|
|
142
|
+
mask: maskValue,
|
|
143
|
+
output_format: bflOptions?.outputFormat,
|
|
144
|
+
prompt_upsampling: bflOptions?.promptUpsampling,
|
|
145
|
+
raw: bflOptions?.raw,
|
|
146
|
+
safety_tolerance: bflOptions?.safetyTolerance,
|
|
147
|
+
webhook_secret: bflOptions?.webhookSecret,
|
|
148
|
+
webhook_url: bflOptions?.webhookUrl,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return { body, warnings };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async doGenerate({
|
|
155
|
+
prompt,
|
|
156
|
+
files,
|
|
157
|
+
mask,
|
|
158
|
+
size,
|
|
159
|
+
aspectRatio,
|
|
160
|
+
seed,
|
|
161
|
+
providerOptions,
|
|
162
|
+
headers,
|
|
163
|
+
abortSignal,
|
|
164
|
+
}: Parameters<ImageModelV3['doGenerate']>[0]): Promise<
|
|
165
|
+
Awaited<ReturnType<ImageModelV3['doGenerate']>>
|
|
166
|
+
> {
|
|
167
|
+
const { body, warnings } = await this.getArgs({
|
|
168
|
+
prompt,
|
|
169
|
+
files,
|
|
170
|
+
mask,
|
|
171
|
+
size,
|
|
172
|
+
aspectRatio,
|
|
173
|
+
seed,
|
|
174
|
+
providerOptions,
|
|
175
|
+
n: 1,
|
|
176
|
+
headers,
|
|
177
|
+
abortSignal,
|
|
178
|
+
} as Parameters<ImageModelV3['doGenerate']>[0]);
|
|
179
|
+
|
|
180
|
+
const bflOptions = await parseProviderOptions({
|
|
181
|
+
provider: 'blackForestLabs',
|
|
182
|
+
providerOptions,
|
|
183
|
+
schema: blackForestLabsImageProviderOptionsSchema,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const currentDate = this.config._internal?.currentDate?.() ?? new Date();
|
|
187
|
+
const combinedHeaders = combineHeaders(
|
|
188
|
+
await resolve(this.config.headers),
|
|
189
|
+
headers,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const submit = await postJsonToApi({
|
|
193
|
+
url: `${this.config.baseURL}/${this.modelId}`,
|
|
194
|
+
headers: combinedHeaders,
|
|
195
|
+
body,
|
|
196
|
+
failedResponseHandler: bflFailedResponseHandler,
|
|
197
|
+
successfulResponseHandler: createJsonResponseHandler(bflSubmitSchema),
|
|
198
|
+
abortSignal,
|
|
199
|
+
fetch: this.config.fetch,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const pollUrl = submit.value.polling_url;
|
|
203
|
+
const requestId = submit.value.id;
|
|
204
|
+
|
|
205
|
+
const {
|
|
206
|
+
imageUrl,
|
|
207
|
+
seed: resultSeed,
|
|
208
|
+
start_time: resultStartTime,
|
|
209
|
+
end_time: resultEndTime,
|
|
210
|
+
duration: resultDuration,
|
|
211
|
+
} = await this.pollForImageUrl({
|
|
212
|
+
pollUrl,
|
|
213
|
+
requestId,
|
|
214
|
+
headers: combinedHeaders,
|
|
215
|
+
abortSignal,
|
|
216
|
+
pollOverrides: {
|
|
217
|
+
pollIntervalMillis: bflOptions?.pollIntervalMillis,
|
|
218
|
+
pollTimeoutMillis: bflOptions?.pollTimeoutMillis,
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const { value: imageBytes, responseHeaders } = await getFromApi({
|
|
223
|
+
url: imageUrl,
|
|
224
|
+
headers: combinedHeaders,
|
|
225
|
+
abortSignal,
|
|
226
|
+
failedResponseHandler: createStatusCodeErrorResponseHandler(),
|
|
227
|
+
successfulResponseHandler: createBinaryResponseHandler(),
|
|
228
|
+
fetch: this.config.fetch,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
images: [imageBytes],
|
|
233
|
+
warnings,
|
|
234
|
+
providerMetadata: {
|
|
235
|
+
blackForestLabs: {
|
|
236
|
+
images: [
|
|
237
|
+
{
|
|
238
|
+
...(resultSeed != null && { seed: resultSeed }),
|
|
239
|
+
...(resultStartTime != null && { start_time: resultStartTime }),
|
|
240
|
+
...(resultEndTime != null && { end_time: resultEndTime }),
|
|
241
|
+
...(resultDuration != null && { duration: resultDuration }),
|
|
242
|
+
...(submit.value.cost != null && { cost: submit.value.cost }),
|
|
243
|
+
...(submit.value.input_mp != null && {
|
|
244
|
+
inputMegapixels: submit.value.input_mp,
|
|
245
|
+
}),
|
|
246
|
+
...(submit.value.output_mp != null && {
|
|
247
|
+
outputMegapixels: submit.value.output_mp,
|
|
248
|
+
}),
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
response: {
|
|
254
|
+
modelId: this.modelId,
|
|
255
|
+
timestamp: currentDate,
|
|
256
|
+
headers: responseHeaders,
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private async pollForImageUrl({
|
|
262
|
+
pollUrl,
|
|
263
|
+
requestId,
|
|
264
|
+
headers,
|
|
265
|
+
abortSignal,
|
|
266
|
+
pollOverrides,
|
|
267
|
+
}: {
|
|
268
|
+
pollUrl: string;
|
|
269
|
+
requestId: string;
|
|
270
|
+
headers: Record<string, string | undefined>;
|
|
271
|
+
abortSignal: AbortSignal | undefined;
|
|
272
|
+
pollOverrides?: {
|
|
273
|
+
pollIntervalMillis?: number;
|
|
274
|
+
pollTimeoutMillis?: number;
|
|
275
|
+
};
|
|
276
|
+
}): Promise<{
|
|
277
|
+
imageUrl: string;
|
|
278
|
+
seed?: number;
|
|
279
|
+
start_time?: number;
|
|
280
|
+
end_time?: number;
|
|
281
|
+
duration?: number;
|
|
282
|
+
}> {
|
|
283
|
+
const pollIntervalMillis =
|
|
284
|
+
pollOverrides?.pollIntervalMillis ??
|
|
285
|
+
this.config.pollIntervalMillis ??
|
|
286
|
+
DEFAULT_POLL_INTERVAL_MILLIS;
|
|
287
|
+
const pollTimeoutMillis =
|
|
288
|
+
pollOverrides?.pollTimeoutMillis ??
|
|
289
|
+
this.config.pollTimeoutMillis ??
|
|
290
|
+
DEFAULT_POLL_TIMEOUT_MILLIS;
|
|
291
|
+
const maxPollAttempts = Math.ceil(
|
|
292
|
+
pollTimeoutMillis / Math.max(1, pollIntervalMillis),
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
const url = new URL(pollUrl);
|
|
296
|
+
if (!url.searchParams.has('id')) {
|
|
297
|
+
url.searchParams.set('id', requestId);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
for (let i = 0; i < maxPollAttempts; i++) {
|
|
301
|
+
const { value } = await getFromApi({
|
|
302
|
+
url: url.toString(),
|
|
303
|
+
headers,
|
|
304
|
+
failedResponseHandler: bflFailedResponseHandler,
|
|
305
|
+
successfulResponseHandler: createJsonResponseHandler(bflPollSchema),
|
|
306
|
+
abortSignal,
|
|
307
|
+
fetch: this.config.fetch,
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
const status = value.status;
|
|
311
|
+
if (status === 'Ready') {
|
|
312
|
+
if (typeof value.result?.sample === 'string') {
|
|
313
|
+
return {
|
|
314
|
+
imageUrl: value.result.sample,
|
|
315
|
+
seed: value.result.seed ?? undefined,
|
|
316
|
+
start_time: value.result.start_time ?? undefined,
|
|
317
|
+
end_time: value.result.end_time ?? undefined,
|
|
318
|
+
duration: value.result.duration ?? undefined,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
throw new Error(
|
|
322
|
+
'Black Forest Labs poll response is Ready but missing result.sample',
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
if (status === 'Error' || status === 'Failed') {
|
|
326
|
+
throw new Error('Black Forest Labs generation failed.');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
await delay(pollIntervalMillis);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
throw new Error('Black Forest Labs generation timed out.');
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export const blackForestLabsImageProviderOptionsSchema = lazySchema(() =>
|
|
337
|
+
zodSchema(
|
|
338
|
+
z.object({
|
|
339
|
+
imagePrompt: z.string().optional(),
|
|
340
|
+
imagePromptStrength: z.number().min(0).max(1).optional(),
|
|
341
|
+
/** @deprecated use prompt.images instead */
|
|
342
|
+
inputImage: z.string().optional(),
|
|
343
|
+
/** @deprecated use prompt.images instead */
|
|
344
|
+
inputImage2: z.string().optional(),
|
|
345
|
+
/** @deprecated use prompt.images instead */
|
|
346
|
+
inputImage3: z.string().optional(),
|
|
347
|
+
/** @deprecated use prompt.images instead */
|
|
348
|
+
inputImage4: z.string().optional(),
|
|
349
|
+
/** @deprecated use prompt.images instead */
|
|
350
|
+
inputImage5: z.string().optional(),
|
|
351
|
+
/** @deprecated use prompt.images instead */
|
|
352
|
+
inputImage6: z.string().optional(),
|
|
353
|
+
/** @deprecated use prompt.images instead */
|
|
354
|
+
inputImage7: z.string().optional(),
|
|
355
|
+
/** @deprecated use prompt.images instead */
|
|
356
|
+
inputImage8: z.string().optional(),
|
|
357
|
+
/** @deprecated use prompt.images instead */
|
|
358
|
+
inputImage9: z.string().optional(),
|
|
359
|
+
/** @deprecated use prompt.images instead */
|
|
360
|
+
inputImage10: z.string().optional(),
|
|
361
|
+
steps: z.number().int().positive().optional(),
|
|
362
|
+
guidance: z.number().min(0).optional(),
|
|
363
|
+
width: z.number().int().min(256).max(1920).optional(),
|
|
364
|
+
height: z.number().int().min(256).max(1920).optional(),
|
|
365
|
+
outputFormat: z.enum(['jpeg', 'png']).optional(),
|
|
366
|
+
promptUpsampling: z.boolean().optional(),
|
|
367
|
+
raw: z.boolean().optional(),
|
|
368
|
+
safetyTolerance: z.number().int().min(0).max(6).optional(),
|
|
369
|
+
webhookSecret: z.string().optional(),
|
|
370
|
+
webhookUrl: z.url().optional(),
|
|
371
|
+
pollIntervalMillis: z.number().int().positive().optional(),
|
|
372
|
+
pollTimeoutMillis: z.number().int().positive().optional(),
|
|
373
|
+
}),
|
|
374
|
+
),
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
export type BlackForestLabsImageProviderOptions = InferSchema<
|
|
378
|
+
typeof blackForestLabsImageProviderOptionsSchema
|
|
379
|
+
>;
|
|
380
|
+
|
|
381
|
+
function convertSizeToAspectRatio(
|
|
382
|
+
size: string,
|
|
383
|
+
): BlackForestLabsAspectRatio | undefined {
|
|
384
|
+
const [wStr, hStr] = size.split('x');
|
|
385
|
+
const width = Number(wStr);
|
|
386
|
+
const height = Number(hStr);
|
|
387
|
+
if (
|
|
388
|
+
!Number.isFinite(width) ||
|
|
389
|
+
!Number.isFinite(height) ||
|
|
390
|
+
width <= 0 ||
|
|
391
|
+
height <= 0
|
|
392
|
+
) {
|
|
393
|
+
return undefined;
|
|
394
|
+
}
|
|
395
|
+
const g = gcd(width, height);
|
|
396
|
+
return `${Math.round(width / g)}:${Math.round(height / g)}`;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function gcd(a: number, b: number): number {
|
|
400
|
+
let x = Math.abs(a);
|
|
401
|
+
let y = Math.abs(b);
|
|
402
|
+
while (y !== 0) {
|
|
403
|
+
const t = y;
|
|
404
|
+
y = x % y;
|
|
405
|
+
x = t;
|
|
406
|
+
}
|
|
407
|
+
return x;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const bflSubmitSchema = z.object({
|
|
411
|
+
id: z.string(),
|
|
412
|
+
polling_url: z.url(),
|
|
413
|
+
cost: z.number().nullish(),
|
|
414
|
+
input_mp: z.number().nullish(),
|
|
415
|
+
output_mp: z.number().nullish(),
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
const bflStatus = z.union([
|
|
419
|
+
z.literal('Pending'),
|
|
420
|
+
z.literal('Ready'),
|
|
421
|
+
z.literal('Error'),
|
|
422
|
+
z.literal('Failed'),
|
|
423
|
+
z.literal('Request Moderated'),
|
|
424
|
+
]);
|
|
425
|
+
|
|
426
|
+
const bflPollSchema = z
|
|
427
|
+
.object({
|
|
428
|
+
status: bflStatus.optional(),
|
|
429
|
+
state: bflStatus.optional(),
|
|
430
|
+
details: z.unknown().optional(),
|
|
431
|
+
result: z
|
|
432
|
+
.object({
|
|
433
|
+
sample: z.url(),
|
|
434
|
+
seed: z.number().optional(),
|
|
435
|
+
start_time: z.number().optional(),
|
|
436
|
+
end_time: z.number().optional(),
|
|
437
|
+
duration: z.number().optional(),
|
|
438
|
+
})
|
|
439
|
+
.nullish(),
|
|
440
|
+
})
|
|
441
|
+
.refine(v => v.status != null || v.state != null, {
|
|
442
|
+
message: 'Missing status in Black Forest Labs poll response',
|
|
443
|
+
})
|
|
444
|
+
.transform(v => ({
|
|
445
|
+
status: (v.status ?? v.state)!,
|
|
446
|
+
result: v.result,
|
|
447
|
+
}));
|
|
448
|
+
|
|
449
|
+
const bflErrorSchema = z.object({
|
|
450
|
+
message: z.string().optional(),
|
|
451
|
+
detail: z.any().optional(),
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
const bflFailedResponseHandler = createJsonErrorResponseHandler({
|
|
455
|
+
errorSchema: bflErrorSchema,
|
|
456
|
+
errorToMessage: error =>
|
|
457
|
+
bflErrorToMessage(error) ?? 'Unknown Black Forest Labs error',
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
function bflErrorToMessage(error: unknown): string | undefined {
|
|
461
|
+
const parsed = bflErrorSchema.safeParse(error);
|
|
462
|
+
if (!parsed.success) return undefined;
|
|
463
|
+
const { message, detail } = parsed.data;
|
|
464
|
+
if (typeof detail === 'string') return detail;
|
|
465
|
+
if (detail != null) {
|
|
466
|
+
try {
|
|
467
|
+
return JSON.stringify(detail);
|
|
468
|
+
} catch {
|
|
469
|
+
// ignore
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return message;
|
|
473
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { ImageModelV3, NoSuchModelError, ProviderV3 } from '@ai-sdk/provider';
|
|
2
|
+
import type { FetchFunction } from '@ai-sdk/provider-utils';
|
|
3
|
+
import {
|
|
4
|
+
loadApiKey,
|
|
5
|
+
withoutTrailingSlash,
|
|
6
|
+
withUserAgentSuffix,
|
|
7
|
+
} from '@ai-sdk/provider-utils';
|
|
8
|
+
import { BlackForestLabsImageModel } from './black-forest-labs-image-model';
|
|
9
|
+
import { BlackForestLabsImageModelId } from './black-forest-labs-image-settings';
|
|
10
|
+
import { VERSION } from './version';
|
|
11
|
+
|
|
12
|
+
export interface BlackForestLabsProviderSettings {
|
|
13
|
+
/**
|
|
14
|
+
Black Forest Labs API key. Default value is taken from the `BFL_API_KEY` environment variable.
|
|
15
|
+
*/
|
|
16
|
+
apiKey?: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
Base URL for the API calls. Defaults to `https://api.bfl.ai/v1`.
|
|
20
|
+
*/
|
|
21
|
+
baseURL?: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
Custom headers to include in the requests.
|
|
25
|
+
*/
|
|
26
|
+
headers?: Record<string, string>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
Custom fetch implementation. You can use it as a middleware to intercept
|
|
30
|
+
requests, or to provide a custom fetch implementation for e.g. testing.
|
|
31
|
+
*/
|
|
32
|
+
fetch?: FetchFunction;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
Poll interval in milliseconds between status checks. Defaults to 500ms.
|
|
36
|
+
*/
|
|
37
|
+
pollIntervalMillis?: number;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
Overall timeout in milliseconds for polling before giving up. Defaults to 60s.
|
|
41
|
+
*/
|
|
42
|
+
pollTimeoutMillis?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface BlackForestLabsProvider extends ProviderV3 {
|
|
46
|
+
/**
|
|
47
|
+
Creates a model for image generation.
|
|
48
|
+
*/
|
|
49
|
+
image(modelId: BlackForestLabsImageModelId): ImageModelV3;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
Creates a model for image generation.
|
|
53
|
+
*/
|
|
54
|
+
imageModel(modelId: BlackForestLabsImageModelId): ImageModelV3;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @deprecated Use `embeddingModel` instead.
|
|
58
|
+
*/
|
|
59
|
+
textEmbeddingModel(modelId: string): never;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const defaultBaseURL = 'https://api.bfl.ai/v1';
|
|
63
|
+
|
|
64
|
+
export function createBlackForestLabs(
|
|
65
|
+
options: BlackForestLabsProviderSettings = {},
|
|
66
|
+
): BlackForestLabsProvider {
|
|
67
|
+
const baseURL = withoutTrailingSlash(options.baseURL ?? defaultBaseURL);
|
|
68
|
+
const getHeaders = () =>
|
|
69
|
+
withUserAgentSuffix(
|
|
70
|
+
{
|
|
71
|
+
'x-key': loadApiKey({
|
|
72
|
+
apiKey: options.apiKey,
|
|
73
|
+
environmentVariableName: 'BFL_API_KEY',
|
|
74
|
+
description: 'Black Forest Labs',
|
|
75
|
+
}),
|
|
76
|
+
...options.headers,
|
|
77
|
+
},
|
|
78
|
+
`ai-sdk/black-forest-labs/${VERSION}`,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const createImageModel = (modelId: BlackForestLabsImageModelId) =>
|
|
82
|
+
new BlackForestLabsImageModel(modelId, {
|
|
83
|
+
provider: 'black-forest-labs.image',
|
|
84
|
+
baseURL: baseURL ?? defaultBaseURL,
|
|
85
|
+
headers: getHeaders,
|
|
86
|
+
fetch: options.fetch,
|
|
87
|
+
pollIntervalMillis: options.pollIntervalMillis,
|
|
88
|
+
pollTimeoutMillis: options.pollTimeoutMillis,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const embeddingModel = (modelId: string) => {
|
|
92
|
+
throw new NoSuchModelError({
|
|
93
|
+
modelId,
|
|
94
|
+
modelType: 'embeddingModel',
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
specificationVersion: 'v3',
|
|
100
|
+
imageModel: createImageModel,
|
|
101
|
+
image: createImageModel,
|
|
102
|
+
languageModel: (modelId: string) => {
|
|
103
|
+
throw new NoSuchModelError({
|
|
104
|
+
modelId,
|
|
105
|
+
modelType: 'languageModel',
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
embeddingModel,
|
|
109
|
+
textEmbeddingModel: embeddingModel,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const blackForestLabs = createBlackForestLabs();
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createBlackForestLabs,
|
|
3
|
+
blackForestLabs,
|
|
4
|
+
} from './black-forest-labs-provider';
|
|
5
|
+
export type {
|
|
6
|
+
BlackForestLabsProvider,
|
|
7
|
+
BlackForestLabsProviderSettings,
|
|
8
|
+
} from './black-forest-labs-provider';
|
|
9
|
+
export type {
|
|
10
|
+
BlackForestLabsImageModelId,
|
|
11
|
+
BlackForestLabsAspectRatio,
|
|
12
|
+
} from './black-forest-labs-image-settings';
|
|
13
|
+
export type { BlackForestLabsImageProviderOptions } from './black-forest-labs-image-model';
|
|
14
|
+
export { VERSION } from './version';
|