@ai-sdk/luma 2.0.10 → 2.0.12

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/luma
2
2
 
3
+ ## 2.0.12
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [462ad00]
8
+ - @ai-sdk/provider-utils@4.0.10
9
+
10
+ ## 2.0.11
11
+
12
+ ### Patch Changes
13
+
14
+ - 4de5a1d: chore: excluded tests from src folder in npm package
15
+ - Updated dependencies [4de5a1d]
16
+ - @ai-sdk/provider@3.0.5
17
+ - @ai-sdk/provider-utils@4.0.9
18
+
3
19
  ## 2.0.10
4
20
 
5
21
  ### Patch Changes
package/dist/index.js CHANGED
@@ -356,7 +356,7 @@ var lumaImageProviderOptionsSchema = (0, import_provider_utils.lazySchema)(
356
356
  );
357
357
 
358
358
  // src/version.ts
359
- var VERSION = true ? "2.0.10" : "0.0.0-test";
359
+ var VERSION = true ? "2.0.12" : "0.0.0-test";
360
360
 
361
361
  // src/luma-provider.ts
362
362
  var defaultBaseURL = "https://api.lumalabs.ai";
package/dist/index.mjs CHANGED
@@ -346,7 +346,7 @@ var lumaImageProviderOptionsSchema = lazySchema(
346
346
  );
347
347
 
348
348
  // src/version.ts
349
- var VERSION = true ? "2.0.10" : "0.0.0-test";
349
+ var VERSION = true ? "2.0.12" : "0.0.0-test";
350
350
 
351
351
  // src/luma-provider.ts
352
352
  var defaultBaseURL = "https://api.lumalabs.ai";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/luma",
3
- "version": "2.0.10",
3
+ "version": "2.0.12",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -10,6 +10,10 @@
10
10
  "dist/**/*",
11
11
  "docs/**/*",
12
12
  "src",
13
+ "!src/**/*.test.ts",
14
+ "!src/**/*.test-d.ts",
15
+ "!src/**/__snapshots__",
16
+ "!src/**/__fixtures__",
13
17
  "CHANGELOG.md",
14
18
  "README.md"
15
19
  ],
@@ -25,15 +29,15 @@
25
29
  }
26
30
  },
27
31
  "dependencies": {
28
- "@ai-sdk/provider": "3.0.4",
29
- "@ai-sdk/provider-utils": "4.0.8"
32
+ "@ai-sdk/provider": "3.0.5",
33
+ "@ai-sdk/provider-utils": "4.0.10"
30
34
  },
31
35
  "devDependencies": {
32
36
  "@types/node": "20.17.24",
33
37
  "tsup": "^8",
34
38
  "typescript": "5.8.3",
35
39
  "zod": "3.25.76",
36
- "@ai-sdk/test-server": "1.0.2",
40
+ "@ai-sdk/test-server": "1.0.3",
37
41
  "@vercel/ai-tsconfig": "0.0.0"
38
42
  },
39
43
  "peerDependencies": {
@@ -1,1001 +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 { LumaImageModel } from './luma-image-model';
5
- import { InvalidResponseDataError } from '@ai-sdk/provider';
6
-
7
- const prompt = 'A cute baby sea otter';
8
-
9
- function createBasicModel({
10
- headers,
11
- fetch,
12
- currentDate,
13
- }: {
14
- headers?: () => Record<string, string>;
15
- fetch?: FetchFunction;
16
- currentDate?: () => Date;
17
- } = {}) {
18
- return new LumaImageModel('test-model', {
19
- provider: 'luma',
20
- baseURL: 'https://api.example.com',
21
- headers: headers ?? (() => ({ 'api-key': 'test-key' })),
22
- fetch,
23
- _internal: {
24
- currentDate,
25
- },
26
- });
27
- }
28
-
29
- describe('LumaImageModel', () => {
30
- const server = createTestServer({
31
- 'https://api.example.com/dream-machine/v1/generations/image': {
32
- response: {
33
- type: 'json-value',
34
- body: {
35
- id: 'test-generation-id',
36
- generation_type: 'image',
37
- state: 'queued',
38
- created_at: '2024-01-01T00:00:00Z',
39
- model: 'test-model',
40
- request: {
41
- generation_type: 'image',
42
- model: 'test-model',
43
- prompt: 'A cute baby sea otter',
44
- },
45
- },
46
- },
47
- },
48
- 'https://api.example.com/dream-machine/v1/generations/test-generation-id': {
49
- response: {
50
- type: 'json-value',
51
- body: {
52
- id: 'test-generation-id',
53
- generation_type: 'image',
54
- state: 'completed',
55
- created_at: '2024-01-01T00:00:00Z',
56
- assets: {
57
- image: 'https://api.example.com/image.png',
58
- },
59
- model: 'test-model',
60
- request: {
61
- generation_type: 'image',
62
- model: 'test-model',
63
- prompt: 'A cute baby sea otter',
64
- },
65
- },
66
- },
67
- },
68
- 'https://api.example.com/image.png': {
69
- response: {
70
- type: 'binary',
71
- body: Buffer.from('test-binary-content'),
72
- },
73
- },
74
- });
75
-
76
- describe('doGenerate', () => {
77
- it('should pass the correct parameters including aspect ratio', async () => {
78
- const model = createBasicModel();
79
-
80
- await model.doGenerate({
81
- prompt,
82
- files: undefined,
83
- mask: undefined,
84
- n: 1,
85
- size: undefined,
86
- aspectRatio: '16:9',
87
- seed: undefined,
88
- providerOptions: { luma: { additional_param: 'value' } },
89
- });
90
-
91
- expect(await server.calls[0].requestBodyJson).toStrictEqual({
92
- prompt,
93
- aspect_ratio: '16:9',
94
- model: 'test-model',
95
- additional_param: 'value',
96
- });
97
- });
98
-
99
- it('should call the correct urls in sequence', async () => {
100
- const model = createBasicModel();
101
-
102
- await model.doGenerate({
103
- prompt,
104
- files: undefined,
105
- mask: undefined,
106
- n: 1,
107
- aspectRatio: '16:9',
108
- providerOptions: {},
109
- size: undefined,
110
- seed: undefined,
111
- });
112
-
113
- expect(server.calls[0].requestMethod).toBe('POST');
114
- expect(server.calls[0].requestUrl).toBe(
115
- 'https://api.example.com/dream-machine/v1/generations/image',
116
- );
117
- expect(server.calls[1].requestMethod).toBe('GET');
118
- expect(server.calls[1].requestUrl).toBe(
119
- 'https://api.example.com/dream-machine/v1/generations/test-generation-id',
120
- );
121
- expect(server.calls[2].requestMethod).toBe('GET');
122
- expect(server.calls[2].requestUrl).toBe(
123
- 'https://api.example.com/image.png',
124
- );
125
- });
126
-
127
- it('should pass headers', async () => {
128
- const modelWithHeaders = createBasicModel({
129
- headers: () => ({
130
- 'Custom-Provider-Header': 'provider-header-value',
131
- }),
132
- });
133
-
134
- await modelWithHeaders.doGenerate({
135
- prompt,
136
- files: undefined,
137
- mask: undefined,
138
- n: 1,
139
- providerOptions: {},
140
- headers: {
141
- 'Custom-Request-Header': 'request-header-value',
142
- },
143
- size: undefined,
144
- seed: undefined,
145
- aspectRatio: undefined,
146
- });
147
-
148
- expect(server.calls[0].requestHeaders).toStrictEqual({
149
- 'content-type': 'application/json',
150
- 'custom-provider-header': 'provider-header-value',
151
- 'custom-request-header': 'request-header-value',
152
- });
153
- });
154
-
155
- it('should not pass providerOptions.{pollIntervalMillis,maxPollAttempts}', async () => {
156
- const model = createBasicModel();
157
-
158
- await model.doGenerate({
159
- prompt,
160
- files: undefined,
161
- mask: undefined,
162
- n: 1,
163
- size: undefined,
164
- aspectRatio: '16:9',
165
- seed: undefined,
166
- providerOptions: {
167
- luma: {
168
- pollIntervalMillis: 1000,
169
- maxPollAttempts: 5,
170
- additional_param: 'value',
171
- },
172
- },
173
- });
174
-
175
- expect(await server.calls[0].requestBodyJson).toStrictEqual({
176
- prompt,
177
- aspect_ratio: '16:9',
178
- model: 'test-model',
179
- additional_param: 'value',
180
- });
181
- });
182
-
183
- it('should handle API errors', async () => {
184
- server.urls[
185
- 'https://api.example.com/dream-machine/v1/generations/image'
186
- ].response = {
187
- type: 'error',
188
- status: 400,
189
- body: 'Bad Request',
190
- };
191
-
192
- const model = createBasicModel();
193
- await expect(
194
- model.doGenerate({
195
- prompt,
196
- files: undefined,
197
- mask: undefined,
198
- n: 1,
199
- providerOptions: {},
200
- size: undefined,
201
- seed: undefined,
202
- aspectRatio: undefined,
203
- }),
204
- ).rejects.toMatchObject({
205
- message: 'Bad Request',
206
- statusCode: 400,
207
- url: 'https://api.example.com/dream-machine/v1/generations/image',
208
- requestBodyValues: {
209
- prompt: 'A cute baby sea otter',
210
- },
211
- responseBody: 'Bad Request',
212
- });
213
- });
214
-
215
- it('should handle failed generation state', async () => {
216
- server.urls[
217
- 'https://api.example.com/dream-machine/v1/generations/test-generation-id'
218
- ].response = {
219
- type: 'json-value',
220
- body: {
221
- id: 'test-generation-id',
222
- generation_type: 'image',
223
- state: 'failed',
224
- failure_reason: 'Generation failed',
225
- created_at: '2024-01-01T00:00:00Z',
226
- model: 'test-model',
227
- request: {
228
- generation_type: 'image',
229
- model: 'test-model',
230
- prompt: 'A cute baby sea otter',
231
- },
232
- },
233
- };
234
-
235
- const model = createBasicModel();
236
- await expect(
237
- model.doGenerate({
238
- prompt,
239
- files: undefined,
240
- mask: undefined,
241
- n: 1,
242
- providerOptions: {},
243
- size: undefined,
244
- seed: undefined,
245
- aspectRatio: undefined,
246
- }),
247
- ).rejects.toThrow(InvalidResponseDataError);
248
- });
249
-
250
- describe('warnings', () => {
251
- it('should return warnings for unsupported parameters', async () => {
252
- const model = createBasicModel();
253
-
254
- const result = await model.doGenerate({
255
- prompt,
256
- files: undefined,
257
- mask: undefined,
258
- n: 1,
259
- size: '1024x1024',
260
- seed: 123,
261
- providerOptions: {},
262
- aspectRatio: undefined,
263
- });
264
-
265
- expect(result.warnings).toMatchInlineSnapshot(`
266
- [
267
- {
268
- "details": "This model does not support the \`seed\` option.",
269
- "feature": "seed",
270
- "type": "unsupported",
271
- },
272
- {
273
- "details": "This model does not support the \`size\` option. Use \`aspectRatio\` instead.",
274
- "feature": "size",
275
- "type": "unsupported",
276
- },
277
- ]
278
- `);
279
- });
280
- });
281
-
282
- describe('response metadata', () => {
283
- it('should include timestamp, headers and modelId in response', async () => {
284
- const testDate = new Date('2024-01-01T00:00:00Z');
285
- const model = createBasicModel({
286
- currentDate: () => testDate,
287
- });
288
-
289
- const result = await model.doGenerate({
290
- prompt,
291
- files: undefined,
292
- mask: undefined,
293
- n: 1,
294
- providerOptions: {},
295
- size: undefined,
296
- seed: undefined,
297
- aspectRatio: undefined,
298
- });
299
-
300
- expect(result.response).toStrictEqual({
301
- timestamp: testDate,
302
- modelId: 'test-model',
303
- headers: expect.any(Object),
304
- });
305
- });
306
- });
307
- });
308
-
309
- describe('constructor', () => {
310
- it('should expose correct provider and model information', () => {
311
- const model = createBasicModel();
312
-
313
- expect(model.provider).toBe('luma');
314
- expect(model.modelId).toBe('test-model');
315
- expect(model.specificationVersion).toBe('v3');
316
- expect(model.maxImagesPerCall).toBe(1);
317
- });
318
- });
319
-
320
- describe('Image Editing', () => {
321
- it('should send image by default when URL file is provided', async () => {
322
- const model = createBasicModel();
323
-
324
- await model.doGenerate({
325
- prompt: 'A warrior with sunglasses',
326
- files: [
327
- {
328
- type: 'url',
329
- url: 'https://example.com/input.jpg',
330
- },
331
- ],
332
- mask: undefined,
333
- n: 1,
334
- size: undefined,
335
- aspectRatio: undefined,
336
- seed: undefined,
337
- providerOptions: {},
338
- });
339
-
340
- const requestBody = await server.calls[0].requestBodyJson;
341
- expect(requestBody).toMatchInlineSnapshot(`
342
- {
343
- "image": [
344
- {
345
- "url": "https://example.com/input.jpg",
346
- "weight": 0.85,
347
- },
348
- ],
349
- "model": "test-model",
350
- "prompt": "A warrior with sunglasses",
351
- }
352
- `);
353
- });
354
-
355
- it('should send modify_image when referenceType is set', async () => {
356
- const model = createBasicModel();
357
-
358
- await model.doGenerate({
359
- prompt: 'Transform flowers to sunflowers',
360
- files: [
361
- {
362
- type: 'url',
363
- url: 'https://example.com/input.jpg',
364
- },
365
- ],
366
- mask: undefined,
367
- n: 1,
368
- size: undefined,
369
- aspectRatio: undefined,
370
- seed: undefined,
371
- providerOptions: {
372
- luma: {
373
- referenceType: 'modify_image',
374
- },
375
- },
376
- });
377
-
378
- const requestBody = await server.calls[0].requestBodyJson;
379
- expect(requestBody).toMatchInlineSnapshot(`
380
- {
381
- "model": "test-model",
382
- "modify_image": {
383
- "url": "https://example.com/input.jpg",
384
- "weight": 1,
385
- },
386
- "prompt": "Transform flowers to sunflowers",
387
- }
388
- `);
389
- });
390
-
391
- it('should send style when referenceType is style', async () => {
392
- const model = createBasicModel();
393
-
394
- await model.doGenerate({
395
- prompt: 'A dog in this style',
396
- files: [
397
- {
398
- type: 'url',
399
- url: 'https://example.com/style.jpg',
400
- },
401
- ],
402
- mask: undefined,
403
- n: 1,
404
- size: undefined,
405
- aspectRatio: undefined,
406
- seed: undefined,
407
- providerOptions: {
408
- luma: {
409
- referenceType: 'style',
410
- },
411
- },
412
- });
413
-
414
- const requestBody = await server.calls[0].requestBodyJson;
415
- expect(requestBody).toMatchInlineSnapshot(`
416
- {
417
- "model": "test-model",
418
- "prompt": "A dog in this style",
419
- "style": [
420
- {
421
- "url": "https://example.com/style.jpg",
422
- "weight": 0.8,
423
- },
424
- ],
425
- }
426
- `);
427
- });
428
-
429
- it('should send character when referenceType is character', async () => {
430
- const model = createBasicModel();
431
-
432
- await model.doGenerate({
433
- prompt: 'A warrior',
434
- files: [
435
- {
436
- type: 'url',
437
- url: 'https://example.com/person1.jpg',
438
- },
439
- {
440
- type: 'url',
441
- url: 'https://example.com/person2.jpg',
442
- },
443
- ],
444
- mask: undefined,
445
- n: 1,
446
- size: undefined,
447
- aspectRatio: undefined,
448
- seed: undefined,
449
- providerOptions: {
450
- luma: {
451
- referenceType: 'character',
452
- },
453
- },
454
- });
455
-
456
- const requestBody = await server.calls[0].requestBodyJson;
457
- expect(requestBody).toMatchInlineSnapshot(`
458
- {
459
- "character": {
460
- "identity0": {
461
- "images": [
462
- "https://example.com/person1.jpg",
463
- "https://example.com/person2.jpg",
464
- ],
465
- },
466
- },
467
- "model": "test-model",
468
- "prompt": "A warrior",
469
- }
470
- `);
471
- });
472
-
473
- it('should send character with custom identity id from images config', async () => {
474
- const model = createBasicModel();
475
-
476
- await model.doGenerate({
477
- prompt: 'A woman with a cat',
478
- files: [
479
- {
480
- type: 'url',
481
- url: 'https://example.com/person.jpg',
482
- },
483
- ],
484
- mask: undefined,
485
- n: 1,
486
- size: undefined,
487
- aspectRatio: undefined,
488
- seed: undefined,
489
- providerOptions: {
490
- luma: {
491
- referenceType: 'character',
492
- images: [{ id: 'identity0' }],
493
- },
494
- },
495
- });
496
-
497
- const requestBody = await server.calls[0].requestBodyJson;
498
- expect(requestBody).toMatchInlineSnapshot(`
499
- {
500
- "character": {
501
- "identity0": {
502
- "images": [
503
- "https://example.com/person.jpg",
504
- ],
505
- },
506
- },
507
- "model": "test-model",
508
- "prompt": "A woman with a cat",
509
- }
510
- `);
511
- });
512
-
513
- it('should send character with multiple identities from images config', async () => {
514
- const model = createBasicModel();
515
-
516
- await model.doGenerate({
517
- prompt: 'Two people talking',
518
- files: [
519
- {
520
- type: 'url',
521
- url: 'https://example.com/person1.jpg',
522
- },
523
- {
524
- type: 'url',
525
- url: 'https://example.com/person2.jpg',
526
- },
527
- ],
528
- mask: undefined,
529
- n: 1,
530
- size: undefined,
531
- aspectRatio: undefined,
532
- seed: undefined,
533
- providerOptions: {
534
- luma: {
535
- referenceType: 'character',
536
- images: [{ id: 'identity0' }, { id: 'identity1' }],
537
- },
538
- },
539
- });
540
-
541
- const requestBody = await server.calls[0].requestBodyJson;
542
- expect(requestBody).toMatchInlineSnapshot(`
543
- {
544
- "character": {
545
- "identity0": {
546
- "images": [
547
- "https://example.com/person1.jpg",
548
- ],
549
- },
550
- "identity1": {
551
- "images": [
552
- "https://example.com/person2.jpg",
553
- ],
554
- },
555
- },
556
- "model": "test-model",
557
- "prompt": "Two people talking",
558
- }
559
- `);
560
- });
561
-
562
- it('should support multiple images for image', async () => {
563
- const model = createBasicModel();
564
-
565
- await model.doGenerate({
566
- prompt: 'Combine these concepts',
567
- files: [
568
- {
569
- type: 'url',
570
- url: 'https://example.com/input1.jpg',
571
- },
572
- {
573
- type: 'url',
574
- url: 'https://example.com/input2.jpg',
575
- },
576
- ],
577
- mask: undefined,
578
- n: 1,
579
- size: undefined,
580
- aspectRatio: undefined,
581
- seed: undefined,
582
- providerOptions: {},
583
- });
584
-
585
- const requestBody = await server.calls[0].requestBodyJson;
586
- expect(requestBody).toMatchInlineSnapshot(`
587
- {
588
- "image": [
589
- {
590
- "url": "https://example.com/input1.jpg",
591
- "weight": 0.85,
592
- },
593
- {
594
- "url": "https://example.com/input2.jpg",
595
- "weight": 0.85,
596
- },
597
- ],
598
- "model": "test-model",
599
- "prompt": "Combine these concepts",
600
- }
601
- `);
602
- });
603
-
604
- it('should use custom weights from images config', async () => {
605
- const model = createBasicModel();
606
-
607
- await model.doGenerate({
608
- prompt: 'Styled image',
609
- files: [
610
- {
611
- type: 'url',
612
- url: 'https://example.com/input.jpg',
613
- },
614
- ],
615
- mask: undefined,
616
- n: 1,
617
- size: undefined,
618
- aspectRatio: undefined,
619
- seed: undefined,
620
- providerOptions: {
621
- luma: {
622
- images: [{ weight: 0.5 }],
623
- },
624
- },
625
- });
626
-
627
- const requestBody = await server.calls[0].requestBodyJson;
628
- expect(requestBody).toMatchInlineSnapshot(`
629
- {
630
- "image": [
631
- {
632
- "url": "https://example.com/input.jpg",
633
- "weight": 0.5,
634
- },
635
- ],
636
- "model": "test-model",
637
- "prompt": "Styled image",
638
- }
639
- `);
640
- });
641
-
642
- it('should throw error when mask is provided', async () => {
643
- const model = createBasicModel();
644
-
645
- await expect(
646
- model.doGenerate({
647
- prompt: 'Replace sky with sunset',
648
- files: [
649
- {
650
- type: 'url',
651
- url: 'https://example.com/input.jpg',
652
- },
653
- ],
654
- mask: {
655
- type: 'url',
656
- url: 'https://example.com/mask.png',
657
- },
658
- n: 1,
659
- size: undefined,
660
- aspectRatio: undefined,
661
- seed: undefined,
662
- providerOptions: {},
663
- }),
664
- ).rejects.toThrow('Luma AI does not support mask-based image editing');
665
- });
666
-
667
- it('should throw error when base64 file data is provided', async () => {
668
- const model = createBasicModel();
669
-
670
- await expect(
671
- model.doGenerate({
672
- prompt: 'Edit this image',
673
- files: [
674
- {
675
- type: 'file',
676
- data: new Uint8Array([137, 80, 78, 71]),
677
- mediaType: 'image/png',
678
- },
679
- ],
680
- mask: undefined,
681
- n: 1,
682
- size: undefined,
683
- aspectRatio: undefined,
684
- seed: undefined,
685
- providerOptions: {},
686
- }),
687
- ).rejects.toThrow('Luma AI only supports URL-based images');
688
- });
689
-
690
- it('should throw error when base64 mask data is provided', async () => {
691
- const model = createBasicModel();
692
-
693
- await expect(
694
- model.doGenerate({
695
- prompt: 'Edit with mask',
696
- files: [
697
- {
698
- type: 'url',
699
- url: 'https://example.com/input.jpg',
700
- },
701
- ],
702
- mask: {
703
- type: 'file',
704
- data: new Uint8Array([255, 255, 255, 0]),
705
- mediaType: 'image/png',
706
- },
707
- n: 1,
708
- size: undefined,
709
- aspectRatio: undefined,
710
- seed: undefined,
711
- providerOptions: {},
712
- }),
713
- ).rejects.toThrow('Luma AI does not support mask-based image editing');
714
- });
715
-
716
- it('should throw error when more than 4 images for image', async () => {
717
- const model = createBasicModel();
718
-
719
- await expect(
720
- model.doGenerate({
721
- prompt: 'Too many images',
722
- files: [
723
- { type: 'url', url: 'https://example.com/1.jpg' },
724
- { type: 'url', url: 'https://example.com/2.jpg' },
725
- { type: 'url', url: 'https://example.com/3.jpg' },
726
- { type: 'url', url: 'https://example.com/4.jpg' },
727
- { type: 'url', url: 'https://example.com/5.jpg' },
728
- ],
729
- mask: undefined,
730
- n: 1,
731
- size: undefined,
732
- aspectRatio: undefined,
733
- seed: undefined,
734
- providerOptions: {},
735
- }),
736
- ).rejects.toThrow('Luma AI image supports up to 4 reference images');
737
- });
738
-
739
- it('should throw error when multiple files for modify_image', async () => {
740
- const model = createBasicModel();
741
-
742
- await expect(
743
- model.doGenerate({
744
- prompt: 'Edit multiple images',
745
- files: [
746
- {
747
- type: 'url',
748
- url: 'https://example.com/input1.jpg',
749
- },
750
- {
751
- type: 'url',
752
- url: 'https://example.com/input2.jpg',
753
- },
754
- ],
755
- mask: undefined,
756
- n: 1,
757
- size: undefined,
758
- aspectRatio: undefined,
759
- seed: undefined,
760
- providerOptions: {
761
- luma: {
762
- referenceType: 'modify_image',
763
- },
764
- },
765
- }),
766
- ).rejects.toThrow(
767
- 'Luma AI modify_image only supports a single input image',
768
- );
769
- });
770
- });
771
-
772
- describe('response schema validation', () => {
773
- it('should parse response with image references', async () => {
774
- server.urls[
775
- 'https://api.example.com/dream-machine/v1/generations/test-generation-id'
776
- ].response = {
777
- type: 'json-value',
778
- body: {
779
- id: 'test-generation-id',
780
- generation_type: 'image',
781
- state: 'completed',
782
- created_at: '2024-01-01T00:00:00Z',
783
- assets: {
784
- image: 'https://api.example.com/image.png',
785
- },
786
- model: 'test-model',
787
- request: {
788
- generation_type: 'image',
789
- model: 'test-model',
790
- prompt: 'A cute baby sea otter',
791
- image_ref: [
792
- {
793
- url: 'https://example.com/ref1.jpg',
794
- weight: 0.85,
795
- },
796
- ],
797
- },
798
- },
799
- };
800
-
801
- const model = createBasicModel();
802
- const result = await model.doGenerate({
803
- prompt,
804
- files: undefined,
805
- mask: undefined,
806
- n: 1,
807
- providerOptions: {},
808
- size: undefined,
809
- seed: undefined,
810
- aspectRatio: undefined,
811
- });
812
-
813
- // If schema validation fails, this won't get reached
814
- expect(result.images).toBeDefined();
815
- });
816
-
817
- it('should parse response with style references', async () => {
818
- server.urls[
819
- 'https://api.example.com/dream-machine/v1/generations/test-generation-id'
820
- ].response = {
821
- type: 'json-value',
822
- body: {
823
- id: 'test-generation-id',
824
- generation_type: 'image',
825
- state: 'completed',
826
- created_at: '2024-01-01T00:00:00Z',
827
- assets: {
828
- image: 'https://api.example.com/image.png',
829
- },
830
- model: 'test-model',
831
- request: {
832
- generation_type: 'image',
833
- model: 'test-model',
834
- prompt: 'A cute baby sea otter',
835
- style_ref: [
836
- {
837
- url: 'https://example.com/style1.jpg',
838
- weight: 0.8,
839
- },
840
- ],
841
- },
842
- },
843
- };
844
-
845
- const model = createBasicModel();
846
- const result = await model.doGenerate({
847
- prompt,
848
- files: undefined,
849
- mask: undefined,
850
- n: 1,
851
- providerOptions: {},
852
- size: undefined,
853
- seed: undefined,
854
- aspectRatio: undefined,
855
- });
856
-
857
- expect(result.images).toBeDefined();
858
- });
859
-
860
- it('should parse response with character references', async () => {
861
- server.urls[
862
- 'https://api.example.com/dream-machine/v1/generations/test-generation-id'
863
- ].response = {
864
- type: 'json-value',
865
- body: {
866
- id: 'test-generation-id',
867
- generation_type: 'image',
868
- state: 'completed',
869
- created_at: '2024-01-01T00:00:00Z',
870
- assets: {
871
- image: 'https://api.example.com/image.png',
872
- },
873
- model: 'test-model',
874
- request: {
875
- generation_type: 'image',
876
- model: 'test-model',
877
- prompt: 'A cute baby sea otter',
878
- character_ref: {
879
- identity0: {
880
- images: ['https://example.com/character1.jpg'],
881
- },
882
- },
883
- },
884
- },
885
- };
886
-
887
- const model = createBasicModel();
888
- const result = await model.doGenerate({
889
- prompt,
890
- files: undefined,
891
- mask: undefined,
892
- n: 1,
893
- providerOptions: {},
894
- size: undefined,
895
- seed: undefined,
896
- aspectRatio: undefined,
897
- });
898
-
899
- expect(result.images).toBeDefined();
900
- });
901
-
902
- it('should parse response with modify image reference', async () => {
903
- server.urls[
904
- 'https://api.example.com/dream-machine/v1/generations/test-generation-id'
905
- ].response = {
906
- type: 'json-value',
907
- body: {
908
- id: 'test-generation-id',
909
- generation_type: 'image',
910
- state: 'completed',
911
- created_at: '2024-01-01T00:00:00Z',
912
- assets: {
913
- image: 'https://api.example.com/image.png',
914
- },
915
- model: 'test-model',
916
- request: {
917
- generation_type: 'image',
918
- model: 'test-model',
919
- prompt: 'A cute baby sea otter',
920
- modify_image_ref: {
921
- url: 'https://example.com/modify.jpg',
922
- weight: 1.0,
923
- },
924
- },
925
- },
926
- };
927
-
928
- const model = createBasicModel();
929
- const result = await model.doGenerate({
930
- prompt,
931
- files: undefined,
932
- mask: undefined,
933
- n: 1,
934
- providerOptions: {},
935
- size: undefined,
936
- seed: undefined,
937
- aspectRatio: undefined,
938
- });
939
-
940
- expect(result.images).toBeDefined();
941
- });
942
-
943
- it('should parse response with multiple reference types', async () => {
944
- server.urls[
945
- 'https://api.example.com/dream-machine/v1/generations/test-generation-id'
946
- ].response = {
947
- type: 'json-value',
948
- body: {
949
- id: 'test-generation-id',
950
- generation_type: 'image',
951
- state: 'completed',
952
- created_at: '2024-01-01T00:00:00Z',
953
- assets: {
954
- image: 'https://api.example.com/image.png',
955
- },
956
- model: 'test-model',
957
- request: {
958
- generation_type: 'image',
959
- model: 'test-model',
960
- prompt: 'A cute baby sea otter',
961
- image_ref: [
962
- {
963
- url: 'https://example.com/ref1.jpg',
964
- weight: 0.85,
965
- },
966
- ],
967
- style_ref: [
968
- {
969
- url: 'https://example.com/style1.jpg',
970
- weight: 0.8,
971
- },
972
- ],
973
- character_ref: {
974
- identity0: {
975
- images: ['https://example.com/character1.jpg'],
976
- },
977
- },
978
- modify_image_ref: {
979
- url: 'https://example.com/modify.jpg',
980
- weight: 1.0,
981
- },
982
- },
983
- },
984
- };
985
-
986
- const model = createBasicModel();
987
- const result = await model.doGenerate({
988
- prompt,
989
- files: undefined,
990
- mask: undefined,
991
- n: 1,
992
- providerOptions: {},
993
- size: undefined,
994
- seed: undefined,
995
- aspectRatio: undefined,
996
- });
997
-
998
- expect(result.images).toBeDefined();
999
- });
1000
- });
1001
- });
@@ -1,57 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- import { createLuma } from './luma-provider';
3
- import { LumaImageModel } from './luma-image-model';
4
-
5
- vi.mock('./luma-image-model', () => ({
6
- LumaImageModel: vi.fn(),
7
- }));
8
-
9
- describe('createLuma', () => {
10
- beforeEach(() => {
11
- vi.clearAllMocks();
12
- });
13
-
14
- describe('image', () => {
15
- it('should construct an image model with default configuration', () => {
16
- const provider = createLuma();
17
- const modelId = 'luma-v1';
18
-
19
- const model = provider.image(modelId);
20
-
21
- expect(model).toBeInstanceOf(LumaImageModel);
22
- expect(LumaImageModel).toHaveBeenCalledWith(
23
- modelId,
24
- expect.objectContaining({
25
- provider: 'luma.image',
26
- baseURL: 'https://api.lumalabs.ai',
27
- }),
28
- );
29
- });
30
-
31
- it('should respect custom configuration options', () => {
32
- const customBaseURL = 'https://custom-api.lumalabs.ai';
33
- const customHeaders = { 'X-Custom-Header': 'value' };
34
- const mockFetch = vi.fn();
35
-
36
- const provider = createLuma({
37
- apiKey: 'custom-api-key',
38
- baseURL: customBaseURL,
39
- headers: customHeaders,
40
- fetch: mockFetch,
41
- });
42
- const modelId = 'luma-v1';
43
-
44
- provider.image(modelId);
45
-
46
- expect(LumaImageModel).toHaveBeenCalledWith(
47
- modelId,
48
- expect.objectContaining({
49
- baseURL: customBaseURL,
50
- headers: expect.any(Function),
51
- fetch: mockFetch,
52
- provider: 'luma.image',
53
- }),
54
- );
55
- });
56
- });
57
- });