@ai-sdk/fireworks 2.0.17 → 2.0.19

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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @ai-sdk/fireworks
2
2
 
3
+ ## 2.0.19
4
+
5
+ ### Patch Changes
6
+
7
+ - 4de5a1d: chore: excluded tests from src folder in npm package
8
+ - Updated dependencies [4de5a1d]
9
+ - @ai-sdk/openai-compatible@2.0.18
10
+ - @ai-sdk/provider@3.0.5
11
+ - @ai-sdk/provider-utils@4.0.9
12
+
13
+ ## 2.0.18
14
+
15
+ ### Patch Changes
16
+
17
+ - 2b8369d: chore: add docs to package dist
18
+
3
19
  ## 2.0.17
4
20
 
5
21
  ### Patch Changes
package/dist/index.js CHANGED
@@ -174,7 +174,7 @@ var import_provider_utils2 = require("@ai-sdk/provider-utils");
174
174
  var import_v4 = require("zod/v4");
175
175
 
176
176
  // src/version.ts
177
- var VERSION = true ? "2.0.17" : "0.0.0-test";
177
+ var VERSION = true ? "2.0.19" : "0.0.0-test";
178
178
 
179
179
  // src/fireworks-provider.ts
180
180
  var fireworksErrorSchema = import_v4.z.object({
package/dist/index.mjs CHANGED
@@ -159,7 +159,7 @@ import {
159
159
  import { z } from "zod/v4";
160
160
 
161
161
  // src/version.ts
162
- var VERSION = true ? "2.0.17" : "0.0.0-test";
162
+ var VERSION = true ? "2.0.19" : "0.0.0-test";
163
163
 
164
164
  // src/fireworks-provider.ts
165
165
  var fireworksErrorSchema = z.object({
@@ -0,0 +1,289 @@
1
+ ---
2
+ title: Fireworks
3
+ description: Learn how to use Fireworks models with the AI SDK.
4
+ ---
5
+
6
+ # Fireworks Provider
7
+
8
+ [Fireworks](https://fireworks.ai/) is a platform for running and testing LLMs through their [API](https://readme.fireworks.ai/).
9
+
10
+ ## Setup
11
+
12
+ The Fireworks provider is available via the `@ai-sdk/fireworks` module. You can install it with
13
+
14
+ <Tabs items={['pnpm', 'npm', 'yarn', 'bun']}>
15
+ <Tab>
16
+ <Snippet text="pnpm add @ai-sdk/fireworks" dark />
17
+ </Tab>
18
+ <Tab>
19
+ <Snippet text="npm install @ai-sdk/fireworks" dark />
20
+ </Tab>
21
+ <Tab>
22
+ <Snippet text="yarn add @ai-sdk/fireworks" dark />
23
+ </Tab>
24
+
25
+ <Tab>
26
+ <Snippet text="bun add @ai-sdk/fireworks" dark />
27
+ </Tab>
28
+ </Tabs>
29
+
30
+ ## Provider Instance
31
+
32
+ You can import the default provider instance `fireworks` from `@ai-sdk/fireworks`:
33
+
34
+ ```ts
35
+ import { fireworks } from '@ai-sdk/fireworks';
36
+ ```
37
+
38
+ If you need a customized setup, you can import `createFireworks` from `@ai-sdk/fireworks`
39
+ and create a provider instance with your settings:
40
+
41
+ ```ts
42
+ import { createFireworks } from '@ai-sdk/fireworks';
43
+
44
+ const fireworks = createFireworks({
45
+ apiKey: process.env.FIREWORKS_API_KEY ?? '',
46
+ });
47
+ ```
48
+
49
+ You can use the following optional settings to customize the Fireworks provider instance:
50
+
51
+ - **baseURL** _string_
52
+
53
+ Use a different URL prefix for API calls, e.g. to use proxy servers.
54
+ The default prefix is `https://api.fireworks.ai/inference/v1`.
55
+
56
+ - **apiKey** _string_
57
+
58
+ API key that is being sent using the `Authorization` header. It defaults to
59
+ the `FIREWORKS_API_KEY` environment variable.
60
+
61
+ - **headers** _Record&lt;string,string&gt;_
62
+
63
+ Custom headers to include in the requests.
64
+
65
+ - **fetch** _(input: RequestInfo, init?: RequestInit) => Promise&lt;Response&gt;_
66
+
67
+ Custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) implementation.
68
+
69
+ ## Language Models
70
+
71
+ You can create [Fireworks models](https://fireworks.ai/models) using a provider instance.
72
+ The first argument is the model id, e.g. `accounts/fireworks/models/firefunction-v1`:
73
+
74
+ ```ts
75
+ const model = fireworks('accounts/fireworks/models/firefunction-v1');
76
+ ```
77
+
78
+ ### Reasoning Models
79
+
80
+ Fireworks exposes the thinking of `deepseek-r1` in the generated text using the `<think>` tag.
81
+ You can use the `extractReasoningMiddleware` to extract this reasoning and expose it as a `reasoning` property on the result:
82
+
83
+ ```ts
84
+ import { fireworks } from '@ai-sdk/fireworks';
85
+ import { wrapLanguageModel, extractReasoningMiddleware } from 'ai';
86
+
87
+ const enhancedModel = wrapLanguageModel({
88
+ model: fireworks('accounts/fireworks/models/deepseek-r1'),
89
+ middleware: extractReasoningMiddleware({ tagName: 'think' }),
90
+ });
91
+ ```
92
+
93
+ You can then use that enhanced model in functions like `generateText` and `streamText`.
94
+
95
+ ### Example
96
+
97
+ You can use Fireworks language models to generate text with the `generateText` function:
98
+
99
+ ```ts
100
+ import { fireworks } from '@ai-sdk/fireworks';
101
+ import { generateText } from 'ai';
102
+
103
+ const { text } = await generateText({
104
+ model: fireworks('accounts/fireworks/models/firefunction-v1'),
105
+ prompt: 'Write a vegetarian lasagna recipe for 4 people.',
106
+ });
107
+ ```
108
+
109
+ Fireworks language models can also be used in the `streamText` function
110
+ (see [AI SDK Core](/docs/ai-sdk-core)).
111
+
112
+ ### Completion Models
113
+
114
+ You can create models that call the Fireworks completions API using the `.completion()` factory method:
115
+
116
+ ```ts
117
+ const model = fireworks.completion('accounts/fireworks/models/firefunction-v1');
118
+ ```
119
+
120
+ ### Model Capabilities
121
+
122
+ | Model | Image Input | Object Generation | Tool Usage | Tool Streaming |
123
+ | ---------------------------------------------------------- | ------------------- | ------------------- | ------------------- | ------------------- |
124
+ | `accounts/fireworks/models/firefunction-v1` | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
125
+ | `accounts/fireworks/models/deepseek-r1` | <Cross size={18} /> | <Check size={18} /> | <Cross size={18} /> | <Cross size={18} /> |
126
+ | `accounts/fireworks/models/deepseek-v3` | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> | <Cross size={18} /> |
127
+ | `accounts/fireworks/models/llama-v3p1-405b-instruct` | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> |
128
+ | `accounts/fireworks/models/llama-v3p1-8b-instruct` | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> | <Cross size={18} /> |
129
+ | `accounts/fireworks/models/llama-v3p2-3b-instruct` | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> | <Cross size={18} /> |
130
+ | `accounts/fireworks/models/llama-v3p3-70b-instruct` | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> | <Cross size={18} /> |
131
+ | `accounts/fireworks/models/mixtral-8x7b-instruct` | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> | <Cross size={18} /> |
132
+ | `accounts/fireworks/models/mixtral-8x7b-instruct-hf` | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> | <Cross size={18} /> |
133
+ | `accounts/fireworks/models/mixtral-8x22b-instruct` | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> | <Cross size={18} /> |
134
+ | `accounts/fireworks/models/qwen2p5-coder-32b-instruct` | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> | <Cross size={18} /> |
135
+ | `accounts/fireworks/models/qwen2p5-72b-instruct` | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> | <Cross size={18} /> |
136
+ | `accounts/fireworks/models/qwen-qwq-32b-preview` | <Cross size={18} /> | <Check size={18} /> | <Cross size={18} /> | <Cross size={18} /> |
137
+ | `accounts/fireworks/models/qwen2-vl-72b-instruct` | <Check size={18} /> | <Check size={18} /> | <Cross size={18} /> | <Cross size={18} /> |
138
+ | `accounts/fireworks/models/llama-v3p2-11b-vision-instruct` | <Check size={18} /> | <Check size={18} /> | <Check size={18} /> | <Cross size={18} /> |
139
+ | `accounts/fireworks/models/qwq-32b` | <Cross size={18} /> | <Check size={18} /> | <Cross size={18} /> | <Cross size={18} /> |
140
+ | `accounts/fireworks/models/yi-large` | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> | <Cross size={18} /> |
141
+ | `accounts/fireworks/models/kimi-k2-instruct` | <Cross size={18} /> | <Check size={18} /> | <Check size={18} /> | <Cross size={18} /> |
142
+
143
+ <Note>
144
+ The table above lists popular models. Please see the [Fireworks models
145
+ page](https://fireworks.ai/models) for a full list of available models.
146
+ </Note>
147
+
148
+ ## Embedding Models
149
+
150
+ You can create models that call the Fireworks embeddings API using the `.embedding()` factory method:
151
+
152
+ ```ts
153
+ const model = fireworks.embedding('nomic-ai/nomic-embed-text-v1.5');
154
+ ```
155
+
156
+ You can use Fireworks embedding models to generate embeddings with the `embed` function:
157
+
158
+ ```ts
159
+ import { fireworks } from '@ai-sdk/fireworks';
160
+ import { embed } from 'ai';
161
+
162
+ const { embedding } = await embed({
163
+ model: fireworks.embedding('nomic-ai/nomic-embed-text-v1.5'),
164
+ value: 'sunny day at the beach',
165
+ });
166
+ ```
167
+
168
+ ### Model Capabilities
169
+
170
+ | Model | Dimensions | Max Tokens |
171
+ | -------------------------------- | ---------- | ---------- |
172
+ | `nomic-ai/nomic-embed-text-v1.5` | 768 | 8192 |
173
+
174
+ <Note>
175
+ For more embedding models, see the [Fireworks models
176
+ page](https://fireworks.ai/models) for a full list of available models.
177
+ </Note>
178
+
179
+ ## Image Models
180
+
181
+ You can create Fireworks image models using the `.image()` factory method.
182
+ For more on image generation with the AI SDK see [generateImage()](/docs/reference/ai-sdk-core/generate-image).
183
+
184
+ ```ts
185
+ import { fireworks } from '@ai-sdk/fireworks';
186
+ import { generateImage } from 'ai';
187
+
188
+ const { image } = await generateImage({
189
+ model: fireworks.image('accounts/fireworks/models/flux-1-dev-fp8'),
190
+ prompt: 'A futuristic cityscape at sunset',
191
+ aspectRatio: '16:9',
192
+ });
193
+ ```
194
+
195
+ <Note>
196
+ Model support for `size` and `aspectRatio` parameters varies. See the [Model
197
+ Capabilities](#model-capabilities-1) section below for supported dimensions,
198
+ or check the model's documentation on [Fireworks models
199
+ page](https://fireworks.ai/models) for more details.
200
+ </Note>
201
+
202
+ ### Image Editing
203
+
204
+ Fireworks supports image editing through FLUX Kontext models (`flux-kontext-pro` and `flux-kontext-max`). Pass input images via `prompt.images` to transform or edit existing images.
205
+
206
+ <Note>
207
+ Fireworks Kontext models do not support explicit masks. Editing is
208
+ prompt-driven — describe what you want to change in the text prompt.
209
+ </Note>
210
+
211
+ #### Basic Image Editing
212
+
213
+ Transform an existing image using text prompts:
214
+
215
+ ```ts
216
+ const imageBuffer = readFileSync('./input-image.png');
217
+
218
+ const { images } = await generateImage({
219
+ model: fireworks.image('accounts/fireworks/models/flux-kontext-pro'),
220
+ prompt: {
221
+ text: 'Turn the cat into a golden retriever dog',
222
+ images: [imageBuffer],
223
+ },
224
+ providerOptions: {
225
+ fireworks: {
226
+ output_format: 'jpeg',
227
+ },
228
+ },
229
+ });
230
+ ```
231
+
232
+ #### Style Transfer
233
+
234
+ Apply artistic styles to an image:
235
+
236
+ ```ts
237
+ const imageBuffer = readFileSync('./input-image.png');
238
+
239
+ const { images } = await generateImage({
240
+ model: fireworks.image('accounts/fireworks/models/flux-kontext-pro'),
241
+ prompt: {
242
+ text: 'Transform this into a watercolor painting style',
243
+ images: [imageBuffer],
244
+ },
245
+ aspectRatio: '1:1',
246
+ });
247
+ ```
248
+
249
+ <Note>
250
+ Input images can be provided as `Buffer`, `ArrayBuffer`, `Uint8Array`, or
251
+ base64-encoded strings. Fireworks only supports a single input image per
252
+ request.
253
+ </Note>
254
+
255
+ ### Model Capabilities
256
+
257
+ For all models supporting aspect ratios, the following aspect ratios are supported:
258
+
259
+ `1:1 (default), 2:3, 3:2, 4:5, 5:4, 16:9, 9:16, 9:21, 21:9`
260
+
261
+ For all models supporting size, the following sizes are supported:
262
+
263
+ `640 x 1536, 768 x 1344, 832 x 1216, 896 x 1152, 1024x1024 (default), 1152 x 896, 1216 x 832, 1344 x 768, 1536 x 640`
264
+
265
+ | Model | Dimensions Specification | Image Editing |
266
+ | ------------------------------------------------------------ | ------------------------ | ------------------- |
267
+ | `accounts/fireworks/models/flux-kontext-pro` | Aspect Ratio | <Check size={18} /> |
268
+ | `accounts/fireworks/models/flux-kontext-max` | Aspect Ratio | <Check size={18} /> |
269
+ | `accounts/fireworks/models/flux-1-dev-fp8` | Aspect Ratio | <Cross size={18} /> |
270
+ | `accounts/fireworks/models/flux-1-schnell-fp8` | Aspect Ratio | <Cross size={18} /> |
271
+ | `accounts/fireworks/models/playground-v2-5-1024px-aesthetic` | Size | <Cross size={18} /> |
272
+ | `accounts/fireworks/models/japanese-stable-diffusion-xl` | Size | <Cross size={18} /> |
273
+ | `accounts/fireworks/models/playground-v2-1024px-aesthetic` | Size | <Cross size={18} /> |
274
+ | `accounts/fireworks/models/SSD-1B` | Size | <Cross size={18} /> |
275
+ | `accounts/fireworks/models/stable-diffusion-xl-1024-v1-0` | Size | <Cross size={18} /> |
276
+
277
+ For more details, see the [Fireworks models page](https://fireworks.ai/models).
278
+
279
+ #### Stability AI Models
280
+
281
+ Fireworks also presents several Stability AI models backed by Stability AI API
282
+ keys and endpoint. The AI SDK Fireworks provider does not currently include
283
+ support for these models:
284
+
285
+ | Model ID |
286
+ | -------------------------------------- |
287
+ | `accounts/stability/models/sd3-turbo` |
288
+ | `accounts/stability/models/sd3-medium` |
289
+ | `accounts/stability/models/sd3` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/fireworks",
3
- "version": "2.0.17",
3
+ "version": "2.0.19",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -8,10 +8,18 @@
8
8
  "types": "./dist/index.d.ts",
9
9
  "files": [
10
10
  "dist/**/*",
11
+ "docs/**/*",
11
12
  "src",
13
+ "!src/**/*.test.ts",
14
+ "!src/**/*.test-d.ts",
15
+ "!src/**/__snapshots__",
16
+ "!src/**/__fixtures__",
12
17
  "CHANGELOG.md",
13
18
  "README.md"
14
19
  ],
20
+ "directories": {
21
+ "doc": "./docs"
22
+ },
15
23
  "exports": {
16
24
  "./package.json": "./package.json",
17
25
  ".": {
@@ -21,16 +29,16 @@
21
29
  }
22
30
  },
23
31
  "dependencies": {
24
- "@ai-sdk/openai-compatible": "2.0.17",
25
- "@ai-sdk/provider": "3.0.4",
26
- "@ai-sdk/provider-utils": "4.0.8"
32
+ "@ai-sdk/openai-compatible": "2.0.18",
33
+ "@ai-sdk/provider": "3.0.5",
34
+ "@ai-sdk/provider-utils": "4.0.9"
27
35
  },
28
36
  "devDependencies": {
29
37
  "@types/node": "20.17.24",
30
38
  "tsup": "^8",
31
39
  "typescript": "5.8.3",
32
40
  "zod": "3.25.76",
33
- "@ai-sdk/test-server": "1.0.2",
41
+ "@ai-sdk/test-server": "1.0.3",
34
42
  "@vercel/ai-tsconfig": "0.0.0"
35
43
  },
36
44
  "peerDependencies": {
@@ -56,7 +64,7 @@
56
64
  "scripts": {
57
65
  "build": "pnpm clean && tsup --tsconfig tsconfig.build.json",
58
66
  "build:watch": "pnpm clean && tsup --watch",
59
- "clean": "del-cli dist *.tsbuildinfo",
67
+ "clean": "del-cli dist docs *.tsbuildinfo",
60
68
  "lint": "eslint \"./**/*.ts*\"",
61
69
  "type-check": "tsc --build",
62
70
  "prettier-check": "prettier --check \"./**/*.ts*\"",
@@ -1,629 +0,0 @@
1
- import { FetchFunction } from '@ai-sdk/provider-utils';
2
- import { createTestServer } from '@ai-sdk/test-server/with-vitest';
3
- import { describe, expect, it, vi } from 'vitest';
4
- import { FireworksImageModel } from './fireworks-image-model';
5
-
6
- const prompt = 'A cute baby sea otter';
7
-
8
- function createBasicModel({
9
- headers,
10
- fetch,
11
- currentDate,
12
- }: {
13
- headers?: () => Record<string, string>;
14
- fetch?: FetchFunction;
15
- currentDate?: () => Date;
16
- } = {}) {
17
- return new FireworksImageModel('accounts/fireworks/models/flux-1-dev-fp8', {
18
- provider: 'fireworks',
19
- baseURL: 'https://api.example.com',
20
- headers: headers ?? (() => ({ 'api-key': 'test-key' })),
21
- fetch,
22
- _internal: {
23
- currentDate,
24
- },
25
- });
26
- }
27
-
28
- function createSizeModel() {
29
- return new FireworksImageModel(
30
- 'accounts/fireworks/models/playground-v2-5-1024px-aesthetic',
31
- {
32
- provider: 'fireworks',
33
- baseURL: 'https://api.size-example.com',
34
- headers: () => ({ 'api-key': 'test-key' }),
35
- },
36
- );
37
- }
38
-
39
- describe('FireworksImageModel', () => {
40
- const server = createTestServer({
41
- 'https://api.example.com/*': {
42
- response: {
43
- type: 'binary',
44
- body: Buffer.from('test-binary-content'),
45
- },
46
- },
47
- 'https://api.size-example.com/*': {
48
- response: {
49
- type: 'binary',
50
- body: Buffer.from('test-binary-content'),
51
- },
52
- },
53
- });
54
-
55
- describe('doGenerate', () => {
56
- it('should pass the correct parameters including aspect ratio and seed', async () => {
57
- const model = createBasicModel();
58
-
59
- await model.doGenerate({
60
- prompt,
61
- files: undefined,
62
- mask: undefined,
63
- n: 1,
64
- size: undefined,
65
- aspectRatio: '16:9',
66
- seed: 42,
67
- providerOptions: { fireworks: { additional_param: 'value' } },
68
- });
69
-
70
- expect(await server.calls[0].requestBodyJson).toStrictEqual({
71
- prompt,
72
- aspect_ratio: '16:9',
73
- seed: 42,
74
- samples: 1,
75
- additional_param: 'value',
76
- });
77
- });
78
-
79
- it('should call the correct url', async () => {
80
- const model = createBasicModel();
81
-
82
- await model.doGenerate({
83
- prompt,
84
- files: undefined,
85
- mask: undefined,
86
- n: 1,
87
- size: undefined,
88
- aspectRatio: '16:9',
89
- seed: 42,
90
- providerOptions: { fireworks: { additional_param: 'value' } },
91
- });
92
-
93
- expect(server.calls[0].requestMethod).toStrictEqual('POST');
94
- expect(server.calls[0].requestUrl).toStrictEqual(
95
- 'https://api.example.com/workflows/accounts/fireworks/models/flux-1-dev-fp8/text_to_image',
96
- );
97
- });
98
-
99
- it('should pass headers', async () => {
100
- const modelWithHeaders = createBasicModel({
101
- headers: () => ({
102
- 'Custom-Provider-Header': 'provider-header-value',
103
- }),
104
- });
105
-
106
- await modelWithHeaders.doGenerate({
107
- prompt,
108
- files: undefined,
109
- mask: undefined,
110
- n: 1,
111
- size: undefined,
112
- aspectRatio: undefined,
113
- seed: undefined,
114
- providerOptions: {},
115
- headers: {
116
- 'Custom-Request-Header': 'request-header-value',
117
- },
118
- });
119
-
120
- expect(server.calls[0].requestHeaders).toStrictEqual({
121
- 'content-type': 'application/json',
122
- 'custom-provider-header': 'provider-header-value',
123
- 'custom-request-header': 'request-header-value',
124
- });
125
- });
126
-
127
- it('should handle empty response body', async () => {
128
- server.urls['https://api.example.com/*'].response = {
129
- type: 'empty',
130
- };
131
-
132
- const model = createBasicModel();
133
- await expect(
134
- model.doGenerate({
135
- prompt,
136
- files: undefined,
137
- mask: undefined,
138
- n: 1,
139
- size: undefined,
140
- aspectRatio: undefined,
141
- seed: undefined,
142
- providerOptions: {},
143
- }),
144
- ).rejects.toMatchObject({
145
- message: 'Response body is empty',
146
- statusCode: 200,
147
- url: 'https://api.example.com/workflows/accounts/fireworks/models/flux-1-dev-fp8/text_to_image',
148
- requestBodyValues: {
149
- prompt: 'A cute baby sea otter',
150
- },
151
- });
152
- });
153
-
154
- it('should handle API errors', async () => {
155
- server.urls['https://api.example.com/*'].response = {
156
- type: 'error',
157
- status: 400,
158
- body: 'Bad Request',
159
- };
160
-
161
- const model = createBasicModel();
162
- await expect(
163
- model.doGenerate({
164
- prompt,
165
- files: undefined,
166
- mask: undefined,
167
- n: 1,
168
- size: undefined,
169
- aspectRatio: undefined,
170
- seed: undefined,
171
- providerOptions: {},
172
- }),
173
- ).rejects.toMatchObject({
174
- message: 'Bad Request',
175
- statusCode: 400,
176
- url: 'https://api.example.com/workflows/accounts/fireworks/models/flux-1-dev-fp8/text_to_image',
177
- requestBodyValues: {
178
- prompt: 'A cute baby sea otter',
179
- },
180
- responseBody: 'Bad Request',
181
- });
182
- });
183
-
184
- it('should handle size parameter for supported models', async () => {
185
- const sizeModel = createSizeModel();
186
-
187
- await sizeModel.doGenerate({
188
- prompt,
189
- files: undefined,
190
- mask: undefined,
191
- n: 1,
192
- size: '1024x768',
193
- aspectRatio: undefined,
194
- seed: 42,
195
- providerOptions: {},
196
- });
197
-
198
- expect(await server.calls[0].requestBodyJson).toStrictEqual({
199
- prompt,
200
- width: '1024',
201
- height: '768',
202
- seed: 42,
203
- samples: 1,
204
- });
205
- });
206
-
207
- describe('warnings', () => {
208
- it('should return size warning on workflow model', async () => {
209
- const model = createBasicModel();
210
-
211
- const result1 = await model.doGenerate({
212
- prompt,
213
- files: undefined,
214
- mask: undefined,
215
- n: 1,
216
- size: '1024x1024',
217
- aspectRatio: '1:1',
218
- seed: 123,
219
- providerOptions: {},
220
- });
221
-
222
- expect(result1.warnings).toMatchInlineSnapshot(`
223
- [
224
- {
225
- "details": "This model does not support the \`size\` option. Use \`aspectRatio\` instead.",
226
- "feature": "size",
227
- "type": "unsupported",
228
- },
229
- ]
230
- `);
231
- });
232
-
233
- it('should return aspectRatio warning on size-supporting model', async () => {
234
- const sizeModel = createSizeModel();
235
-
236
- const result2 = await sizeModel.doGenerate({
237
- prompt,
238
- files: undefined,
239
- mask: undefined,
240
- n: 1,
241
- size: '1024x1024',
242
- aspectRatio: '1:1',
243
- seed: 123,
244
- providerOptions: {},
245
- });
246
-
247
- expect(result2.warnings).toMatchInlineSnapshot(`
248
- [
249
- {
250
- "details": "This model does not support the \`aspectRatio\` option.",
251
- "feature": "aspectRatio",
252
- "type": "unsupported",
253
- },
254
- ]
255
- `);
256
- });
257
- });
258
-
259
- it('should respect the abort signal', async () => {
260
- const model = createBasicModel();
261
- const controller = new AbortController();
262
-
263
- const generatePromise = model.doGenerate({
264
- prompt,
265
- files: undefined,
266
- mask: undefined,
267
- n: 1,
268
- size: undefined,
269
- aspectRatio: undefined,
270
- seed: undefined,
271
- providerOptions: {},
272
- abortSignal: controller.signal,
273
- });
274
-
275
- controller.abort();
276
-
277
- await expect(generatePromise).rejects.toThrow(
278
- 'This operation was aborted',
279
- );
280
- });
281
-
282
- it('should use custom fetch function when provided', async () => {
283
- const mockFetch = vi.fn().mockResolvedValue(
284
- new Response(Buffer.from('mock-image-data'), {
285
- status: 200,
286
- }),
287
- );
288
-
289
- const model = createBasicModel({
290
- fetch: mockFetch,
291
- });
292
-
293
- await model.doGenerate({
294
- prompt,
295
- files: undefined,
296
- mask: undefined,
297
- n: 1,
298
- size: undefined,
299
- aspectRatio: undefined,
300
- seed: undefined,
301
- providerOptions: {},
302
- });
303
-
304
- expect(mockFetch).toHaveBeenCalled();
305
- });
306
-
307
- it('should pass samples parameter to API', async () => {
308
- const model = createBasicModel();
309
-
310
- await model.doGenerate({
311
- prompt,
312
- files: undefined,
313
- mask: undefined,
314
- n: 42,
315
- size: undefined,
316
- aspectRatio: undefined,
317
- seed: undefined,
318
- providerOptions: {},
319
- });
320
-
321
- expect(await server.calls[0].requestBodyJson).toHaveProperty(
322
- 'samples',
323
- 42,
324
- );
325
- });
326
-
327
- describe('response metadata', () => {
328
- it('should include timestamp, headers and modelId in response', async () => {
329
- const testDate = new Date('2024-01-01T00:00:00Z');
330
- const model = createBasicModel({
331
- currentDate: () => testDate,
332
- });
333
-
334
- const result = await model.doGenerate({
335
- prompt,
336
- files: undefined,
337
- mask: undefined,
338
- n: 1,
339
- size: undefined,
340
- aspectRatio: undefined,
341
- seed: undefined,
342
- providerOptions: {},
343
- });
344
-
345
- expect(result.response).toStrictEqual({
346
- timestamp: testDate,
347
- modelId: 'accounts/fireworks/models/flux-1-dev-fp8',
348
- headers: expect.any(Object),
349
- });
350
- });
351
-
352
- it('should include response headers from API call', async () => {
353
- server.urls['https://api.example.com/*'].response = {
354
- type: 'binary',
355
- body: Buffer.from('test-binary-content'),
356
- headers: {
357
- 'x-request-id': 'test-request-id',
358
- 'content-type': 'image/png',
359
- },
360
- };
361
-
362
- const model = createBasicModel();
363
- const result = await model.doGenerate({
364
- prompt,
365
- files: undefined,
366
- mask: undefined,
367
- n: 1,
368
- size: undefined,
369
- aspectRatio: undefined,
370
- seed: undefined,
371
- providerOptions: {},
372
- });
373
-
374
- expect(result.response.headers).toStrictEqual({
375
- 'content-length': '19',
376
- 'x-request-id': 'test-request-id',
377
- 'content-type': 'image/png',
378
- });
379
- });
380
- });
381
- });
382
-
383
- describe('constructor', () => {
384
- it('should expose correct provider and model information', () => {
385
- const model = createBasicModel();
386
-
387
- expect(model.provider).toBe('fireworks');
388
- expect(model.modelId).toBe('accounts/fireworks/models/flux-1-dev-fp8');
389
- expect(model.specificationVersion).toBe('v3');
390
- expect(model.maxImagesPerCall).toBe(1);
391
- });
392
- });
393
-
394
- describe('Image Editing', () => {
395
- const editServer = createTestServer({
396
- 'https://api.edit.example.com/*': {
397
- response: {
398
- type: 'binary',
399
- body: Buffer.from('edited-image-data'),
400
- },
401
- },
402
- });
403
-
404
- function createKontextModel() {
405
- return new FireworksImageModel(
406
- 'accounts/fireworks/models/flux-kontext-pro',
407
- {
408
- provider: 'fireworks',
409
- baseURL: 'https://api.edit.example.com',
410
- headers: () => ({ 'api-key': 'test-key' }),
411
- },
412
- );
413
- }
414
-
415
- it('should send edit request with files as data URI', async () => {
416
- const imageData = new Uint8Array([137, 80, 78, 71]); // PNG magic bytes
417
-
418
- await createKontextModel().doGenerate({
419
- prompt: 'Turn the cat into a dog',
420
- files: [
421
- {
422
- type: 'file',
423
- data: imageData,
424
- mediaType: 'image/png',
425
- },
426
- ],
427
- mask: undefined,
428
- n: 1,
429
- size: undefined,
430
- aspectRatio: undefined,
431
- seed: undefined,
432
- providerOptions: {},
433
- });
434
-
435
- const requestBody = await editServer.calls[0].requestBodyJson;
436
- expect(requestBody).toMatchInlineSnapshot(`
437
- {
438
- "input_image": "data:image/png;base64,iVBORw==",
439
- "prompt": "Turn the cat into a dog",
440
- "samples": 1,
441
- }
442
- `);
443
- });
444
-
445
- it('should use correct URL for Kontext model (no text_to_image suffix)', async () => {
446
- const imageData = new Uint8Array([137, 80, 78, 71]);
447
-
448
- await createKontextModel().doGenerate({
449
- prompt: 'Edit this image',
450
- files: [
451
- {
452
- type: 'file',
453
- data: imageData,
454
- mediaType: 'image/png',
455
- },
456
- ],
457
- mask: undefined,
458
- n: 1,
459
- size: undefined,
460
- aspectRatio: undefined,
461
- seed: undefined,
462
- providerOptions: {},
463
- });
464
-
465
- expect(editServer.calls[0].requestUrl).toBe(
466
- 'https://api.edit.example.com/workflows/accounts/fireworks/models/flux-kontext-pro',
467
- );
468
- });
469
-
470
- it('should send edit request with URL-based file', async () => {
471
- await createKontextModel().doGenerate({
472
- prompt: 'Edit this image',
473
- files: [
474
- {
475
- type: 'url',
476
- url: 'https://example.com/input.png',
477
- },
478
- ],
479
- mask: undefined,
480
- n: 1,
481
- size: undefined,
482
- aspectRatio: undefined,
483
- seed: undefined,
484
- providerOptions: {},
485
- });
486
-
487
- const requestBody = await editServer.calls[0].requestBodyJson;
488
- expect(requestBody).toMatchInlineSnapshot(`
489
- {
490
- "input_image": "https://example.com/input.png",
491
- "prompt": "Edit this image",
492
- "samples": 1,
493
- }
494
- `);
495
- });
496
-
497
- it('should send edit request with base64 string data', async () => {
498
- await createKontextModel().doGenerate({
499
- prompt: 'Edit this image',
500
- files: [
501
- {
502
- type: 'file',
503
- data: 'iVBORw0KGgoAAAANSUhEUgAAAAE=',
504
- mediaType: 'image/png',
505
- },
506
- ],
507
- mask: undefined,
508
- n: 1,
509
- size: undefined,
510
- aspectRatio: undefined,
511
- seed: undefined,
512
- providerOptions: {},
513
- });
514
-
515
- const requestBody = await editServer.calls[0].requestBodyJson;
516
- expect(requestBody).toMatchInlineSnapshot(`
517
- {
518
- "input_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAE=",
519
- "prompt": "Edit this image",
520
- "samples": 1,
521
- }
522
- `);
523
- });
524
-
525
- it('should warn when multiple files are provided', async () => {
526
- const imageData = new Uint8Array([137, 80, 78, 71]);
527
-
528
- const result = await createKontextModel().doGenerate({
529
- prompt: 'Edit images',
530
- files: [
531
- {
532
- type: 'file',
533
- data: imageData,
534
- mediaType: 'image/png',
535
- },
536
- {
537
- type: 'file',
538
- data: imageData,
539
- mediaType: 'image/png',
540
- },
541
- ],
542
- mask: undefined,
543
- n: 1,
544
- size: undefined,
545
- aspectRatio: undefined,
546
- seed: undefined,
547
- providerOptions: {},
548
- });
549
-
550
- expect(result.warnings).toContainEqual({
551
- type: 'other',
552
- message:
553
- 'Fireworks only supports a single input image. Additional images are ignored.',
554
- });
555
- });
556
-
557
- it('should warn when mask is provided', async () => {
558
- const imageData = new Uint8Array([137, 80, 78, 71]);
559
- const maskData = new Uint8Array([255, 255, 255, 0]);
560
-
561
- const result = await createKontextModel().doGenerate({
562
- prompt: 'Edit with mask',
563
- files: [
564
- {
565
- type: 'file',
566
- data: imageData,
567
- mediaType: 'image/png',
568
- },
569
- ],
570
- mask: {
571
- type: 'file',
572
- data: maskData,
573
- mediaType: 'image/png',
574
- },
575
- n: 1,
576
- size: undefined,
577
- aspectRatio: undefined,
578
- seed: undefined,
579
- providerOptions: {},
580
- });
581
-
582
- expect(result.warnings).toContainEqual({
583
- type: 'unsupported',
584
- feature: 'mask',
585
- details:
586
- 'Fireworks Kontext models do not support explicit masks. Use the prompt to describe the areas to edit.',
587
- });
588
- });
589
-
590
- it('should pass provider options with edit request', async () => {
591
- const imageData = new Uint8Array([137, 80, 78, 71]);
592
-
593
- await createKontextModel().doGenerate({
594
- prompt: 'Edit with options',
595
- files: [
596
- {
597
- type: 'file',
598
- data: imageData,
599
- mediaType: 'image/png',
600
- },
601
- ],
602
- mask: undefined,
603
- n: 1,
604
- size: undefined,
605
- aspectRatio: '16:9',
606
- seed: 42,
607
- providerOptions: {
608
- fireworks: {
609
- output_format: 'jpeg',
610
- safety_tolerance: 2,
611
- },
612
- },
613
- });
614
-
615
- const requestBody = await editServer.calls[0].requestBodyJson;
616
- expect(requestBody).toMatchInlineSnapshot(`
617
- {
618
- "aspect_ratio": "16:9",
619
- "input_image": "data:image/png;base64,iVBORw==",
620
- "output_format": "jpeg",
621
- "prompt": "Edit with options",
622
- "safety_tolerance": 2,
623
- "samples": 1,
624
- "seed": 42,
625
- }
626
- `);
627
- });
628
- });
629
- });
@@ -1,198 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
2
- import { createFireworks } from './fireworks-provider';
3
- import { LanguageModelV3, EmbeddingModelV3 } from '@ai-sdk/provider';
4
- import { loadApiKey } from '@ai-sdk/provider-utils';
5
- import {
6
- OpenAICompatibleChatLanguageModel,
7
- OpenAICompatibleCompletionLanguageModel,
8
- OpenAICompatibleEmbeddingModel,
9
- } from '@ai-sdk/openai-compatible';
10
- import { FireworksImageModel } from './fireworks-image-model';
11
-
12
- // Add type assertion for the mocked class
13
- const OpenAICompatibleChatLanguageModelMock =
14
- OpenAICompatibleChatLanguageModel as unknown as Mock;
15
-
16
- vi.mock('@ai-sdk/openai-compatible', () => {
17
- // Create mock constructor functions that behave like classes
18
- const createMockConstructor = (providerName: string) => {
19
- const mockConstructor = vi.fn().mockImplementation(function (
20
- this: any,
21
- modelId: string,
22
- settings: any,
23
- ) {
24
- this.provider = providerName;
25
- this.modelId = modelId;
26
- this.settings = settings;
27
- });
28
- return mockConstructor;
29
- };
30
-
31
- return {
32
- OpenAICompatibleChatLanguageModel: createMockConstructor('fireworks.chat'),
33
- OpenAICompatibleCompletionLanguageModel: createMockConstructor(
34
- 'fireworks.completion',
35
- ),
36
- OpenAICompatibleEmbeddingModel: createMockConstructor(
37
- 'fireworks.embedding',
38
- ),
39
- };
40
- });
41
-
42
- vi.mock('@ai-sdk/provider-utils', async () => {
43
- const actual = await vi.importActual('@ai-sdk/provider-utils');
44
- return {
45
- ...actual,
46
- loadApiKey: vi.fn().mockReturnValue('mock-api-key'),
47
- withoutTrailingSlash: vi.fn(url => url),
48
- };
49
- });
50
-
51
- vi.mock('./fireworks-image-model', () => ({
52
- FireworksImageModel: vi.fn(),
53
- }));
54
-
55
- describe('FireworksProvider', () => {
56
- let mockLanguageModel: LanguageModelV3;
57
- let mockEmbeddingModel: EmbeddingModelV3;
58
-
59
- beforeEach(() => {
60
- // Mock implementations of models
61
- mockLanguageModel = {
62
- // Add any required methods for LanguageModelV3
63
- } as LanguageModelV3;
64
- mockEmbeddingModel = {
65
- // Add any required methods for EmbeddingModelV3
66
- } as EmbeddingModelV3;
67
-
68
- // Reset mocks
69
- vi.clearAllMocks();
70
- });
71
-
72
- describe('createFireworks', () => {
73
- it('should create a FireworksProvider instance with default options', () => {
74
- const provider = createFireworks();
75
- const model = provider('model-id');
76
-
77
- // Use the mocked version
78
- const constructorCall =
79
- OpenAICompatibleChatLanguageModelMock.mock.calls[0];
80
- const config = constructorCall[1];
81
- config.headers();
82
-
83
- expect(loadApiKey).toHaveBeenCalledWith({
84
- apiKey: undefined,
85
- environmentVariableName: 'FIREWORKS_API_KEY',
86
- description: 'Fireworks API key',
87
- });
88
- });
89
-
90
- it('should create a FireworksProvider instance with custom options', () => {
91
- const options = {
92
- apiKey: 'custom-key',
93
- baseURL: 'https://custom.url',
94
- headers: { 'Custom-Header': 'value' },
95
- };
96
- const provider = createFireworks(options);
97
- const model = provider('model-id');
98
-
99
- const constructorCall =
100
- OpenAICompatibleChatLanguageModelMock.mock.calls[0];
101
- const config = constructorCall[1];
102
- config.headers();
103
-
104
- expect(loadApiKey).toHaveBeenCalledWith({
105
- apiKey: 'custom-key',
106
- environmentVariableName: 'FIREWORKS_API_KEY',
107
- description: 'Fireworks API key',
108
- });
109
- });
110
-
111
- it('should return a chat model when called as a function', () => {
112
- const provider = createFireworks();
113
- const modelId = 'foo-model-id';
114
-
115
- const model = provider(modelId);
116
- expect(model).toBeInstanceOf(OpenAICompatibleChatLanguageModel);
117
- });
118
- });
119
-
120
- describe('chatModel', () => {
121
- it('should construct a chat model with correct configuration', () => {
122
- const provider = createFireworks();
123
- const modelId = 'fireworks-chat-model';
124
-
125
- const model = provider.chatModel(modelId);
126
-
127
- expect(model).toBeInstanceOf(OpenAICompatibleChatLanguageModel);
128
- });
129
- });
130
-
131
- describe('completionModel', () => {
132
- it('should construct a completion model with correct configuration', () => {
133
- const provider = createFireworks();
134
- const modelId = 'fireworks-completion-model';
135
-
136
- const model = provider.completionModel(modelId);
137
-
138
- expect(model).toBeInstanceOf(OpenAICompatibleCompletionLanguageModel);
139
- });
140
- });
141
-
142
- describe('embeddingModel', () => {
143
- it('should construct a text embedding model with correct configuration', () => {
144
- const provider = createFireworks();
145
- const modelId = 'fireworks-embedding-model';
146
-
147
- const model = provider.embeddingModel(modelId);
148
-
149
- expect(model).toBeInstanceOf(OpenAICompatibleEmbeddingModel);
150
- });
151
- });
152
-
153
- describe('image', () => {
154
- it('should construct an image model with correct configuration', () => {
155
- const provider = createFireworks();
156
- const modelId = 'accounts/fireworks/models/flux-1-dev-fp8';
157
-
158
- const model = provider.image(modelId);
159
-
160
- expect(model).toBeInstanceOf(FireworksImageModel);
161
- expect(FireworksImageModel).toHaveBeenCalledWith(
162
- modelId,
163
- expect.objectContaining({
164
- provider: 'fireworks.image',
165
- baseURL: 'https://api.fireworks.ai/inference/v1',
166
- }),
167
- );
168
- });
169
-
170
- it('should use default settings when none provided', () => {
171
- const provider = createFireworks();
172
- const modelId = 'accounts/fireworks/models/flux-1-dev-fp8';
173
-
174
- const model = provider.image(modelId);
175
-
176
- expect(model).toBeInstanceOf(FireworksImageModel);
177
- expect(FireworksImageModel).toHaveBeenCalledWith(
178
- modelId,
179
- expect.any(Object),
180
- );
181
- });
182
-
183
- it('should respect custom baseURL', () => {
184
- const customBaseURL = 'https://custom.api.fireworks.ai';
185
- const provider = createFireworks({ baseURL: customBaseURL });
186
- const modelId = 'accounts/fireworks/models/flux-1-dev-fp8';
187
-
188
- provider.image(modelId);
189
-
190
- expect(FireworksImageModel).toHaveBeenCalledWith(
191
- modelId,
192
- expect.objectContaining({
193
- baseURL: customBaseURL,
194
- }),
195
- );
196
- });
197
- });
198
- });