Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/byok/common/test/geminiFunctionDeclarationConverter.spec.ts
13405 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Type } from '@google/genai';
7
import { describe, expect, it } from 'vitest';
8
import { toGeminiFunction, ToolJsonSchema } from '../geminiFunctionDeclarationConverter';
9
10
describe('GeminiFunctionDeclarationConverter', () => {
11
describe('toGeminiFunction', () => {
12
it('should convert basic function with simple parameters', () => {
13
const schema: ToolJsonSchema = {
14
type: 'object',
15
properties: {
16
name: {
17
type: 'string',
18
description: 'The name parameter'
19
},
20
age: {
21
type: 'number',
22
description: 'The age parameter'
23
},
24
isActive: {
25
type: 'boolean',
26
description: 'Whether the user is active'
27
}
28
},
29
required: ['name', 'age']
30
};
31
32
const result = toGeminiFunction('testFunction', 'A test function', schema);
33
34
expect(result.name).toBe('testFunction');
35
expect(result.description).toBe('A test function');
36
expect(result.parameters).toBeDefined();
37
expect(result.parameters!.type).toBe(Type.OBJECT);
38
expect(result.parameters!.required).toEqual(['name', 'age']);
39
expect(result.parameters!.properties).toBeDefined();
40
expect(result.parameters!.properties!['name']).toEqual({
41
type: Type.STRING,
42
description: 'The name parameter'
43
});
44
expect(result.parameters!.properties!['age']).toEqual({
45
type: Type.NUMBER,
46
description: 'The age parameter'
47
});
48
expect(result.parameters!.properties!['isActive']).toEqual({
49
type: Type.BOOLEAN,
50
description: 'Whether the user is active'
51
});
52
});
53
54
it('should handle function with no description', () => {
55
const schema: ToolJsonSchema = {
56
type: 'object',
57
properties: {
58
value: { type: 'string' }
59
}
60
};
61
62
const result = toGeminiFunction('noDescFunction', '', schema);
63
64
expect(result.description).toBe('No description provided.');
65
});
66
67
it('should handle integer type by mapping to INTEGER', () => {
68
const schema: ToolJsonSchema = {
69
type: 'object',
70
properties: {
71
count: {
72
type: 'integer',
73
description: 'An integer count'
74
},
75
groupIndex: {
76
type: 'integer',
77
description: 'Group index'
78
}
79
},
80
required: ['count']
81
};
82
83
const result = toGeminiFunction('integerFunction', 'Function with integer parameters', schema);
84
85
expect(result.parameters).toBeDefined();
86
expect(result.parameters!.type).toBe(Type.OBJECT);
87
expect(result.parameters!.required).toEqual(['count']);
88
expect(result.parameters!.properties).toBeDefined();
89
expect(result.parameters!.properties!['count']).toEqual({
90
type: Type.INTEGER,
91
description: 'An integer count'
92
});
93
expect(result.parameters!.properties!['groupIndex']).toEqual({
94
type: Type.INTEGER,
95
description: 'Group index'
96
});
97
});
98
99
it('should handle null type by mapping to NULL', () => {
100
const schema: ToolJsonSchema = {
101
type: 'object',
102
properties: {
103
nullableField: {
104
type: 'null',
105
description: 'A nullable field'
106
}
107
}
108
};
109
110
const result = toGeminiFunction('nullFunction', 'Function with null parameter', schema);
111
112
expect(result.parameters).toBeDefined();
113
expect(result.parameters!.properties).toBeDefined();
114
expect(result.parameters!.properties!['nullableField']).toEqual({
115
type: Type.NULL,
116
description: 'A nullable field'
117
});
118
});
119
120
it('should handle array schema by using items as parameters', () => {
121
const schema: ToolJsonSchema = {
122
type: 'array',
123
items: {
124
type: 'object',
125
properties: {
126
id: { type: 'string' },
127
count: { type: 'number' }
128
},
129
required: ['id']
130
}
131
};
132
133
const result = toGeminiFunction('arrayFunction', 'Array function', schema);
134
135
expect(result.parameters).toBeDefined();
136
expect(result.parameters!.type).toBe(Type.OBJECT);
137
expect(result.parameters!.required).toEqual(['id']);
138
expect(result.parameters!.properties).toBeDefined();
139
expect(result.parameters!.properties!['id']).toEqual({
140
type: Type.STRING
141
});
142
expect(result.parameters!.properties!['count']).toEqual({
143
type: Type.NUMBER
144
});
145
});
146
147
it('should handle nested object properties', () => {
148
const schema: ToolJsonSchema = {
149
type: 'object',
150
properties: {
151
user: {
152
type: 'object',
153
description: 'User information',
154
properties: {
155
profile: {
156
type: 'object',
157
properties: {
158
firstName: { type: 'string' },
159
lastName: { type: 'string' }
160
},
161
required: ['firstName']
162
},
163
settings: {
164
type: 'object',
165
properties: {
166
theme: { type: 'string' },
167
notifications: { type: 'boolean' }
168
}
169
}
170
},
171
required: ['profile']
172
}
173
}
174
};
175
176
const result = toGeminiFunction('nestedFunction', 'Function with nested objects', schema);
177
178
expect(result.parameters).toBeDefined();
179
expect(result.parameters!.properties).toBeDefined();
180
const userProperty = result.parameters!.properties!['user'];
181
expect(userProperty.type).toBe(Type.OBJECT);
182
expect(userProperty.description).toBe('User information');
183
expect(userProperty.required).toEqual(['profile']);
184
expect(userProperty.properties).toBeDefined();
185
186
const profileProperty = userProperty.properties!['profile'];
187
expect(profileProperty.type).toBe(Type.OBJECT);
188
expect(profileProperty.required).toEqual(['firstName']);
189
expect(profileProperty.properties).toBeDefined();
190
expect(profileProperty.properties!['firstName']).toEqual({
191
type: Type.STRING
192
});
193
expect(profileProperty.properties!['lastName']).toEqual({
194
type: Type.STRING
195
});
196
197
const settingsProperty = userProperty.properties!['settings'];
198
expect(settingsProperty.type).toBe(Type.OBJECT);
199
expect(settingsProperty.properties).toBeDefined();
200
expect(settingsProperty.properties!['theme']).toEqual({
201
type: Type.STRING
202
});
203
expect(settingsProperty.properties!['notifications']).toEqual({
204
type: Type.BOOLEAN
205
});
206
});
207
208
it('should handle array properties with primitive items', () => {
209
const schema: ToolJsonSchema = {
210
type: 'object',
211
properties: {
212
tags: {
213
type: 'array',
214
description: 'List of tags',
215
items: {
216
type: 'string',
217
description: 'Individual tag'
218
}
219
},
220
scores: {
221
type: 'array',
222
items: {
223
type: 'number'
224
}
225
}
226
}
227
};
228
229
const result = toGeminiFunction('arrayPropsFunction', 'Function with arrays', schema);
230
231
expect(result.parameters).toBeDefined();
232
expect(result.parameters!.properties).toBeDefined();
233
const tagsProperty = result.parameters!.properties!['tags'];
234
expect(tagsProperty.type).toBe(Type.ARRAY);
235
expect(tagsProperty.description).toBe('List of tags');
236
expect(tagsProperty.items).toEqual({
237
type: Type.STRING,
238
description: 'Individual tag'
239
});
240
241
const scoresProperty = result.parameters!.properties!['scores'];
242
expect(scoresProperty.type).toBe(Type.ARRAY);
243
expect(scoresProperty.items).toEqual({
244
type: Type.NUMBER
245
});
246
});
247
248
it('should handle array properties with object items', () => {
249
const schema: ToolJsonSchema = {
250
type: 'object',
251
properties: {
252
items: {
253
type: 'array',
254
description: 'List of items',
255
items: {
256
type: 'object',
257
description: 'Individual item',
258
properties: {
259
id: { type: 'string' },
260
name: { type: 'string' },
261
metadata: {
262
type: 'object',
263
properties: {
264
created: { type: 'string' },
265
version: { type: 'number' }
266
}
267
}
268
},
269
required: ['id', 'name']
270
}
271
}
272
}
273
};
274
275
const result = toGeminiFunction('complexArrayFunction', 'Function with complex arrays', schema);
276
277
expect(result.parameters).toBeDefined();
278
expect(result.parameters!.properties).toBeDefined();
279
const itemsProperty = result.parameters!.properties!['items'];
280
expect(itemsProperty.type).toBe(Type.ARRAY);
281
expect(itemsProperty.description).toBe('List of items');
282
expect(itemsProperty.items).toBeDefined();
283
expect(itemsProperty.items!.type).toBe(Type.OBJECT);
284
expect(itemsProperty.items!.description).toBe('Individual item');
285
expect(itemsProperty.items!.required).toEqual(['id', 'name']);
286
expect(itemsProperty.items!.properties).toBeDefined();
287
expect(itemsProperty.items!.properties!['id']).toEqual({
288
type: Type.STRING
289
});
290
expect(itemsProperty.items!.properties!['name']).toEqual({
291
type: Type.STRING
292
});
293
expect(itemsProperty.items!.properties!['metadata'].type).toBe(Type.OBJECT);
294
expect(itemsProperty.items!.properties!['metadata'].properties).toBeDefined();
295
expect(itemsProperty.items!.properties!['metadata'].properties!['created']).toEqual({
296
type: Type.STRING
297
});
298
expect(itemsProperty.items!.properties!['metadata'].properties!['version']).toEqual({
299
type: Type.NUMBER
300
});
301
});
302
303
it('should handle enum properties', () => {
304
const schema: ToolJsonSchema = {
305
type: 'object',
306
properties: {
307
status: {
308
type: 'string',
309
description: 'Status value',
310
enum: ['active', 'inactive', 'pending']
311
},
312
priority: {
313
type: 'string',
314
enum: ['1', '2', '3', '4', '5']
315
}
316
}
317
};
318
319
const result = toGeminiFunction('enumFunction', 'Function with enums', schema);
320
321
expect(result.parameters).toBeDefined();
322
expect(result.parameters!.properties).toBeDefined();
323
const statusProperty = result.parameters!.properties!['status'];
324
expect(statusProperty.type).toBe(Type.STRING);
325
expect(statusProperty.description).toBe('Status value');
326
expect(statusProperty.enum).toEqual(['active', 'inactive', 'pending']);
327
328
const priorityProperty = result.parameters!.properties!['priority'];
329
expect(priorityProperty.type).toBe(Type.STRING);
330
expect(priorityProperty.enum).toEqual(['1', '2', '3', '4', '5']);
331
});
332
333
it('should handle anyOf composition by using first option', () => {
334
const schema: ToolJsonSchema = {
335
type: 'object',
336
properties: {
337
value: {
338
anyOf: [
339
{ type: 'string', description: 'String value' },
340
{ type: 'number', description: 'Number value' }
341
]
342
}
343
}
344
};
345
346
const result = toGeminiFunction('anyOfFunction', 'Function with anyOf', schema);
347
348
expect(result.parameters).toBeDefined();
349
expect(result.parameters!.properties).toBeDefined();
350
const valueProperty = result.parameters!.properties!['value'];
351
expect(valueProperty.type).toBe(Type.STRING);
352
expect(valueProperty.description).toBe('String value');
353
});
354
355
it('should handle oneOf composition by using first option', () => {
356
const schema: ToolJsonSchema = {
357
type: 'object',
358
properties: {
359
data: {
360
oneOf: [
361
{ type: 'boolean', description: 'Boolean data' },
362
{ type: 'string', description: 'String data' }
363
]
364
}
365
}
366
};
367
368
const result = toGeminiFunction('oneOfFunction', 'Function with oneOf', schema);
369
370
expect(result.parameters).toBeDefined();
371
expect(result.parameters!.properties).toBeDefined();
372
const dataProperty = result.parameters!.properties!['data'];
373
expect(dataProperty.type).toBe(Type.BOOLEAN);
374
expect(dataProperty.description).toBe('Boolean data');
375
});
376
377
it('should handle allOf composition by using first option', () => {
378
const schema: ToolJsonSchema = {
379
type: 'object',
380
properties: {
381
config: {
382
allOf: [
383
{ type: 'object', description: 'Config object' },
384
{ type: 'string', description: 'Config string' }
385
]
386
}
387
}
388
};
389
390
const result = toGeminiFunction('allOfFunction', 'Function with allOf', schema);
391
392
expect(result.parameters).toBeDefined();
393
expect(result.parameters!.properties).toBeDefined();
394
const configProperty = result.parameters!.properties!['config'];
395
expect(configProperty.type).toBe(Type.OBJECT);
396
expect(configProperty.description).toBe('Config object');
397
});
398
399
it('should handle schema with no properties', () => {
400
const schema: ToolJsonSchema = {
401
type: 'object'
402
};
403
404
const result = toGeminiFunction('emptyFunction', 'Function with no properties', schema);
405
406
expect(result.parameters).toBeDefined();
407
expect(result.parameters!.type).toBe(Type.OBJECT);
408
expect(result.parameters!.properties).toEqual({});
409
expect(result.parameters!.required).toEqual([]);
410
});
411
412
it('should handle schema with no required fields', () => {
413
const schema: ToolJsonSchema = {
414
type: 'object',
415
properties: {
416
optional1: { type: 'string' },
417
optional2: { type: 'number' }
418
}
419
};
420
421
const result = toGeminiFunction('optionalFunction', 'Function with optional params', schema);
422
423
expect(result.parameters).toBeDefined();
424
expect(result.parameters!.required).toEqual([]);
425
expect(result.parameters!.properties).toBeDefined();
426
expect(result.parameters!.properties!['optional1']).toEqual({
427
type: Type.STRING
428
});
429
expect(result.parameters!.properties!['optional2']).toEqual({
430
type: Type.NUMBER
431
});
432
});
433
434
it('should default to object type when type is missing', () => {
435
const schema: ToolJsonSchema = {
436
properties: {
437
field: {
438
description: 'Field without type'
439
}
440
}
441
};
442
443
const result = toGeminiFunction('defaultTypeFunction', 'Function with missing types', schema);
444
445
expect(result.parameters).toBeDefined();
446
expect(result.parameters!.properties).toBeDefined();
447
const fieldProperty = result.parameters!.properties!['field'];
448
expect(fieldProperty.type).toBe(Type.OBJECT);
449
expect(fieldProperty.description).toBe('Field without type');
450
});
451
});
452
});
453