@lobehub/chat 1.138.3 → 1.138.4

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.
@@ -2,145 +2,174 @@
2
2
  import { Type as SchemaType } from '@google/genai';
3
3
  import { describe, expect, it, vi } from 'vitest';
4
4
 
5
- import { convertOpenAISchemaToGoogleSchema, createGoogleGenerateObject } from './generateObject';
5
+ import {
6
+ convertOpenAISchemaToGoogleSchema,
7
+ createGoogleGenerateObject,
8
+ createGoogleGenerateObjectWithTools,
9
+ } from './generateObject';
6
10
 
7
11
  describe('Google generateObject', () => {
8
12
  describe('convertOpenAISchemaToGoogleSchema', () => {
9
13
  it('should convert basic types correctly', () => {
10
14
  const openAISchema = {
11
- type: 'object',
12
- properties: {
13
- name: { type: 'string' },
14
- age: { type: 'number' },
15
- isActive: { type: 'boolean' },
16
- count: { type: 'integer' },
15
+ name: 'person',
16
+ schema: {
17
+ properties: {
18
+ age: { type: 'number' },
19
+ count: { type: 'integer' },
20
+ isActive: { type: 'boolean' },
21
+ name: { type: 'string' },
22
+ },
23
+ type: 'object' as const,
17
24
  },
18
25
  };
19
26
 
20
27
  const result = convertOpenAISchemaToGoogleSchema(openAISchema);
21
28
 
22
29
  expect(result).toEqual({
23
- type: SchemaType.OBJECT,
24
30
  properties: {
25
- name: { type: SchemaType.STRING },
26
31
  age: { type: SchemaType.NUMBER },
27
- isActive: { type: SchemaType.BOOLEAN },
28
32
  count: { type: SchemaType.INTEGER },
33
+ isActive: { type: SchemaType.BOOLEAN },
34
+ name: { type: SchemaType.STRING },
29
35
  },
36
+ type: SchemaType.OBJECT,
30
37
  });
31
38
  });
32
39
 
33
40
  it('should convert array schemas correctly', () => {
34
41
  const openAISchema = {
35
- type: 'array',
36
- items: {
37
- type: 'object',
42
+ name: 'recipes',
43
+ schema: {
38
44
  properties: {
39
- recipeName: { type: 'string' },
40
- ingredients: {
45
+ recipes: {
46
+ items: {
47
+ properties: {
48
+ ingredients: {
49
+ items: { type: 'string' },
50
+ type: 'array',
51
+ },
52
+ recipeName: { type: 'string' },
53
+ },
54
+ propertyOrdering: ['recipeName', 'ingredients'],
55
+ type: 'object',
56
+ },
41
57
  type: 'array',
42
- items: { type: 'string' },
43
58
  },
44
59
  },
45
- propertyOrdering: ['recipeName', 'ingredients'],
60
+ type: 'object' as const,
46
61
  },
47
62
  };
48
63
 
49
64
  const result = convertOpenAISchemaToGoogleSchema(openAISchema);
50
65
 
51
66
  expect(result).toEqual({
52
- type: SchemaType.ARRAY,
53
- items: {
54
- type: SchemaType.OBJECT,
55
- properties: {
56
- recipeName: { type: SchemaType.STRING },
57
- ingredients: {
58
- type: SchemaType.ARRAY,
59
- items: { type: SchemaType.STRING },
67
+ properties: {
68
+ recipes: {
69
+ items: {
70
+ properties: {
71
+ ingredients: {
72
+ items: { type: SchemaType.STRING },
73
+ type: SchemaType.ARRAY,
74
+ },
75
+ recipeName: { type: SchemaType.STRING },
76
+ },
77
+ propertyOrdering: ['recipeName', 'ingredients'],
78
+ type: SchemaType.OBJECT,
60
79
  },
80
+ type: SchemaType.ARRAY,
61
81
  },
62
- propertyOrdering: ['recipeName', 'ingredients'],
63
82
  },
83
+ type: SchemaType.OBJECT,
64
84
  });
65
85
  });
66
86
 
67
87
  it('should handle nested objects', () => {
68
88
  const openAISchema = {
69
- type: 'object',
70
- properties: {
71
- user: {
72
- type: 'object',
73
- properties: {
74
- profile: {
75
- type: 'object',
76
- properties: {
77
- preferences: {
78
- type: 'array',
79
- items: { type: 'string' },
89
+ name: 'user_data',
90
+ schema: {
91
+ properties: {
92
+ user: {
93
+ properties: {
94
+ profile: {
95
+ properties: {
96
+ preferences: {
97
+ items: { type: 'string' },
98
+ type: 'array',
99
+ },
80
100
  },
101
+ type: 'object',
81
102
  },
82
103
  },
104
+ type: 'object',
83
105
  },
84
106
  },
107
+ type: 'object' as const,
85
108
  },
86
109
  };
87
110
 
88
111
  const result = convertOpenAISchemaToGoogleSchema(openAISchema);
89
112
 
90
113
  expect(result).toEqual({
91
- type: SchemaType.OBJECT,
92
114
  properties: {
93
115
  user: {
94
- type: SchemaType.OBJECT,
95
116
  properties: {
96
117
  profile: {
97
- type: SchemaType.OBJECT,
98
118
  properties: {
99
119
  preferences: {
100
- type: SchemaType.ARRAY,
101
120
  items: { type: SchemaType.STRING },
121
+ type: SchemaType.ARRAY,
102
122
  },
103
123
  },
124
+ type: SchemaType.OBJECT,
104
125
  },
105
126
  },
127
+ type: SchemaType.OBJECT,
106
128
  },
107
129
  },
130
+ type: SchemaType.OBJECT,
108
131
  });
109
132
  });
110
133
 
111
134
  it('should preserve additional properties like description, enum, required', () => {
112
135
  const openAISchema = {
113
- type: 'object',
114
- description: 'A person object',
115
- properties: {
116
- status: {
117
- type: 'string',
118
- enum: ['active', 'inactive'],
119
- description: 'The status of the person',
136
+ name: 'person',
137
+ schema: {
138
+ description: 'A person object',
139
+ properties: {
140
+ status: {
141
+ description: 'The status of the person',
142
+ enum: ['active', 'inactive'],
143
+ type: 'string',
144
+ },
120
145
  },
121
- },
122
- required: ['status'],
146
+ required: ['status'],
147
+ type: 'object' as const,
148
+ } as any,
123
149
  };
124
150
 
125
151
  const result = convertOpenAISchemaToGoogleSchema(openAISchema);
126
152
 
127
153
  expect(result).toEqual({
128
- type: SchemaType.OBJECT,
129
154
  description: 'A person object',
130
155
  properties: {
131
156
  status: {
132
- type: SchemaType.STRING,
133
- enum: ['active', 'inactive'],
134
157
  description: 'The status of the person',
158
+ enum: ['active', 'inactive'],
159
+ type: SchemaType.STRING,
135
160
  },
136
161
  },
137
162
  required: ['status'],
163
+ type: SchemaType.OBJECT,
138
164
  });
139
165
  });
140
166
 
141
167
  it('should handle unknown types by defaulting to STRING', () => {
142
168
  const openAISchema = {
143
- type: 'unknown-type',
169
+ name: 'test',
170
+ schema: {
171
+ type: 'unknown-type' as any,
172
+ } as any,
144
173
  };
145
174
 
146
175
  const result = convertOpenAISchemaToGoogleSchema(openAISchema);
@@ -161,15 +190,18 @@ describe('Google generateObject', () => {
161
190
  },
162
191
  };
163
192
 
164
- const contents = [{ role: 'user', parts: [{ text: 'Generate a person object' }] }];
193
+ const contents = [{ parts: [{ text: 'Generate a person object' }], role: 'user' }];
165
194
 
166
195
  const payload = {
167
196
  contents,
197
+ model: 'gemini-2.5-flash',
168
198
  schema: {
169
- type: 'object',
170
- properties: { name: { type: 'string' }, age: { type: 'number' } },
199
+ name: 'person',
200
+ schema: {
201
+ properties: { age: { type: 'number' }, name: { type: 'string' } },
202
+ type: 'object' as const,
203
+ },
171
204
  },
172
- model: 'gemini-2.5-flash',
173
205
  };
174
206
 
175
207
  const result = await createGoogleGenerateObject(mockClient as any, payload);
@@ -178,11 +210,11 @@ describe('Google generateObject', () => {
178
210
  config: expect.objectContaining({
179
211
  responseMimeType: 'application/json',
180
212
  responseSchema: expect.objectContaining({
181
- type: SchemaType.OBJECT,
182
213
  properties: expect.objectContaining({
183
- name: { type: SchemaType.STRING },
184
214
  age: { type: SchemaType.NUMBER },
215
+ name: { type: SchemaType.STRING },
185
216
  }),
217
+ type: SchemaType.OBJECT,
186
218
  }),
187
219
  safetySettings: expect.any(Array),
188
220
  }),
@@ -190,7 +222,7 @@ describe('Google generateObject', () => {
190
222
  model: 'gemini-2.5-flash',
191
223
  });
192
224
 
193
- expect(result).toEqual({ name: 'John', age: 30 });
225
+ expect(result).toEqual({ age: 30, name: 'John' });
194
226
  });
195
227
 
196
228
  it('should handle options correctly', async () => {
@@ -202,12 +234,18 @@ describe('Google generateObject', () => {
202
234
  },
203
235
  };
204
236
 
205
- const contents = [{ role: 'user', parts: [{ text: 'Generate status' }] }];
237
+ const contents = [{ parts: [{ text: 'Generate status' }], role: 'user' }];
206
238
 
207
239
  const payload = {
208
240
  contents,
209
- schema: { type: 'object', properties: { status: { type: 'string' } } },
210
241
  model: 'gemini-2.5-flash',
242
+ schema: {
243
+ name: 'status',
244
+ schema: {
245
+ properties: { status: { type: 'string' } },
246
+ type: 'object' as const,
247
+ },
248
+ },
211
249
  };
212
250
 
213
251
  const options = {
@@ -221,10 +259,10 @@ describe('Google generateObject', () => {
221
259
  abortSignal: options.signal,
222
260
  responseMimeType: 'application/json',
223
261
  responseSchema: expect.objectContaining({
224
- type: SchemaType.OBJECT,
225
262
  properties: expect.objectContaining({
226
263
  status: { type: SchemaType.STRING },
227
264
  }),
265
+ type: SchemaType.OBJECT,
228
266
  }),
229
267
  }),
230
268
  contents,
@@ -248,8 +286,14 @@ describe('Google generateObject', () => {
248
286
 
249
287
  const payload = {
250
288
  contents,
251
- schema: { type: 'object' },
252
289
  model: 'gemini-2.5-flash',
290
+ schema: {
291
+ name: 'test',
292
+ schema: {
293
+ properties: {},
294
+ type: 'object' as const,
295
+ },
296
+ },
253
297
  };
254
298
 
255
299
  const result = await createGoogleGenerateObject(mockClient as any, payload);
@@ -273,31 +317,37 @@ describe('Google generateObject', () => {
273
317
 
274
318
  const payload = {
275
319
  contents,
320
+ model: 'gemini-2.5-flash',
276
321
  schema: {
277
- type: 'object',
278
- properties: {
279
- user: {
280
- type: 'object',
281
- properties: {
282
- name: { type: 'string' },
283
- profile: {
284
- type: 'object',
285
- properties: {
286
- age: { type: 'number' },
287
- preferences: { type: 'array', items: { type: 'string' } },
322
+ name: 'user_data',
323
+ schema: {
324
+ properties: {
325
+ metadata: { type: 'object' },
326
+ user: {
327
+ properties: {
328
+ name: { type: 'string' },
329
+ profile: {
330
+ properties: {
331
+ age: { type: 'number' },
332
+ preferences: { items: { type: 'string' }, type: 'array' },
333
+ },
334
+ type: 'object',
288
335
  },
289
336
  },
337
+ type: 'object',
290
338
  },
291
339
  },
292
- metadata: { type: 'object' },
340
+ type: 'object' as const,
293
341
  },
294
342
  },
295
- model: 'gemini-2.5-flash',
296
343
  };
297
344
 
298
345
  const result = await createGoogleGenerateObject(mockClient as any, payload);
299
346
 
300
347
  expect(result).toEqual({
348
+ metadata: {
349
+ created: '2024-01-01',
350
+ },
301
351
  user: {
302
352
  name: 'Alice',
303
353
  profile: {
@@ -305,9 +355,6 @@ describe('Google generateObject', () => {
305
355
  preferences: ['music', 'sports'],
306
356
  },
307
357
  },
308
- metadata: {
309
- created: '2024-01-01',
310
- },
311
358
  });
312
359
  });
313
360
 
@@ -324,8 +371,14 @@ describe('Google generateObject', () => {
324
371
 
325
372
  const payload = {
326
373
  contents,
327
- schema: { type: 'object' },
328
374
  model: 'gemini-2.5-flash',
375
+ schema: {
376
+ name: 'test',
377
+ schema: {
378
+ properties: {},
379
+ type: 'object' as const,
380
+ },
381
+ },
329
382
  };
330
383
 
331
384
  await expect(createGoogleGenerateObject(mockClient as any, payload)).rejects.toThrow();
@@ -345,8 +398,14 @@ describe('Google generateObject', () => {
345
398
 
346
399
  const payload = {
347
400
  contents,
348
- schema: { type: 'object' },
349
401
  model: 'gemini-2.5-flash',
402
+ schema: {
403
+ name: 'test',
404
+ schema: {
405
+ properties: {},
406
+ type: 'object' as const,
407
+ },
408
+ },
350
409
  };
351
410
 
352
411
  const options = {
@@ -358,4 +417,450 @@ describe('Google generateObject', () => {
358
417
  ).rejects.toThrow();
359
418
  });
360
419
  });
420
+
421
+ describe('createGoogleGenerateObjectWithTools', () => {
422
+ it('should return function calls on successful API call with tools', async () => {
423
+ const mockClient = {
424
+ models: {
425
+ generateContent: vi.fn().mockResolvedValue({
426
+ candidates: [
427
+ {
428
+ content: {
429
+ parts: [
430
+ {
431
+ functionCall: {
432
+ args: { city: 'New York', unit: 'celsius' },
433
+ name: 'get_weather',
434
+ },
435
+ },
436
+ ],
437
+ },
438
+ },
439
+ ],
440
+ }),
441
+ },
442
+ };
443
+
444
+ const contents = [{ parts: [{ text: 'What is the weather in New York?' }], role: 'user' }];
445
+
446
+ const payload = {
447
+ contents,
448
+ model: 'gemini-2.5-flash',
449
+ tools: [
450
+ {
451
+ function: {
452
+ description: 'Get weather information',
453
+ name: 'get_weather',
454
+ parameters: {
455
+ properties: {
456
+ city: { type: 'string' },
457
+ unit: { type: 'string' },
458
+ },
459
+ required: ['city'],
460
+ type: 'object' as const,
461
+ },
462
+ },
463
+ type: 'function' as const,
464
+ },
465
+ ],
466
+ };
467
+
468
+ const result = await createGoogleGenerateObjectWithTools(mockClient as any, payload);
469
+
470
+ expect(mockClient.models.generateContent).toHaveBeenCalledWith({
471
+ config: expect.objectContaining({
472
+ safetySettings: expect.any(Array),
473
+ toolConfig: {
474
+ functionCallingConfig: {
475
+ mode: 'ANY',
476
+ },
477
+ },
478
+ tools: [
479
+ {
480
+ functionDeclarations: [
481
+ {
482
+ description: 'Get weather information',
483
+ name: 'get_weather',
484
+ parameters: {
485
+ description: undefined,
486
+ properties: {
487
+ city: { type: 'string' },
488
+ unit: { type: 'string' },
489
+ },
490
+ required: ['city'],
491
+ type: SchemaType.OBJECT,
492
+ },
493
+ },
494
+ ],
495
+ },
496
+ ],
497
+ }),
498
+ contents,
499
+ model: 'gemini-2.5-flash',
500
+ });
501
+
502
+ expect(result).toEqual([
503
+ { arguments: { city: 'New York', unit: 'celsius' }, name: 'get_weather' },
504
+ ]);
505
+ });
506
+
507
+ it('should handle multiple function calls', async () => {
508
+ const mockClient = {
509
+ models: {
510
+ generateContent: vi.fn().mockResolvedValue({
511
+ candidates: [
512
+ {
513
+ content: {
514
+ parts: [
515
+ {
516
+ functionCall: {
517
+ args: { city: 'New York', unit: 'celsius' },
518
+ name: 'get_weather',
519
+ },
520
+ },
521
+ {
522
+ functionCall: {
523
+ args: { timezone: 'America/New_York' },
524
+ name: 'get_time',
525
+ },
526
+ },
527
+ ],
528
+ },
529
+ },
530
+ ],
531
+ }),
532
+ },
533
+ };
534
+
535
+ const contents: any[] = [];
536
+
537
+ const payload = {
538
+ contents,
539
+ model: 'gemini-2.5-flash',
540
+ tools: [
541
+ {
542
+ function: {
543
+ description: 'Get weather information',
544
+ name: 'get_weather',
545
+ parameters: {
546
+ properties: {
547
+ city: { type: 'string' },
548
+ unit: { type: 'string' },
549
+ },
550
+ required: ['city'],
551
+ type: 'object' as const,
552
+ },
553
+ },
554
+ type: 'function' as const,
555
+ },
556
+ {
557
+ function: {
558
+ description: 'Get current time',
559
+ name: 'get_time',
560
+ parameters: {
561
+ properties: {
562
+ timezone: { type: 'string' },
563
+ },
564
+ required: ['timezone'],
565
+ type: 'object' as const,
566
+ },
567
+ },
568
+ type: 'function' as const,
569
+ },
570
+ ],
571
+ };
572
+
573
+ const result = await createGoogleGenerateObjectWithTools(mockClient as any, payload);
574
+
575
+ expect(result).toEqual([
576
+ { arguments: { city: 'New York', unit: 'celsius' }, name: 'get_weather' },
577
+ { arguments: { timezone: 'America/New_York' }, name: 'get_time' },
578
+ ]);
579
+ });
580
+
581
+ it('should handle options correctly', async () => {
582
+ const mockClient = {
583
+ models: {
584
+ generateContent: vi.fn().mockResolvedValue({
585
+ candidates: [
586
+ {
587
+ content: {
588
+ parts: [
589
+ {
590
+ functionCall: {
591
+ args: { a: 5, b: 3, operation: 'add' },
592
+ name: 'calculate',
593
+ },
594
+ },
595
+ ],
596
+ },
597
+ },
598
+ ],
599
+ }),
600
+ },
601
+ };
602
+
603
+ const contents: any[] = [];
604
+
605
+ const payload = {
606
+ contents,
607
+ model: 'gemini-2.5-flash',
608
+ tools: [
609
+ {
610
+ function: {
611
+ description: 'Perform mathematical calculation',
612
+ name: 'calculate',
613
+ parameters: {
614
+ properties: {
615
+ a: { type: 'number' },
616
+ b: { type: 'number' },
617
+ operation: { type: 'string' },
618
+ },
619
+ required: ['operation', 'a', 'b'],
620
+ type: 'object' as const,
621
+ },
622
+ },
623
+ type: 'function' as const,
624
+ },
625
+ ],
626
+ };
627
+
628
+ const options = {
629
+ signal: new AbortController().signal,
630
+ };
631
+
632
+ const result = await createGoogleGenerateObjectWithTools(mockClient as any, payload, options);
633
+
634
+ expect(mockClient.models.generateContent).toHaveBeenCalledWith({
635
+ config: expect.objectContaining({
636
+ abortSignal: options.signal,
637
+ }),
638
+ contents,
639
+ model: 'gemini-2.5-flash',
640
+ });
641
+
642
+ expect(result).toEqual([{ arguments: { a: 5, b: 3, operation: 'add' }, name: 'calculate' }]);
643
+ });
644
+
645
+ it('should return undefined when no function calls in response', async () => {
646
+ const mockClient = {
647
+ models: {
648
+ generateContent: vi.fn().mockResolvedValue({
649
+ candidates: [
650
+ {
651
+ content: {
652
+ parts: [
653
+ {
654
+ text: 'Some text response without function call',
655
+ },
656
+ ],
657
+ },
658
+ },
659
+ ],
660
+ }),
661
+ },
662
+ };
663
+
664
+ const contents: any[] = [];
665
+
666
+ const payload = {
667
+ contents,
668
+ model: 'gemini-2.5-flash',
669
+ tools: [
670
+ {
671
+ function: {
672
+ description: 'Test function',
673
+ name: 'test_function',
674
+ parameters: {
675
+ properties: {},
676
+ type: 'object' as const,
677
+ },
678
+ },
679
+ type: 'function' as const,
680
+ },
681
+ ],
682
+ };
683
+
684
+ const result = await createGoogleGenerateObjectWithTools(mockClient as any, payload);
685
+
686
+ expect(result).toBeUndefined();
687
+ });
688
+
689
+ it('should return undefined when no content parts in response', async () => {
690
+ const mockClient = {
691
+ models: {
692
+ generateContent: vi.fn().mockResolvedValue({
693
+ candidates: [
694
+ {
695
+ content: {},
696
+ },
697
+ ],
698
+ }),
699
+ },
700
+ };
701
+
702
+ const contents: any[] = [];
703
+
704
+ const payload = {
705
+ contents,
706
+ model: 'gemini-2.5-flash',
707
+ tools: [
708
+ {
709
+ function: {
710
+ description: 'Test function',
711
+ name: 'test_function',
712
+ parameters: {
713
+ properties: {},
714
+ type: 'object' as const,
715
+ },
716
+ },
717
+ type: 'function' as const,
718
+ },
719
+ ],
720
+ };
721
+
722
+ const result = await createGoogleGenerateObjectWithTools(mockClient as any, payload);
723
+
724
+ expect(result).toBeUndefined();
725
+ });
726
+
727
+ it('should propagate API errors correctly', async () => {
728
+ const apiError = new Error('API Error: Model not found');
729
+
730
+ const mockClient = {
731
+ models: {
732
+ generateContent: vi.fn().mockRejectedValue(apiError),
733
+ },
734
+ };
735
+
736
+ const contents: any[] = [];
737
+
738
+ const payload = {
739
+ contents,
740
+ model: 'gemini-2.5-flash',
741
+ tools: [
742
+ {
743
+ function: {
744
+ description: 'Test function',
745
+ name: 'test_function',
746
+ parameters: {
747
+ properties: {},
748
+ type: 'object' as const,
749
+ },
750
+ },
751
+ type: 'function' as const,
752
+ },
753
+ ],
754
+ };
755
+
756
+ await expect(createGoogleGenerateObjectWithTools(mockClient as any, payload)).rejects.toThrow(
757
+ 'API Error: Model not found',
758
+ );
759
+ });
760
+
761
+ it('should handle abort signals correctly', async () => {
762
+ const apiError = new Error('Request was cancelled');
763
+ apiError.name = 'AbortError';
764
+
765
+ const mockClient = {
766
+ models: {
767
+ generateContent: vi.fn().mockRejectedValue(apiError),
768
+ },
769
+ };
770
+
771
+ const contents: any[] = [];
772
+
773
+ const payload = {
774
+ contents,
775
+ model: 'gemini-2.5-flash',
776
+ tools: [
777
+ {
778
+ function: {
779
+ description: 'Test function',
780
+ name: 'test_function',
781
+ parameters: {
782
+ properties: {},
783
+ type: 'object' as const,
784
+ },
785
+ },
786
+ type: 'function' as const,
787
+ },
788
+ ],
789
+ };
790
+
791
+ const options = {
792
+ signal: new AbortController().signal,
793
+ };
794
+
795
+ await expect(
796
+ createGoogleGenerateObjectWithTools(mockClient as any, payload, options),
797
+ ).rejects.toThrow();
798
+ });
799
+
800
+ it('should handle tools with empty parameters', async () => {
801
+ const mockClient = {
802
+ models: {
803
+ generateContent: vi.fn().mockResolvedValue({
804
+ candidates: [
805
+ {
806
+ content: {
807
+ parts: [
808
+ {
809
+ functionCall: {
810
+ args: {},
811
+ name: 'simple_function',
812
+ },
813
+ },
814
+ ],
815
+ },
816
+ },
817
+ ],
818
+ }),
819
+ },
820
+ };
821
+
822
+ const contents: any[] = [];
823
+
824
+ const payload = {
825
+ contents,
826
+ model: 'gemini-2.5-flash',
827
+ tools: [
828
+ {
829
+ function: {
830
+ description: 'A simple function with no parameters',
831
+ name: 'simple_function',
832
+ parameters: {
833
+ properties: {},
834
+ type: 'object' as const,
835
+ },
836
+ },
837
+ type: 'function' as const,
838
+ },
839
+ ],
840
+ };
841
+
842
+ const result = await createGoogleGenerateObjectWithTools(mockClient as any, payload);
843
+
844
+ // Should use dummy property for empty parameters
845
+ expect(mockClient.models.generateContent).toHaveBeenCalledWith({
846
+ config: expect.objectContaining({
847
+ tools: [
848
+ {
849
+ functionDeclarations: [
850
+ expect.objectContaining({
851
+ parameters: expect.objectContaining({
852
+ properties: { dummy: { type: 'string' } },
853
+ }),
854
+ }),
855
+ ],
856
+ },
857
+ ],
858
+ }),
859
+ contents,
860
+ model: 'gemini-2.5-flash',
861
+ });
862
+
863
+ expect(result).toEqual([{ arguments: {}, name: 'simple_function' }]);
864
+ });
865
+ });
361
866
  });