@wabot-dev/framework 0.9.26 → 0.9.80

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.
@@ -0,0 +1,656 @@
1
+ import { testImageBase64Url, testPdfBase64Url } from '../fixtures.js';
2
+
3
+ function ensure(condition, message) {
4
+ if (!condition) {
5
+ throw new Error(message);
6
+ }
7
+ }
8
+ async function ensureRejects(promise, message) {
9
+ let rejected = false;
10
+ try {
11
+ await promise;
12
+ }
13
+ catch {
14
+ rejected = true;
15
+ }
16
+ ensure(rejected, message);
17
+ }
18
+ /**
19
+ * Provider-agnostic conformance suite for IChatAdapter implementations.
20
+ * Runner-agnostic: each case is a plain async function, so it can be wired
21
+ * to node:test, vitest or bun test by the caller.
22
+ */
23
+ function chatAdapterConformanceCases({ adapter, model, }) {
24
+ const models = [{ model }];
25
+ const countryTools = [
26
+ {
27
+ language: 'english',
28
+ name: 'getCountryTime',
29
+ parameters: [
30
+ {
31
+ name: 'country',
32
+ required: true,
33
+ schema: { type: 'string', description: 'the country iso code' },
34
+ },
35
+ ],
36
+ description: 'return the current time of a country',
37
+ },
38
+ {
39
+ language: 'english',
40
+ name: 'getCountryMainLanguage',
41
+ parameters: [
42
+ {
43
+ name: 'country',
44
+ required: true,
45
+ schema: { type: 'string', description: 'the country iso code' },
46
+ },
47
+ ],
48
+ description: 'return the main language of a country',
49
+ },
50
+ ];
51
+ return [
52
+ {
53
+ name: 'bot responds to human message',
54
+ run: async () => {
55
+ const { nextItems } = await adapter.nextItems({
56
+ models,
57
+ tools: [],
58
+ systemPrompt: 'Act as a Bot',
59
+ prevItems: [{ type: 'humanMessage', humanMessage: { text: 'Hello' } }],
60
+ });
61
+ ensure(Array.isArray(nextItems), 'nextItems is not array');
62
+ ensure(nextItems.length === 1, 'nexItems length should be 1');
63
+ ensure(nextItems[0].type === 'botMessage', 'next item should have botMessage type');
64
+ },
65
+ },
66
+ {
67
+ name: 'response include usage',
68
+ run: async () => {
69
+ const { usage } = await adapter.nextItems({
70
+ models,
71
+ tools: [],
72
+ systemPrompt: 'Act as a Bot',
73
+ prevItems: [{ type: 'humanMessage', humanMessage: { text: 'Hello' } }],
74
+ });
75
+ ensure(usage != null, 'usage should be defined');
76
+ ensure(typeof usage === 'object', 'usage should be an object');
77
+ ensure(typeof usage.inputTokens === 'number', 'inputTokens should be number');
78
+ ensure(typeof usage.outputTokens === 'number', 'outputTokens should be number');
79
+ ensure(usage.inputTokens > 0, 'inputTokens should be positive');
80
+ ensure(usage.outputTokens > 0, 'outputTokens should be positive');
81
+ ensure(typeof usage.provider === 'string' && usage.provider.length > 0, 'usage.provider should be a non-empty string');
82
+ ensure(typeof usage.model === 'string' && usage.model.length > 0, 'usage.model should be a non-empty string');
83
+ if (usage.cacheReadTokens !== undefined) {
84
+ ensure(typeof usage.cacheReadTokens === 'number', 'cacheReadTokens should be a number when present');
85
+ ensure(usage.cacheReadTokens >= 0, 'cacheReadTokens should be non-negative');
86
+ }
87
+ if (usage.cacheWriteTokens !== undefined) {
88
+ ensure(typeof usage.cacheWriteTokens === 'number', 'cacheWriteTokens should be a number when present');
89
+ ensure(usage.cacheWriteTokens >= 0, 'cacheWriteTokens should be non-negative');
90
+ }
91
+ if (usage.costUsd !== undefined) {
92
+ ensure(typeof usage.costUsd === 'number', 'costUsd should be a number when present');
93
+ ensure(usage.costUsd >= 0, 'costUsd should be non-negative');
94
+ }
95
+ },
96
+ },
97
+ {
98
+ name: 'throws when the request is invalid',
99
+ run: async () => {
100
+ const responsePromise = adapter.nextItems({
101
+ models,
102
+ tools: [],
103
+ systemPrompt: 'Act as a Bot',
104
+ prevItems: [
105
+ {
106
+ type: 'humanMessage',
107
+ botMessage: { text: 'Hello' },
108
+ },
109
+ ],
110
+ });
111
+ await ensureRejects(responsePromise, 'adapter should reject an invalid request');
112
+ },
113
+ },
114
+ {
115
+ name: 'call the appropiate tool',
116
+ run: async () => {
117
+ const { nextItems } = await adapter.nextItems({
118
+ models,
119
+ tools: countryTools,
120
+ systemPrompt: 'Act as a Bot',
121
+ prevItems: [
122
+ { type: 'humanMessage', humanMessage: { text: 'I am from Colombia' } },
123
+ { type: 'botMessage', botMessage: { text: 'That is a great country' } },
124
+ {
125
+ type: 'humanMessage',
126
+ humanMessage: { text: 'What is the current time in my country' },
127
+ },
128
+ ],
129
+ });
130
+ ensure(Array.isArray(nextItems), 'nextItems is not array');
131
+ const functionCallItem = nextItems.find((x) => x.type === 'functionCall');
132
+ ensure(functionCallItem != null, 'nextItems should contain one functionCall Item');
133
+ const functionCall = functionCallItem.functionCall;
134
+ ensure(functionCall.id != null, 'function call id should be defined');
135
+ ensure(functionCall.name === 'getCountryTime', 'function call name should be getCountryTime');
136
+ ensure(typeof functionCall.arguments === 'string', 'function call arguments should be string');
137
+ const args = JSON.parse(functionCall.arguments);
138
+ ensure(typeof args === 'object', 'function call argument should be an object');
139
+ ensure(typeof args.country === 'string', 'country argument should be string');
140
+ },
141
+ },
142
+ {
143
+ name: 'consume function calls response',
144
+ run: async () => {
145
+ const humanTurn = {
146
+ type: 'humanMessage',
147
+ humanMessage: { text: 'What is the time in Colombia' },
148
+ };
149
+ const firstTurn = await adapter.nextItems({
150
+ models,
151
+ tools: countryTools,
152
+ systemPrompt: 'Act as a Bot',
153
+ prevItems: [humanTurn],
154
+ });
155
+ const callItems = firstTurn.nextItems.filter((x) => x.type === 'functionCall');
156
+ ensure(callItems.length > 0, 'first turn should contain at least one functionCall item');
157
+ const resolvedCallItems = callItems.map((item) => ({
158
+ type: 'functionCall',
159
+ functionCall: {
160
+ ...item.functionCall,
161
+ result: '2023-10-12T12:00:00.000Z',
162
+ },
163
+ }));
164
+ const { nextItems } = await adapter.nextItems({
165
+ models,
166
+ tools: countryTools,
167
+ systemPrompt: 'Act as a Bot',
168
+ prevItems: [humanTurn, ...resolvedCallItems],
169
+ });
170
+ ensure(Array.isArray(nextItems), 'nextItems is not array');
171
+ ensure(nextItems.length === 1, 'nexItems length should be 1');
172
+ ensure(nextItems[0].type === 'botMessage', 'next item should have botMessage type');
173
+ },
174
+ },
175
+ {
176
+ name: 'consume public image',
177
+ run: async () => {
178
+ const { nextItems } = await adapter.nextItems({
179
+ models,
180
+ tools: [],
181
+ systemPrompt: 'Act as a Bot',
182
+ prevItems: [
183
+ {
184
+ type: 'humanMessage',
185
+ humanMessage: {
186
+ images: [
187
+ {
188
+ id: 'image1',
189
+ mimeType: 'image/png',
190
+ publicUrl: 'https://raw.githubusercontent.com/github/explore/main/topics/python/python.png',
191
+ },
192
+ ],
193
+ },
194
+ },
195
+ ],
196
+ });
197
+ ensure(Array.isArray(nextItems), 'nextItems is not array');
198
+ ensure(nextItems.length === 1, 'nexItems length should be 1');
199
+ ensure(nextItems[0].type === 'botMessage', 'next item should have botMessage type');
200
+ },
201
+ },
202
+ {
203
+ name: 'consume private image',
204
+ run: async () => {
205
+ const { nextItems } = await adapter.nextItems({
206
+ models,
207
+ tools: [],
208
+ systemPrompt: 'Act as a Bot',
209
+ prevItems: [
210
+ {
211
+ type: 'humanMessage',
212
+ humanMessage: {
213
+ images: [{ id: 'image1', mimeType: 'image/jpeg', base64Url: testImageBase64Url }],
214
+ },
215
+ },
216
+ ],
217
+ });
218
+ ensure(Array.isArray(nextItems), 'nextItems is not array');
219
+ ensure(nextItems.length === 1, 'nexItems length should be 1');
220
+ ensure(nextItems[0].type === 'botMessage', 'next item should have botMessage type');
221
+ },
222
+ },
223
+ {
224
+ name: 'reads the total price from a receipt image',
225
+ run: async () => {
226
+ const { nextItems } = await adapter.nextItems({
227
+ models,
228
+ tools: [],
229
+ systemPrompt: 'Act as a Bot',
230
+ prevItems: [
231
+ {
232
+ type: 'humanMessage',
233
+ humanMessage: {
234
+ text: 'This is a store receipt. What is the total price? Reply with the number only.',
235
+ images: [{ id: 'receipt', mimeType: 'image/jpeg', base64Url: testImageBase64Url }],
236
+ },
237
+ },
238
+ ],
239
+ });
240
+ ensure(Array.isArray(nextItems), 'nextItems is not array');
241
+ ensure(nextItems.length === 1, 'nexItems length should be 1');
242
+ ensure(nextItems[0].type === 'botMessage', 'next item should have botMessage type');
243
+ const text = nextItems[0].botMessage.text ?? '';
244
+ const digits = text.replace(/[^0-9]/g, '');
245
+ ensure(digits.includes('11570'), `bot response should report the total 11.570, got '${text}'`);
246
+ },
247
+ },
248
+ {
249
+ name: 'consume public document',
250
+ run: async () => {
251
+ const { nextItems } = await adapter.nextItems({
252
+ models,
253
+ tools: [],
254
+ systemPrompt: 'Act as a Bot',
255
+ prevItems: [
256
+ {
257
+ type: 'humanMessage',
258
+ humanMessage: {
259
+ documents: [
260
+ {
261
+ id: 'doc1',
262
+ mimeType: 'application/pdf',
263
+ publicUrl: 'https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf',
264
+ },
265
+ ],
266
+ },
267
+ },
268
+ ],
269
+ });
270
+ ensure(Array.isArray(nextItems), 'nextItems is not array');
271
+ ensure(nextItems.length === 1, 'nexItems length should be 1');
272
+ ensure(nextItems[0].type === 'botMessage', 'next item should have botMessage type');
273
+ },
274
+ },
275
+ {
276
+ name: 'consume private document',
277
+ run: async () => {
278
+ const { nextItems } = await adapter.nextItems({
279
+ models,
280
+ tools: [],
281
+ systemPrompt: 'Act as a Bot',
282
+ prevItems: [
283
+ {
284
+ type: 'humanMessage',
285
+ humanMessage: {
286
+ documents: [
287
+ { id: 'doc1', mimeType: 'application/pdf', base64Url: testPdfBase64Url },
288
+ ],
289
+ },
290
+ },
291
+ ],
292
+ });
293
+ ensure(Array.isArray(nextItems), 'nextItems is not array');
294
+ ensure(nextItems.length === 1, 'nexItems length should be 1');
295
+ ensure(nextItems[0].type === 'botMessage', 'next item should have botMessage type');
296
+ },
297
+ },
298
+ {
299
+ name: 'throws when human message has no content',
300
+ run: async () => {
301
+ const responsePromise = adapter.nextItems({
302
+ models,
303
+ tools: [],
304
+ systemPrompt: 'Act as a Bot',
305
+ prevItems: [{ type: 'humanMessage', humanMessage: {} }],
306
+ });
307
+ await ensureRejects(responsePromise, 'adapter should reject an empty human message');
308
+ },
309
+ },
310
+ {
311
+ name: 'consume object-only human message',
312
+ run: async () => {
313
+ const { nextItems } = await adapter.nextItems({
314
+ models,
315
+ tools: [],
316
+ systemPrompt: 'Act as a Bot',
317
+ prevItems: [
318
+ {
319
+ type: 'humanMessage',
320
+ humanMessage: { object: { user: 'Jorge', country: 'Colombia' } },
321
+ },
322
+ ],
323
+ });
324
+ ensure(Array.isArray(nextItems), 'nextItems is not array');
325
+ ensure(nextItems.length === 1, 'nexItems length should be 1');
326
+ ensure(nextItems[0].type === 'botMessage', 'next item should have botMessage type');
327
+ },
328
+ },
329
+ {
330
+ name: 'skips unsupported image format without crashing',
331
+ run: async () => {
332
+ const { nextItems } = await adapter.nextItems({
333
+ models,
334
+ tools: [],
335
+ systemPrompt: 'Act as a Bot',
336
+ prevItems: [
337
+ {
338
+ type: 'humanMessage',
339
+ humanMessage: {
340
+ text: 'Describe what I sent',
341
+ images: [
342
+ {
343
+ id: 'image1',
344
+ mimeType: 'image/bmp',
345
+ publicUrl: 'https://example.com/sample.bmp',
346
+ },
347
+ ],
348
+ },
349
+ },
350
+ ],
351
+ });
352
+ ensure(Array.isArray(nextItems), 'nextItems is not array');
353
+ ensure(nextItems.length === 1, 'nexItems length should be 1');
354
+ ensure(nextItems[0].type === 'botMessage', 'next item should have botMessage type');
355
+ },
356
+ },
357
+ {
358
+ name: 'call tool with array-of-string parameter',
359
+ run: async () => {
360
+ const tools = [
361
+ {
362
+ language: 'english',
363
+ name: 'lookupCountries',
364
+ description: 'Look up information about a list of countries',
365
+ parameters: [
366
+ {
367
+ name: 'countryCodes',
368
+ required: true,
369
+ schema: {
370
+ type: 'array',
371
+ description: 'ISO codes of the countries to look up',
372
+ items: { type: 'string' },
373
+ },
374
+ },
375
+ ],
376
+ },
377
+ ];
378
+ const { nextItems } = await adapter.nextItems({
379
+ models,
380
+ tools,
381
+ systemPrompt: 'Act as a Bot. Always call lookupCountries when the user mentions multiple countries.',
382
+ prevItems: [
383
+ {
384
+ type: 'humanMessage',
385
+ humanMessage: { text: 'Look up info for Colombia, Argentina and Brazil' },
386
+ },
387
+ ],
388
+ });
389
+ const functionCallItem = nextItems.find((x) => x.type === 'functionCall');
390
+ ensure(functionCallItem != null, 'nextItems should contain a functionCall item');
391
+ ensure(functionCallItem.functionCall.name === 'lookupCountries', 'function call should target lookupCountries');
392
+ const args = JSON.parse(functionCallItem.functionCall.arguments ?? '{}');
393
+ ensure(Array.isArray(args.countryCodes), 'countryCodes should be an array');
394
+ ensure(args.countryCodes.length >= 2, 'countryCodes should contain multiple entries');
395
+ for (const code of args.countryCodes) {
396
+ ensure(typeof code === 'string', 'each countryCodes item should be a string');
397
+ }
398
+ },
399
+ },
400
+ {
401
+ name: 'call tool with array-of-number parameter',
402
+ run: async () => {
403
+ const tools = [
404
+ {
405
+ language: 'english',
406
+ name: 'sumNumbers',
407
+ description: 'Compute the sum of a list of numbers',
408
+ parameters: [
409
+ {
410
+ name: 'numbers',
411
+ required: true,
412
+ schema: {
413
+ type: 'array',
414
+ description: 'Numbers to add together',
415
+ items: { type: 'number' },
416
+ },
417
+ },
418
+ ],
419
+ },
420
+ ];
421
+ const { nextItems } = await adapter.nextItems({
422
+ models,
423
+ tools,
424
+ systemPrompt: 'Act as a Bot. Always use sumNumbers to add lists of numbers.',
425
+ prevItems: [
426
+ {
427
+ type: 'humanMessage',
428
+ humanMessage: { text: 'What is the sum of 3, 5, 7 and 11?' },
429
+ },
430
+ ],
431
+ });
432
+ const functionCallItem = nextItems.find((x) => x.type === 'functionCall');
433
+ ensure(functionCallItem != null, 'nextItems should contain a functionCall item');
434
+ ensure(functionCallItem.functionCall.name === 'sumNumbers', 'function call should target sumNumbers');
435
+ const args = JSON.parse(functionCallItem.functionCall.arguments ?? '{}');
436
+ ensure(Array.isArray(args.numbers), 'numbers should be an array');
437
+ ensure(args.numbers.length >= 2, 'numbers should contain multiple entries');
438
+ for (const value of args.numbers) {
439
+ ensure(typeof value === 'number', 'each numbers item should be a number');
440
+ }
441
+ },
442
+ },
443
+ {
444
+ name: 'call tool with multiple mixed-type parameters',
445
+ run: async () => {
446
+ const tools = [
447
+ {
448
+ language: 'english',
449
+ name: 'scheduleMeeting',
450
+ description: 'Schedule a meeting on the calendar',
451
+ parameters: [
452
+ {
453
+ name: 'title',
454
+ required: true,
455
+ schema: { type: 'string', description: 'meeting title' },
456
+ },
457
+ {
458
+ name: 'durationMinutes',
459
+ required: true,
460
+ schema: { type: 'number', description: 'duration in minutes' },
461
+ },
462
+ {
463
+ name: 'attendees',
464
+ required: true,
465
+ schema: {
466
+ type: 'array',
467
+ description: 'list of attendee emails',
468
+ items: { type: 'string' },
469
+ },
470
+ },
471
+ ],
472
+ },
473
+ ];
474
+ const { nextItems } = await adapter.nextItems({
475
+ models,
476
+ tools,
477
+ systemPrompt: 'Act as a Bot. Always use scheduleMeeting when asked to schedule a meeting.',
478
+ prevItems: [
479
+ {
480
+ type: 'humanMessage',
481
+ humanMessage: {
482
+ text: 'Schedule a 30 minute meeting titled "Quarterly Review" with alice@x.com and bob@x.com',
483
+ },
484
+ },
485
+ ],
486
+ });
487
+ const functionCallItem = nextItems.find((x) => x.type === 'functionCall');
488
+ ensure(functionCallItem != null, 'nextItems should contain a functionCall item');
489
+ ensure(functionCallItem.functionCall.name === 'scheduleMeeting', 'function call should target scheduleMeeting');
490
+ const args = JSON.parse(functionCallItem.functionCall.arguments ?? '{}');
491
+ ensure(typeof args.title === 'string' && args.title.length > 0, 'title should be a string');
492
+ ensure(typeof args.durationMinutes === 'number' && args.durationMinutes > 0, 'durationMinutes should be a positive number');
493
+ ensure(Array.isArray(args.attendees), 'attendees should be an array');
494
+ ensure(args.attendees.length >= 2, 'attendees should contain multiple entries');
495
+ for (const attendee of args.attendees) {
496
+ ensure(typeof attendee === 'string', 'each attendee should be a string');
497
+ }
498
+ },
499
+ },
500
+ {
501
+ name: 'call tool with enum parameter',
502
+ run: async () => {
503
+ const tools = [
504
+ {
505
+ language: 'english',
506
+ name: 'setTaskPriority',
507
+ description: 'Set the priority of the current task. The level must be one of "low", "medium" or "high".',
508
+ parameters: [
509
+ {
510
+ name: 'level',
511
+ required: true,
512
+ schema: {
513
+ type: 'string',
514
+ description: 'priority level, one of low, medium or high',
515
+ enum: ['low', 'medium', 'high'],
516
+ },
517
+ },
518
+ ],
519
+ },
520
+ ];
521
+ const { nextItems } = await adapter.nextItems({
522
+ models,
523
+ tools,
524
+ systemPrompt: 'Act as a Bot. You MUST call the setTaskPriority tool whenever the user wants to change a task priority. Do not respond with plain text in that case.',
525
+ prevItems: [
526
+ {
527
+ type: 'humanMessage',
528
+ humanMessage: {
529
+ text: 'Use the setTaskPriority tool to mark this task as the highest priority.',
530
+ },
531
+ },
532
+ ],
533
+ });
534
+ const functionCallItem = nextItems.find((x) => x.type === 'functionCall');
535
+ ensure(functionCallItem != null, 'nextItems should contain a functionCall item');
536
+ ensure(functionCallItem.functionCall.name === 'setTaskPriority', 'function call should target setTaskPriority');
537
+ const args = JSON.parse(functionCallItem.functionCall.arguments ?? '{}');
538
+ ensure(['low', 'medium', 'high'].includes(args.level), `level should be one of low/medium/high, got '${args.level}'`);
539
+ },
540
+ },
541
+ {
542
+ name: 'call tool with nested object parameter',
543
+ run: async () => {
544
+ const tools = [
545
+ {
546
+ language: 'english',
547
+ name: 'registerUser',
548
+ description: 'Register a new user',
549
+ parameters: [
550
+ {
551
+ name: 'user',
552
+ required: true,
553
+ schema: {
554
+ type: 'object',
555
+ description: 'user information',
556
+ properties: {
557
+ name: { type: 'string', description: 'full name' },
558
+ age: { type: 'number', description: 'age in years' },
559
+ },
560
+ required: ['name', 'age'],
561
+ additionalProperties: false,
562
+ },
563
+ },
564
+ ],
565
+ },
566
+ ];
567
+ const { nextItems } = await adapter.nextItems({
568
+ models,
569
+ tools,
570
+ systemPrompt: 'Act as a Bot. Always use registerUser when asked to register someone.',
571
+ prevItems: [
572
+ {
573
+ type: 'humanMessage',
574
+ humanMessage: { text: 'Register user Jorge Narvaez, 30 years old' },
575
+ },
576
+ ],
577
+ });
578
+ const functionCallItem = nextItems.find((x) => x.type === 'functionCall');
579
+ ensure(functionCallItem != null, 'nextItems should contain a functionCall item');
580
+ ensure(functionCallItem.functionCall.name === 'registerUser', 'function call should target registerUser');
581
+ const args = JSON.parse(functionCallItem.functionCall.arguments ?? '{}');
582
+ ensure(args.user != null && typeof args.user === 'object', 'user should be an object');
583
+ ensure(typeof args.user.name === 'string', 'user.name should be a string');
584
+ ensure(typeof args.user.age === 'number', 'user.age should be a number');
585
+ },
586
+ },
587
+ {
588
+ name: 'call tool with only required parameter omitting the optional one',
589
+ run: async () => {
590
+ const tools = [
591
+ {
592
+ language: 'english',
593
+ name: 'searchProducts',
594
+ description: 'Search the catalog of products',
595
+ parameters: [
596
+ {
597
+ name: 'query',
598
+ required: true,
599
+ schema: { type: 'string', description: 'search keywords' },
600
+ },
601
+ {
602
+ name: 'limit',
603
+ required: false,
604
+ schema: { type: 'number', description: 'maximum number of results' },
605
+ },
606
+ ],
607
+ },
608
+ ];
609
+ const { nextItems } = await adapter.nextItems({
610
+ models,
611
+ tools,
612
+ systemPrompt: 'Act as a Bot. Always use searchProducts when the user wants to search.',
613
+ prevItems: [
614
+ { type: 'humanMessage', humanMessage: { text: 'Search for red running shoes' } },
615
+ ],
616
+ });
617
+ const functionCallItem = nextItems.find((x) => x.type === 'functionCall');
618
+ ensure(functionCallItem != null, 'nextItems should contain a functionCall item');
619
+ ensure(functionCallItem.functionCall.name === 'searchProducts', 'function call should target searchProducts');
620
+ const args = JSON.parse(functionCallItem.functionCall.arguments ?? '{}');
621
+ ensure(typeof args.query === 'string' && args.query.length > 0, 'query should be a string');
622
+ },
623
+ },
624
+ {
625
+ name: 'call tool with no parameters',
626
+ run: async () => {
627
+ const tools = [
628
+ {
629
+ language: 'english',
630
+ name: 'getCurrentServerTime',
631
+ description: 'Get the authoritative current server time as an ISO 8601 string. This is the ONLY way to obtain the current time; the model does not know it. Always call this tool when the user asks for the current time.',
632
+ parameters: [],
633
+ },
634
+ ];
635
+ const { nextItems } = await adapter.nextItems({
636
+ models,
637
+ tools,
638
+ systemPrompt: 'Act as a Bot. You do NOT know the current time and have no way to compute it. The ONLY way to learn the current time is to call the getCurrentServerTime tool. Whenever the user asks anything related to the current time, you MUST call getCurrentServerTime. Do not answer with text in that case.',
639
+ prevItems: [
640
+ {
641
+ type: 'humanMessage',
642
+ humanMessage: {
643
+ text: 'Invoke the getCurrentServerTime tool right now to retrieve the current server time and tell me what it returns.',
644
+ },
645
+ },
646
+ ],
647
+ });
648
+ const functionCallItem = nextItems.find((x) => x.type === 'functionCall');
649
+ ensure(functionCallItem != null, 'nextItems should contain a functionCall item');
650
+ ensure(functionCallItem.functionCall.name === 'getCurrentServerTime', 'function call should target getCurrentServerTime');
651
+ },
652
+ },
653
+ ];
654
+ }
655
+
656
+ export { chatAdapterConformanceCases };