@ai-sdk/togetherai 2.0.18 → 2.0.20

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.
@@ -1,488 +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 } from 'vitest';
4
- import { TogetherAIImageModel } from './togetherai-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 TogetherAIImageModel('stabilityai/stable-diffusion-xl', {
18
- provider: 'togetherai',
19
- baseURL: 'https://api.example.com',
20
- headers: headers ?? (() => ({ 'api-key': 'test-key' })),
21
- fetch,
22
- _internal: {
23
- currentDate,
24
- },
25
- });
26
- }
27
-
28
- const server = createTestServer({
29
- 'https://api.example.com/*': {
30
- response: {
31
- type: 'json-value',
32
- body: {
33
- id: 'test-id',
34
- data: [{ index: 0, b64_json: 'test-base64-content' }],
35
- model: 'stabilityai/stable-diffusion-xl',
36
- object: 'list',
37
- },
38
- },
39
- },
40
- });
41
-
42
- describe('doGenerate', () => {
43
- it('should pass the correct parameters including size and seed', async () => {
44
- const model = createBasicModel();
45
-
46
- await model.doGenerate({
47
- prompt,
48
- files: undefined,
49
- mask: undefined,
50
- n: 1,
51
- size: '1024x1024',
52
- seed: 42,
53
- providerOptions: { togetherai: { additional_param: 'value' } },
54
- aspectRatio: undefined,
55
- });
56
-
57
- expect(await server.calls[0].requestBodyJson).toStrictEqual({
58
- model: 'stabilityai/stable-diffusion-xl',
59
- prompt,
60
- seed: 42,
61
- width: 1024,
62
- height: 1024,
63
- response_format: 'base64',
64
- additional_param: 'value',
65
- });
66
- });
67
-
68
- it('should include n parameter when requesting multiple images', async () => {
69
- const model = createBasicModel();
70
-
71
- await model.doGenerate({
72
- prompt,
73
- files: undefined,
74
- mask: undefined,
75
- n: 3,
76
- size: '1024x1024',
77
- seed: 42,
78
- providerOptions: {},
79
- aspectRatio: undefined,
80
- });
81
-
82
- expect(await server.calls[0].requestBodyJson).toStrictEqual({
83
- model: 'stabilityai/stable-diffusion-xl',
84
- prompt,
85
- seed: 42,
86
- n: 3,
87
- width: 1024,
88
- height: 1024,
89
- response_format: 'base64',
90
- });
91
- });
92
-
93
- it('should call the correct url', async () => {
94
- const model = createBasicModel();
95
-
96
- await model.doGenerate({
97
- prompt,
98
- files: undefined,
99
- mask: undefined,
100
- n: 1,
101
- size: '1024x1024',
102
- seed: 42,
103
- providerOptions: {},
104
- aspectRatio: undefined,
105
- });
106
-
107
- expect(server.calls[0].requestMethod).toStrictEqual('POST');
108
- expect(server.calls[0].requestUrl).toStrictEqual(
109
- 'https://api.example.com/images/generations',
110
- );
111
- });
112
-
113
- it('should pass headers', async () => {
114
- const modelWithHeaders = createBasicModel({
115
- headers: () => ({
116
- 'Custom-Provider-Header': 'provider-header-value',
117
- }),
118
- });
119
-
120
- await modelWithHeaders.doGenerate({
121
- prompt,
122
- files: undefined,
123
- mask: undefined,
124
- n: 1,
125
- size: undefined,
126
- seed: undefined,
127
- providerOptions: {},
128
- aspectRatio: undefined,
129
- headers: {
130
- 'Custom-Request-Header': 'request-header-value',
131
- },
132
- });
133
-
134
- expect(server.calls[0].requestHeaders).toStrictEqual({
135
- 'content-type': 'application/json',
136
- 'custom-provider-header': 'provider-header-value',
137
- 'custom-request-header': 'request-header-value',
138
- });
139
- });
140
-
141
- it('should handle API errors', async () => {
142
- server.urls['https://api.example.com/*'].response = {
143
- type: 'error',
144
- status: 400,
145
- body: JSON.stringify({
146
- error: {
147
- message: 'Bad Request',
148
- },
149
- }),
150
- };
151
-
152
- const model = createBasicModel();
153
- await expect(
154
- model.doGenerate({
155
- prompt,
156
- files: undefined,
157
- mask: undefined,
158
- n: 1,
159
- size: undefined,
160
- seed: undefined,
161
- providerOptions: {},
162
- aspectRatio: undefined,
163
- }),
164
- ).rejects.toMatchObject({
165
- message: 'Bad Request',
166
- });
167
- });
168
-
169
- describe('warnings', () => {
170
- it('should return aspectRatio warning when aspectRatio is provided', async () => {
171
- const model = createBasicModel();
172
-
173
- const result = await model.doGenerate({
174
- prompt,
175
- files: undefined,
176
- mask: undefined,
177
- n: 1,
178
- size: '1024x1024',
179
- aspectRatio: '1:1',
180
- seed: 123,
181
- providerOptions: {},
182
- });
183
-
184
- expect(result.warnings).toMatchInlineSnapshot(`
185
- [
186
- {
187
- "details": "This model does not support the \`aspectRatio\` option. Use \`size\` instead.",
188
- "feature": "aspectRatio",
189
- "type": "unsupported",
190
- },
191
- ]
192
- `);
193
- });
194
- });
195
-
196
- it('should respect the abort signal', async () => {
197
- const model = createBasicModel();
198
- const controller = new AbortController();
199
-
200
- const generatePromise = model.doGenerate({
201
- prompt,
202
- files: undefined,
203
- mask: undefined,
204
- n: 1,
205
- size: undefined,
206
- seed: undefined,
207
- providerOptions: {},
208
- aspectRatio: undefined,
209
- abortSignal: controller.signal,
210
- });
211
-
212
- controller.abort();
213
-
214
- await expect(generatePromise).rejects.toThrow('This operation was aborted');
215
- });
216
-
217
- describe('response metadata', () => {
218
- it('should include timestamp, headers and modelId in response', async () => {
219
- const testDate = new Date('2024-01-01T00:00:00Z');
220
- const model = createBasicModel({
221
- currentDate: () => testDate,
222
- });
223
-
224
- const result = await model.doGenerate({
225
- prompt,
226
- files: undefined,
227
- mask: undefined,
228
- n: 1,
229
- size: undefined,
230
- seed: undefined,
231
- providerOptions: {},
232
- aspectRatio: undefined,
233
- });
234
-
235
- expect(result.response).toStrictEqual({
236
- timestamp: testDate,
237
- modelId: 'stabilityai/stable-diffusion-xl',
238
- headers: expect.any(Object),
239
- });
240
- });
241
-
242
- it('should include response headers from API call', async () => {
243
- server.urls['https://api.example.com/*'].response = {
244
- type: 'json-value',
245
- body: {
246
- id: 'test-id',
247
- data: [{ index: 0, b64_json: 'test-base64-content' }],
248
- model: 'stabilityai/stable-diffusion-xl',
249
- object: 'list',
250
- },
251
- headers: {
252
- 'x-request-id': 'test-request-id',
253
- 'content-length': '128',
254
- },
255
- };
256
-
257
- const model = createBasicModel();
258
- const result = await model.doGenerate({
259
- prompt,
260
- files: undefined,
261
- mask: undefined,
262
- n: 1,
263
- size: undefined,
264
- seed: undefined,
265
- providerOptions: {},
266
- aspectRatio: undefined,
267
- });
268
-
269
- expect(result.response.headers).toStrictEqual({
270
- 'x-request-id': 'test-request-id',
271
- 'content-type': 'application/json',
272
- 'content-length': '128',
273
- });
274
- });
275
- });
276
- });
277
-
278
- describe('constructor', () => {
279
- it('should expose correct provider and model information', () => {
280
- const model = createBasicModel();
281
-
282
- expect(model.provider).toBe('togetherai');
283
- expect(model.modelId).toBe('stabilityai/stable-diffusion-xl');
284
- expect(model.specificationVersion).toBe('v3');
285
- expect(model.maxImagesPerCall).toBe(1);
286
- });
287
- });
288
-
289
- describe('Image Editing', () => {
290
- const server = createTestServer({
291
- 'https://api.example.com/*': {
292
- response: {
293
- type: 'json-value',
294
- body: {
295
- id: 'test-id',
296
- data: [{ index: 0, b64_json: 'test-base64-content' }],
297
- model: 'black-forest-labs/FLUX.1-kontext-pro',
298
- object: 'list',
299
- },
300
- },
301
- },
302
- });
303
-
304
- it('should send image_url when URL file is provided', async () => {
305
- const model = createBasicModel();
306
-
307
- await model.doGenerate({
308
- prompt: 'Make the shirt yellow',
309
- files: [
310
- {
311
- type: 'url',
312
- url: 'https://example.com/input.jpg',
313
- },
314
- ],
315
- mask: undefined,
316
- n: 1,
317
- size: undefined,
318
- aspectRatio: undefined,
319
- seed: undefined,
320
- providerOptions: {},
321
- });
322
-
323
- const requestBody = await server.calls[0].requestBodyJson;
324
- expect(requestBody).toMatchInlineSnapshot(`
325
- {
326
- "image_url": "https://example.com/input.jpg",
327
- "model": "stabilityai/stable-diffusion-xl",
328
- "prompt": "Make the shirt yellow",
329
- "response_format": "base64",
330
- }
331
- `);
332
- });
333
-
334
- it('should convert Uint8Array file to data URI', async () => {
335
- const model = createBasicModel();
336
- const testImageData = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
337
-
338
- await model.doGenerate({
339
- prompt: 'Transform this image',
340
- files: [
341
- {
342
- type: 'file',
343
- data: testImageData,
344
- mediaType: 'image/png',
345
- },
346
- ],
347
- mask: undefined,
348
- n: 1,
349
- size: undefined,
350
- aspectRatio: undefined,
351
- seed: undefined,
352
- providerOptions: {},
353
- });
354
-
355
- const requestBody = await server.calls[0].requestBodyJson;
356
- expect(requestBody.image_url).toMatch(/^data:image\/png;base64,/);
357
- expect(requestBody.prompt).toBe('Transform this image');
358
- });
359
-
360
- it('should convert file with base64 string data to data URI', async () => {
361
- const model = createBasicModel();
362
-
363
- await model.doGenerate({
364
- prompt: 'Edit this',
365
- files: [
366
- {
367
- type: 'file',
368
- data: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
369
- mediaType: 'image/png',
370
- },
371
- ],
372
- mask: undefined,
373
- n: 1,
374
- size: undefined,
375
- aspectRatio: undefined,
376
- seed: undefined,
377
- providerOptions: {},
378
- });
379
-
380
- const requestBody = await server.calls[0].requestBodyJson;
381
- expect(requestBody.image_url).toBe(
382
- 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
383
- );
384
- });
385
-
386
- it('should throw error when mask is provided', async () => {
387
- const model = createBasicModel();
388
-
389
- await expect(
390
- model.doGenerate({
391
- prompt: 'Inpaint this area',
392
- files: [
393
- {
394
- type: 'url',
395
- url: 'https://example.com/input.jpg',
396
- },
397
- ],
398
- mask: {
399
- type: 'url',
400
- url: 'https://example.com/mask.png',
401
- },
402
- n: 1,
403
- size: undefined,
404
- aspectRatio: undefined,
405
- seed: undefined,
406
- providerOptions: {},
407
- }),
408
- ).rejects.toThrow(
409
- 'Together AI does not support mask-based image editing. ' +
410
- 'Use FLUX Kontext models (e.g., black-forest-labs/FLUX.1-kontext-pro) ' +
411
- 'with a reference image and descriptive prompt instead.',
412
- );
413
- });
414
-
415
- it('should warn when multiple files are provided', async () => {
416
- const model = createBasicModel();
417
-
418
- const result = await model.doGenerate({
419
- prompt: 'Edit multiple images',
420
- files: [
421
- {
422
- type: 'url',
423
- url: 'https://example.com/input1.jpg',
424
- },
425
- {
426
- type: 'url',
427
- url: 'https://example.com/input2.jpg',
428
- },
429
- ],
430
- mask: undefined,
431
- n: 1,
432
- size: undefined,
433
- aspectRatio: undefined,
434
- seed: undefined,
435
- providerOptions: {},
436
- });
437
-
438
- expect(result.warnings).toMatchInlineSnapshot(`
439
- [
440
- {
441
- "message": "Together AI only supports a single input image. Additional images are ignored.",
442
- "type": "other",
443
- },
444
- ]
445
- `);
446
-
447
- // Should only use the first image
448
- const requestBody = await server.calls[0].requestBodyJson;
449
- expect(requestBody.image_url).toBe('https://example.com/input1.jpg');
450
- });
451
-
452
- it('should pass provider options with image editing', async () => {
453
- const model = createBasicModel();
454
-
455
- await model.doGenerate({
456
- prompt: 'Transform the style',
457
- files: [
458
- {
459
- type: 'url',
460
- url: 'https://example.com/input.jpg',
461
- },
462
- ],
463
- mask: undefined,
464
- n: 1,
465
- size: undefined,
466
- aspectRatio: undefined,
467
- seed: undefined,
468
- providerOptions: {
469
- togetherai: {
470
- steps: 28,
471
- guidance: 3.5,
472
- },
473
- },
474
- });
475
-
476
- const requestBody = await server.calls[0].requestBodyJson;
477
- expect(requestBody).toMatchInlineSnapshot(`
478
- {
479
- "guidance": 3.5,
480
- "image_url": "https://example.com/input.jpg",
481
- "model": "stabilityai/stable-diffusion-xl",
482
- "prompt": "Transform the style",
483
- "response_format": "base64",
484
- "steps": 28,
485
- }
486
- `);
487
- });
488
- });
@@ -1,196 +0,0 @@
1
- import {
2
- OpenAICompatibleChatLanguageModel,
3
- OpenAICompatibleCompletionLanguageModel,
4
- OpenAICompatibleEmbeddingModel,
5
- } from '@ai-sdk/openai-compatible';
6
- import {
7
- EmbeddingModelV3,
8
- LanguageModelV3,
9
- RerankingModelV3,
10
- } from '@ai-sdk/provider';
11
- import { loadApiKey } from '@ai-sdk/provider-utils';
12
- import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
13
- import { TogetherAIRerankingModel } from './reranking/togetherai-reranking-model';
14
- import { TogetherAIImageModel } from './togetherai-image-model';
15
- import { createTogetherAI } from './togetherai-provider';
16
-
17
- // Add type assertion for the mocked class
18
- const OpenAICompatibleChatLanguageModelMock =
19
- OpenAICompatibleChatLanguageModel as unknown as Mock;
20
-
21
- vi.mock('@ai-sdk/openai-compatible', () => ({
22
- OpenAICompatibleChatLanguageModel: vi.fn(),
23
- OpenAICompatibleCompletionLanguageModel: vi.fn(),
24
- OpenAICompatibleEmbeddingModel: vi.fn(),
25
- }));
26
-
27
- vi.mock('@ai-sdk/provider-utils', async () => {
28
- const actual = await vi.importActual('@ai-sdk/provider-utils');
29
- return {
30
- ...actual,
31
- loadApiKey: vi.fn().mockReturnValue('mock-api-key'),
32
- withoutTrailingSlash: vi.fn(url => url),
33
- };
34
- });
35
-
36
- vi.mock('./togetherai-image-model', () => ({
37
- TogetherAIImageModel: vi.fn(),
38
- }));
39
-
40
- vi.mock('./reranking/togetherai-reranking-model', () => ({
41
- TogetherAIRerankingModel: vi.fn(),
42
- }));
43
-
44
- describe('TogetherAIProvider', () => {
45
- let mockLanguageModel: LanguageModelV3;
46
- let mockEmbeddingModel: EmbeddingModelV3;
47
- let mockRerankingModel: RerankingModelV3;
48
-
49
- beforeEach(() => {
50
- // Mock implementations of models
51
- mockLanguageModel = {
52
- // Add any required methods for LanguageModelV3
53
- } as LanguageModelV3;
54
- mockEmbeddingModel = {
55
- // Add any required methods for EmbeddingModelV3
56
- } as EmbeddingModelV3;
57
- mockRerankingModel = {
58
- // Add any required methods for RerankingModelV3
59
- } as RerankingModelV3;
60
-
61
- // Reset mocks
62
- vi.clearAllMocks();
63
- });
64
-
65
- describe('createTogetherAI', () => {
66
- it('should create a TogetherAIProvider instance with default options', () => {
67
- const provider = createTogetherAI();
68
- const model = provider('model-id');
69
-
70
- // Use the mocked version
71
- const constructorCall =
72
- OpenAICompatibleChatLanguageModelMock.mock.calls[0];
73
- const config = constructorCall[1];
74
- config.headers();
75
-
76
- expect(loadApiKey).toHaveBeenCalledWith({
77
- apiKey: undefined,
78
- environmentVariableName: 'TOGETHER_AI_API_KEY',
79
- description: 'TogetherAI',
80
- });
81
- });
82
-
83
- it('should create a TogetherAIProvider instance with custom options', () => {
84
- const options = {
85
- apiKey: 'custom-key',
86
- baseURL: 'https://custom.url',
87
- headers: { 'Custom-Header': 'value' },
88
- };
89
- const provider = createTogetherAI(options);
90
- const model = provider('model-id');
91
-
92
- const constructorCall =
93
- OpenAICompatibleChatLanguageModelMock.mock.calls[0];
94
- const config = constructorCall[1];
95
- config.headers();
96
-
97
- expect(loadApiKey).toHaveBeenCalledWith({
98
- apiKey: 'custom-key',
99
- environmentVariableName: 'TOGETHER_AI_API_KEY',
100
- description: 'TogetherAI',
101
- });
102
- });
103
-
104
- it('should return a chat model when called as a function', () => {
105
- const provider = createTogetherAI();
106
- const modelId = 'foo-model-id';
107
-
108
- const model = provider(modelId);
109
- expect(model).toBeInstanceOf(OpenAICompatibleChatLanguageModel);
110
- });
111
- });
112
-
113
- describe('chatModel', () => {
114
- it('should construct a chat model with correct configuration', () => {
115
- const provider = createTogetherAI();
116
- const modelId = 'together-chat-model';
117
-
118
- const model = provider.chatModel(modelId);
119
-
120
- expect(model).toBeInstanceOf(OpenAICompatibleChatLanguageModel);
121
- });
122
- });
123
-
124
- describe('completionModel', () => {
125
- it('should construct a completion model with correct configuration', () => {
126
- const provider = createTogetherAI();
127
- const modelId = 'together-completion-model';
128
-
129
- const model = provider.completionModel(modelId);
130
-
131
- expect(model).toBeInstanceOf(OpenAICompatibleCompletionLanguageModel);
132
- });
133
- });
134
-
135
- describe('embeddingModel', () => {
136
- it('should construct a text embedding model with correct configuration', () => {
137
- const provider = createTogetherAI();
138
- const modelId = 'together-embedding-model';
139
-
140
- const model = provider.embeddingModel(modelId);
141
-
142
- expect(model).toBeInstanceOf(OpenAICompatibleEmbeddingModel);
143
- });
144
- });
145
-
146
- describe('image', () => {
147
- it('should construct an image model with correct configuration', () => {
148
- const provider = createTogetherAI();
149
- const modelId = 'stabilityai/stable-diffusion-xl';
150
-
151
- const model = provider.image(modelId);
152
-
153
- expect(TogetherAIImageModel).toHaveBeenCalledWith(
154
- modelId,
155
- expect.objectContaining({
156
- provider: 'togetherai.image',
157
- baseURL: 'https://api.together.xyz/v1/',
158
- }),
159
- );
160
- expect(model).toBeInstanceOf(TogetherAIImageModel);
161
- });
162
-
163
- it('should pass custom baseURL to image model', () => {
164
- const provider = createTogetherAI({
165
- baseURL: 'https://custom.url/',
166
- });
167
- const modelId = 'stabilityai/stable-diffusion-xl';
168
-
169
- provider.image(modelId);
170
-
171
- expect(TogetherAIImageModel).toHaveBeenCalledWith(
172
- modelId,
173
- expect.objectContaining({
174
- baseURL: 'https://custom.url/',
175
- }),
176
- );
177
- });
178
- });
179
-
180
- describe('rerankingModel', () => {
181
- it('should construct a reranking model with correct configuration', () => {
182
- const provider = createTogetherAI();
183
- const modelId = 'Salesforce/Llama-Rank-v1';
184
- 0;
185
- const model = provider.rerankingModel(modelId);
186
-
187
- expect(TogetherAIRerankingModel).toHaveBeenCalledWith(
188
- modelId,
189
- expect.objectContaining({
190
- baseURL: 'https://api.together.xyz/v1/',
191
- }),
192
- );
193
- expect(model).toBeInstanceOf(TogetherAIRerankingModel);
194
- });
195
- });
196
- });