@ai-sdk/xai 3.0.32 → 3.0.34

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,1805 +0,0 @@
1
- import { LanguageModelV3Prompt } from '@ai-sdk/provider';
2
- import { describe, it, expect, vi } from 'vitest';
3
- import { createTestServer } from '@ai-sdk/test-server/with-vitest';
4
- import { convertReadableStreamToArray } from '@ai-sdk/provider-utils/test';
5
- import { XaiChatLanguageModel } from './xai-chat-language-model';
6
- import { createXai } from './xai-provider';
7
-
8
- const TEST_PROMPT: LanguageModelV3Prompt = [
9
- { role: 'user', content: [{ type: 'text', text: 'Hello' }] },
10
- ];
11
-
12
- vi.mock('./version', () => ({
13
- VERSION: '0.0.0-test',
14
- }));
15
-
16
- const testConfig = {
17
- provider: 'xai.chat',
18
- baseURL: 'https://api.x.ai/v1',
19
- headers: () => ({ authorization: 'Bearer test-api-key' }),
20
- generateId: () => 'test-id',
21
- };
22
-
23
- const model = new XaiChatLanguageModel('grok-beta', testConfig);
24
-
25
- const server = createTestServer({
26
- 'https://api.x.ai/v1/chat/completions': {},
27
- });
28
-
29
- describe('XaiChatLanguageModel', () => {
30
- it('should be instantiated correctly', () => {
31
- expect(model.modelId).toBe('grok-beta');
32
- expect(model.provider).toBe('xai.chat');
33
- expect(model.specificationVersion).toBe('v3');
34
- });
35
-
36
- it('should have supported URLs', () => {
37
- expect(model.supportedUrls).toEqual({
38
- 'image/*': [/^https?:\/\/.*$/],
39
- });
40
- });
41
-
42
- describe('doGenerate', () => {
43
- function prepareJsonResponse({
44
- content = '',
45
- usage = {
46
- prompt_tokens: 4,
47
- total_tokens: 34,
48
- completion_tokens: 30,
49
- },
50
- id = 'chatcmpl-test-id',
51
- created = 1699472111,
52
- model = 'grok-beta',
53
- headers,
54
- }: {
55
- content?: string;
56
- usage?: {
57
- prompt_tokens: number;
58
- total_tokens: number;
59
- completion_tokens: number;
60
- };
61
- id?: string;
62
- created?: number;
63
- model?: string;
64
- headers?: Record<string, string>;
65
- }) {
66
- server.urls['https://api.x.ai/v1/chat/completions'].response = {
67
- type: 'json-value',
68
- headers,
69
- body: {
70
- id,
71
- object: 'chat.completion',
72
- created,
73
- model,
74
- choices: [
75
- {
76
- index: 0,
77
- message: {
78
- role: 'assistant',
79
- content,
80
- tool_calls: null,
81
- },
82
- finish_reason: 'stop',
83
- },
84
- ],
85
- usage,
86
- },
87
- };
88
- }
89
-
90
- it('should extract text content', async () => {
91
- prepareJsonResponse({ content: 'Hello, World!' });
92
-
93
- const { content } = await model.doGenerate({
94
- prompt: TEST_PROMPT,
95
- });
96
-
97
- expect(content).toMatchInlineSnapshot(`
98
- [
99
- {
100
- "text": "Hello, World!",
101
- "type": "text",
102
- },
103
- ]
104
- `);
105
- });
106
-
107
- it('should avoid duplication when there is a trailing assistant message', async () => {
108
- prepareJsonResponse({ content: 'prefix and more content' });
109
-
110
- const { content } = await model.doGenerate({
111
- prompt: [
112
- { role: 'user', content: [{ type: 'text', text: 'Hello' }] },
113
- {
114
- role: 'assistant',
115
- content: [{ type: 'text', text: 'prefix ' }],
116
- },
117
- ],
118
- });
119
-
120
- expect(content).toMatchInlineSnapshot(`
121
- [
122
- {
123
- "text": "prefix and more content",
124
- "type": "text",
125
- },
126
- ]
127
- `);
128
- });
129
-
130
- it('should extract tool call content', async () => {
131
- server.urls['https://api.x.ai/v1/chat/completions'].response = {
132
- type: 'json-value',
133
- body: {
134
- id: 'chatcmpl-test-tool-call',
135
- object: 'chat.completion',
136
- created: 1699472111,
137
- model: 'grok-beta',
138
- choices: [
139
- {
140
- index: 0,
141
- message: {
142
- role: 'assistant',
143
- content: null,
144
- tool_calls: [
145
- {
146
- id: 'call_test123',
147
- type: 'function',
148
- function: {
149
- name: 'weatherTool',
150
- arguments: '{"location": "paris"}',
151
- },
152
- },
153
- ],
154
- },
155
- finish_reason: 'tool_calls',
156
- },
157
- ],
158
- usage: {
159
- prompt_tokens: 124,
160
- total_tokens: 146,
161
- completion_tokens: 22,
162
- },
163
- },
164
- };
165
-
166
- const { content } = await model.doGenerate({
167
- prompt: TEST_PROMPT,
168
- });
169
-
170
- expect(content).toMatchInlineSnapshot(`
171
- [
172
- {
173
- "input": "{"location": "paris"}",
174
- "toolCallId": "call_test123",
175
- "toolName": "weatherTool",
176
- "type": "tool-call",
177
- },
178
- ]
179
- `);
180
- });
181
-
182
- it('should extract usage', async () => {
183
- prepareJsonResponse({
184
- usage: { prompt_tokens: 20, total_tokens: 25, completion_tokens: 5 },
185
- });
186
-
187
- const { usage } = await model.doGenerate({
188
- prompt: TEST_PROMPT,
189
- });
190
-
191
- expect(usage).toMatchInlineSnapshot(`
192
- {
193
- "inputTokens": {
194
- "cacheRead": 0,
195
- "cacheWrite": undefined,
196
- "noCache": 20,
197
- "total": 20,
198
- },
199
- "outputTokens": {
200
- "reasoning": 0,
201
- "text": 5,
202
- "total": 5,
203
- },
204
- "raw": {
205
- "completion_tokens": 5,
206
- "prompt_tokens": 20,
207
- "total_tokens": 25,
208
- },
209
- }
210
- `);
211
- });
212
-
213
- it('should send additional response information', async () => {
214
- prepareJsonResponse({
215
- id: 'test-id',
216
- created: 123,
217
- model: 'test-model',
218
- });
219
-
220
- const { response } = await model.doGenerate({
221
- prompt: TEST_PROMPT,
222
- });
223
-
224
- expect({
225
- id: response?.id,
226
- timestamp: response?.timestamp,
227
- modelId: response?.modelId,
228
- }).toStrictEqual({
229
- id: 'test-id',
230
- timestamp: new Date(123 * 1000),
231
- modelId: 'test-model',
232
- });
233
- });
234
-
235
- it('should expose the raw response headers', async () => {
236
- prepareJsonResponse({
237
- headers: { 'test-header': 'test-value' },
238
- });
239
-
240
- const { response } = await model.doGenerate({
241
- prompt: TEST_PROMPT,
242
- });
243
-
244
- expect(response?.headers).toStrictEqual({
245
- // default headers:
246
- 'content-length': '271',
247
- 'content-type': 'application/json',
248
-
249
- // custom header
250
- 'test-header': 'test-value',
251
- });
252
- });
253
-
254
- it('should pass the model and the messages', async () => {
255
- prepareJsonResponse({ content: '' });
256
-
257
- await model.doGenerate({
258
- prompt: TEST_PROMPT,
259
- });
260
-
261
- expect(await server.calls[0].requestBodyJson).toStrictEqual({
262
- model: 'grok-beta',
263
- messages: [{ role: 'user', content: 'Hello' }],
264
- });
265
- });
266
-
267
- it('should pass tools and toolChoice', async () => {
268
- prepareJsonResponse({ content: '' });
269
-
270
- await model.doGenerate({
271
- tools: [
272
- {
273
- type: 'function',
274
- name: 'test-tool',
275
- inputSchema: {
276
- type: 'object',
277
- properties: { value: { type: 'string' } },
278
- required: ['value'],
279
- additionalProperties: false,
280
- $schema: 'http://json-schema.org/draft-07/schema#',
281
- },
282
- },
283
- ],
284
- toolChoice: {
285
- type: 'tool',
286
- toolName: 'test-tool',
287
- },
288
- prompt: TEST_PROMPT,
289
- });
290
-
291
- expect(await server.calls[0].requestBodyJson).toStrictEqual({
292
- model: 'grok-beta',
293
- messages: [{ role: 'user', content: 'Hello' }],
294
- tools: [
295
- {
296
- type: 'function',
297
- function: {
298
- name: 'test-tool',
299
- parameters: {
300
- type: 'object',
301
- properties: { value: { type: 'string' } },
302
- required: ['value'],
303
- additionalProperties: false,
304
- $schema: 'http://json-schema.org/draft-07/schema#',
305
- },
306
- },
307
- },
308
- ],
309
- tool_choice: {
310
- type: 'function',
311
- function: { name: 'test-tool' },
312
- },
313
- });
314
- });
315
-
316
- it('should pass parallel_function_calling provider option', async () => {
317
- prepareJsonResponse({ content: '' });
318
-
319
- await model.doGenerate({
320
- prompt: TEST_PROMPT,
321
- providerOptions: {
322
- xai: {
323
- parallel_function_calling: false,
324
- },
325
- },
326
- });
327
-
328
- expect(await server.calls[0].requestBodyJson).toMatchObject({
329
- model: 'grok-beta',
330
- messages: [{ role: 'user', content: 'Hello' }],
331
- parallel_function_calling: false,
332
- });
333
- });
334
-
335
- it('should pass headers', async () => {
336
- prepareJsonResponse({ content: '' });
337
-
338
- const modelWithHeaders = new XaiChatLanguageModel('grok-beta', {
339
- provider: 'xai.chat',
340
- baseURL: 'https://api.x.ai/v1',
341
- headers: () => ({
342
- authorization: 'Bearer test-api-key',
343
- 'Custom-Provider-Header': 'provider-header-value',
344
- }),
345
-
346
- generateId: () => 'test-id',
347
- });
348
-
349
- await modelWithHeaders.doGenerate({
350
- prompt: TEST_PROMPT,
351
- headers: {
352
- 'Custom-Request-Header': 'request-header-value',
353
- },
354
- });
355
-
356
- const requestHeaders = server.calls[0].requestHeaders;
357
-
358
- expect(requestHeaders).toStrictEqual({
359
- authorization: 'Bearer test-api-key',
360
- 'content-type': 'application/json',
361
- 'custom-provider-header': 'provider-header-value',
362
- 'custom-request-header': 'request-header-value',
363
- });
364
- });
365
-
366
- it('should include provider user agent when using createXai', async () => {
367
- prepareJsonResponse({ content: '' });
368
-
369
- const xai = createXai({
370
- apiKey: 'test-api-key',
371
- headers: { 'Custom-Provider-Header': 'provider-header-value' },
372
- });
373
-
374
- const modelWithHeaders = xai.chat('grok-beta');
375
-
376
- await modelWithHeaders.doGenerate({
377
- prompt: TEST_PROMPT,
378
- headers: { 'Custom-Request-Header': 'request-header-value' },
379
- });
380
-
381
- expect(server.calls[0].requestUserAgent).toContain(
382
- `ai-sdk/xai/0.0.0-test`,
383
- );
384
- });
385
-
386
- it('should send request body', async () => {
387
- prepareJsonResponse({ content: '' });
388
-
389
- const { request } = await model.doGenerate({
390
- prompt: TEST_PROMPT,
391
- });
392
-
393
- expect(request).toMatchInlineSnapshot(`
394
- {
395
- "body": {
396
- "max_completion_tokens": undefined,
397
- "messages": [
398
- {
399
- "content": "Hello",
400
- "role": "user",
401
- },
402
- ],
403
- "model": "grok-beta",
404
- "parallel_function_calling": undefined,
405
- "reasoning_effort": undefined,
406
- "response_format": undefined,
407
- "search_parameters": undefined,
408
- "seed": undefined,
409
- "temperature": undefined,
410
- "tool_choice": undefined,
411
- "tools": undefined,
412
- "top_p": undefined,
413
- },
414
- }
415
- `);
416
- });
417
-
418
- it('should pass search parameters', async () => {
419
- prepareJsonResponse({ content: '' });
420
-
421
- await model.doGenerate({
422
- prompt: TEST_PROMPT,
423
- providerOptions: {
424
- xai: {
425
- searchParameters: {
426
- mode: 'auto',
427
- returnCitations: true,
428
- fromDate: '2024-01-01',
429
- toDate: '2024-12-31',
430
- maxSearchResults: 10,
431
- },
432
- },
433
- },
434
- });
435
-
436
- expect(await server.calls[0].requestBodyJson).toStrictEqual({
437
- model: 'grok-beta',
438
- messages: [{ role: 'user', content: 'Hello' }],
439
- search_parameters: {
440
- mode: 'auto',
441
- return_citations: true,
442
- from_date: '2024-01-01',
443
- to_date: '2024-12-31',
444
- max_search_results: 10,
445
- },
446
- });
447
- });
448
-
449
- it('should pass search parameters with sources array', async () => {
450
- prepareJsonResponse({ content: '' });
451
-
452
- await model.doGenerate({
453
- prompt: TEST_PROMPT,
454
- providerOptions: {
455
- xai: {
456
- searchParameters: {
457
- mode: 'on',
458
- sources: [
459
- {
460
- type: 'web',
461
- country: 'US',
462
- excludedWebsites: ['example.com'],
463
- safeSearch: false,
464
- },
465
- {
466
- type: 'x',
467
- includedXHandles: ['grok'],
468
- excludedXHandles: ['openai'],
469
- postFavoriteCount: 5,
470
- postViewCount: 50,
471
- },
472
- {
473
- type: 'news',
474
- country: 'GB',
475
- },
476
- {
477
- type: 'rss',
478
- links: ['https://status.x.ai/feed.xml'],
479
- },
480
- ],
481
- },
482
- },
483
- },
484
- });
485
-
486
- expect(await server.calls[0].requestBodyJson).toStrictEqual({
487
- model: 'grok-beta',
488
- messages: [{ role: 'user', content: 'Hello' }],
489
- search_parameters: {
490
- mode: 'on',
491
- sources: [
492
- {
493
- type: 'web',
494
- country: 'US',
495
- excluded_websites: ['example.com'],
496
- safe_search: false,
497
- },
498
- {
499
- type: 'x',
500
- included_x_handles: ['grok'],
501
- excluded_x_handles: ['openai'],
502
- post_favorite_count: 5,
503
- post_view_count: 50,
504
- },
505
- {
506
- type: 'news',
507
- country: 'GB',
508
- },
509
- {
510
- type: 'rss',
511
- links: ['https://status.x.ai/feed.xml'],
512
- },
513
- ],
514
- },
515
- });
516
- });
517
-
518
- it('should extract content when message content is a content object', async () => {
519
- server.urls['https://api.x.ai/v1/chat/completions'].response = {
520
- type: 'json-value',
521
- body: {
522
- id: 'object-id',
523
- object: 'chat.completion',
524
- created: 1699472111,
525
- model: 'grok-beta',
526
- choices: [
527
- {
528
- index: 0,
529
- message: {
530
- role: 'assistant',
531
- content: 'Hello from object',
532
- tool_calls: null,
533
- },
534
- finish_reason: 'stop',
535
- },
536
- ],
537
- usage: { prompt_tokens: 4, total_tokens: 34, completion_tokens: 30 },
538
- },
539
- };
540
-
541
- const { content } = await model.doGenerate({
542
- prompt: TEST_PROMPT,
543
- });
544
-
545
- expect(content).toMatchInlineSnapshot(`
546
- [
547
- {
548
- "text": "Hello from object",
549
- "type": "text",
550
- },
551
- ]
552
- `);
553
- });
554
-
555
- it('should extract citations as sources', async () => {
556
- server.urls['https://api.x.ai/v1/chat/completions'].response = {
557
- type: 'json-value',
558
- body: {
559
- id: 'citations-test',
560
- object: 'chat.completion',
561
- created: 1699472111,
562
- model: 'grok-beta',
563
- choices: [
564
- {
565
- index: 0,
566
- message: {
567
- role: 'assistant',
568
- content: 'Here are the latest developments in AI.',
569
- tool_calls: null,
570
- },
571
- finish_reason: 'stop',
572
- },
573
- ],
574
- usage: { prompt_tokens: 4, total_tokens: 34, completion_tokens: 30 },
575
- citations: [
576
- 'https://example.com/article1',
577
- 'https://example.com/article2',
578
- ],
579
- },
580
- };
581
-
582
- const { content } = await model.doGenerate({
583
- prompt: TEST_PROMPT,
584
- providerOptions: {
585
- xai: {
586
- searchParameters: {
587
- mode: 'auto',
588
- returnCitations: true,
589
- },
590
- },
591
- },
592
- });
593
-
594
- expect(content).toMatchInlineSnapshot(`
595
- [
596
- {
597
- "text": "Here are the latest developments in AI.",
598
- "type": "text",
599
- },
600
- {
601
- "id": "test-id",
602
- "sourceType": "url",
603
- "type": "source",
604
- "url": "https://example.com/article1",
605
- },
606
- {
607
- "id": "test-id",
608
- "sourceType": "url",
609
- "type": "source",
610
- "url": "https://example.com/article2",
611
- },
612
- ]
613
- `);
614
- });
615
-
616
- it('should handle complex search parameter combinations', async () => {
617
- prepareJsonResponse({
618
- content: 'Research results from multiple sources',
619
- });
620
-
621
- await model.doGenerate({
622
- prompt: TEST_PROMPT,
623
- providerOptions: {
624
- xai: {
625
- searchParameters: {
626
- mode: 'on',
627
- returnCitations: true,
628
- fromDate: '2024-01-01',
629
- toDate: '2024-12-31',
630
- maxSearchResults: 15,
631
- sources: [
632
- {
633
- type: 'web',
634
- country: 'US',
635
- allowedWebsites: ['arxiv.org', 'nature.com'],
636
- safeSearch: true,
637
- },
638
- {
639
- type: 'news',
640
- country: 'GB',
641
- excludedWebsites: ['tabloid.com'],
642
- },
643
- {
644
- type: 'x',
645
- includedXHandles: ['openai', 'deepmind'],
646
- excludedXHandles: ['grok'],
647
- postFavoriteCount: 10,
648
- postViewCount: 100,
649
- },
650
- ],
651
- },
652
- },
653
- },
654
- });
655
-
656
- expect(await server.calls[0].requestBodyJson).toStrictEqual({
657
- model: 'grok-beta',
658
- messages: [{ role: 'user', content: 'Hello' }],
659
- search_parameters: {
660
- mode: 'on',
661
- return_citations: true,
662
- from_date: '2024-01-01',
663
- to_date: '2024-12-31',
664
- max_search_results: 15,
665
- sources: [
666
- {
667
- type: 'web',
668
- country: 'US',
669
- allowed_websites: ['arxiv.org', 'nature.com'],
670
- safe_search: true,
671
- },
672
- {
673
- type: 'news',
674
- country: 'GB',
675
- excluded_websites: ['tabloid.com'],
676
- },
677
- {
678
- type: 'x',
679
- included_x_handles: ['openai', 'deepmind'],
680
- excluded_x_handles: ['grok'],
681
- post_favorite_count: 10,
682
- post_view_count: 100,
683
- },
684
- ],
685
- },
686
- });
687
- });
688
-
689
- it('should handle empty citations array', async () => {
690
- server.urls['https://api.x.ai/v1/chat/completions'].response = {
691
- type: 'json-value',
692
- body: {
693
- id: 'no-citations-test',
694
- object: 'chat.completion',
695
- created: 1699472111,
696
- model: 'grok-beta',
697
- choices: [
698
- {
699
- index: 0,
700
- message: {
701
- role: 'assistant',
702
- content: 'Response without citations.',
703
- tool_calls: null,
704
- },
705
- finish_reason: 'stop',
706
- },
707
- ],
708
- usage: { prompt_tokens: 4, total_tokens: 34, completion_tokens: 30 },
709
- citations: [],
710
- },
711
- };
712
-
713
- const { content } = await model.doGenerate({
714
- prompt: TEST_PROMPT,
715
- providerOptions: {
716
- xai: {
717
- searchParameters: {
718
- mode: 'auto',
719
- returnCitations: true,
720
- },
721
- },
722
- },
723
- });
724
-
725
- expect(content).toMatchInlineSnapshot(`
726
- [
727
- {
728
- "text": "Response without citations.",
729
- "type": "text",
730
- },
731
- ]
732
- `);
733
- });
734
-
735
- it('should support json schema response format without warnings', async () => {
736
- prepareJsonResponse({ content: '{"name":"john doe"}' });
737
-
738
- const { warnings } = await model.doGenerate({
739
- prompt: TEST_PROMPT,
740
- responseFormat: {
741
- type: 'json',
742
- schema: {
743
- type: 'object',
744
- properties: {
745
- name: { type: 'string' },
746
- },
747
- required: ['name'],
748
- additionalProperties: false,
749
- },
750
- },
751
- });
752
-
753
- expect(warnings).toEqual([]);
754
- });
755
-
756
- it('should send json schema in response format', async () => {
757
- prepareJsonResponse({ content: '{"name":"john"}' });
758
-
759
- await model.doGenerate({
760
- prompt: TEST_PROMPT,
761
- responseFormat: {
762
- type: 'json',
763
- name: 'person',
764
- schema: {
765
- type: 'object',
766
- properties: {
767
- name: { type: 'string' },
768
- },
769
- required: ['name'],
770
- },
771
- },
772
- });
773
-
774
- expect(await server.calls[0].requestBodyJson).toMatchObject({
775
- model: 'grok-beta',
776
- response_format: {
777
- type: 'json_schema',
778
- json_schema: {
779
- name: 'person',
780
- schema: {
781
- type: 'object',
782
- properties: {
783
- name: { type: 'string' },
784
- },
785
- required: ['name'],
786
- },
787
- strict: true,
788
- },
789
- },
790
- });
791
- });
792
- });
793
-
794
- describe('doStream', () => {
795
- function prepareStreamResponse({
796
- content,
797
- headers,
798
- }: {
799
- content: string[];
800
- headers?: Record<string, string>;
801
- }) {
802
- server.urls['https://api.x.ai/v1/chat/completions'].response = {
803
- type: 'stream-chunks',
804
- headers,
805
- chunks: [
806
- `data: {"id":"35e18f56-4ec6-48e4-8ca0-c1c4cbeeebbe","object":"chat.completion.chunk",` +
807
- `"created":1750537778,"model":"grok-beta","choices":[{"index":0,` +
808
- `"delta":{"role":"assistant","content":""},"finish_reason":null}],"system_fingerprint":"fp_13a6dc65a6"}\n\n`,
809
- ...content.map(text => {
810
- return (
811
- `data: {"id":"35e18f56-4ec6-48e4-8ca0-c1c4cbeeebbe","object":"chat.completion.chunk",` +
812
- `"created":1750537778,"model":"grok-beta","choices":[{"index":0,` +
813
- `"delta":{"role":"assistant","content":"${text}"},"finish_reason":null}],"system_fingerprint":"fp_13a6dc65a6"}\n\n`
814
- );
815
- }),
816
- `data: {"id":"35e18f56-4ec6-48e4-8ca0-c1c4cbeeebbe","object":"chat.completion.chunk",` +
817
- `"created":1750537778,"model":"grok-beta","choices":[{"index":0,` +
818
- `"delta":{"content":""},"finish_reason":"stop"}],` +
819
- `"usage":{"prompt_tokens":4,"total_tokens":36,"completion_tokens":32},"system_fingerprint":"fp_13a6dc65a6"}\n\n`,
820
- `data: [DONE]\n\n`,
821
- ],
822
- };
823
- }
824
-
825
- it('should stream text deltas', async () => {
826
- prepareStreamResponse({ content: ['Hello', ', ', 'world!'] });
827
-
828
- const { stream } = await model.doStream({
829
- prompt: TEST_PROMPT,
830
- includeRawChunks: false,
831
- });
832
-
833
- expect(await convertReadableStreamToArray(stream)).toMatchInlineSnapshot(`
834
- [
835
- {
836
- "type": "stream-start",
837
- "warnings": [],
838
- },
839
- {
840
- "id": "35e18f56-4ec6-48e4-8ca0-c1c4cbeeebbe",
841
- "modelId": "grok-beta",
842
- "timestamp": 2025-06-21T20:29:38.000Z,
843
- "type": "response-metadata",
844
- },
845
- {
846
- "id": "text-35e18f56-4ec6-48e4-8ca0-c1c4cbeeebbe",
847
- "type": "text-start",
848
- },
849
- {
850
- "delta": "Hello",
851
- "id": "text-35e18f56-4ec6-48e4-8ca0-c1c4cbeeebbe",
852
- "type": "text-delta",
853
- },
854
- {
855
- "delta": ", ",
856
- "id": "text-35e18f56-4ec6-48e4-8ca0-c1c4cbeeebbe",
857
- "type": "text-delta",
858
- },
859
- {
860
- "delta": "world!",
861
- "id": "text-35e18f56-4ec6-48e4-8ca0-c1c4cbeeebbe",
862
- "type": "text-delta",
863
- },
864
- {
865
- "id": "text-35e18f56-4ec6-48e4-8ca0-c1c4cbeeebbe",
866
- "type": "text-end",
867
- },
868
- {
869
- "finishReason": {
870
- "raw": "stop",
871
- "unified": "stop",
872
- },
873
- "type": "finish",
874
- "usage": {
875
- "inputTokens": {
876
- "cacheRead": 0,
877
- "cacheWrite": undefined,
878
- "noCache": 4,
879
- "total": 4,
880
- },
881
- "outputTokens": {
882
- "reasoning": 0,
883
- "text": 32,
884
- "total": 32,
885
- },
886
- "raw": {
887
- "completion_tokens": 32,
888
- "prompt_tokens": 4,
889
- "total_tokens": 36,
890
- },
891
- },
892
- },
893
- ]
894
- `);
895
- });
896
-
897
- it('should avoid duplication when there is a trailing assistant message', async () => {
898
- prepareStreamResponse({ content: ['prefix', ' and', ' more content'] });
899
-
900
- const { stream } = await model.doStream({
901
- prompt: [
902
- { role: 'user', content: [{ type: 'text', text: 'Hello' }] },
903
- {
904
- role: 'assistant',
905
- content: [{ type: 'text', text: 'prefix ' }],
906
- },
907
- ],
908
- includeRawChunks: false,
909
- });
910
-
911
- expect(await convertReadableStreamToArray(stream)).toMatchInlineSnapshot(`
912
- [
913
- {
914
- "type": "stream-start",
915
- "warnings": [],
916
- },
917
- {
918
- "id": "35e18f56-4ec6-48e4-8ca0-c1c4cbeeebbe",
919
- "modelId": "grok-beta",
920
- "timestamp": 2025-06-21T20:29:38.000Z,
921
- "type": "response-metadata",
922
- },
923
- {
924
- "id": "text-35e18f56-4ec6-48e4-8ca0-c1c4cbeeebbe",
925
- "type": "text-start",
926
- },
927
- {
928
- "delta": "prefix",
929
- "id": "text-35e18f56-4ec6-48e4-8ca0-c1c4cbeeebbe",
930
- "type": "text-delta",
931
- },
932
- {
933
- "delta": " and",
934
- "id": "text-35e18f56-4ec6-48e4-8ca0-c1c4cbeeebbe",
935
- "type": "text-delta",
936
- },
937
- {
938
- "delta": " more content",
939
- "id": "text-35e18f56-4ec6-48e4-8ca0-c1c4cbeeebbe",
940
- "type": "text-delta",
941
- },
942
- {
943
- "id": "text-35e18f56-4ec6-48e4-8ca0-c1c4cbeeebbe",
944
- "type": "text-end",
945
- },
946
- {
947
- "finishReason": {
948
- "raw": "stop",
949
- "unified": "stop",
950
- },
951
- "type": "finish",
952
- "usage": {
953
- "inputTokens": {
954
- "cacheRead": 0,
955
- "cacheWrite": undefined,
956
- "noCache": 4,
957
- "total": 4,
958
- },
959
- "outputTokens": {
960
- "reasoning": 0,
961
- "text": 32,
962
- "total": 32,
963
- },
964
- "raw": {
965
- "completion_tokens": 32,
966
- "prompt_tokens": 4,
967
- "total_tokens": 36,
968
- },
969
- },
970
- },
971
- ]
972
- `);
973
- });
974
-
975
- it('should stream tool deltas', async () => {
976
- server.urls['https://api.x.ai/v1/chat/completions'].response = {
977
- type: 'stream-chunks',
978
- chunks: [
979
- `data: {"id":"a9648117-740c-4270-9e07-6a8457f23b7a","object":"chat.completion.chunk","created":1750535985,"model":"grok-beta",` +
980
- `"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}],"system_fingerprint":"fp_13a6dc65a6"}\n\n`,
981
- `data: {"id":"a9648117-740c-4270-9e07-6a8457f23b7a","object":"chat.completion.chunk","created":1750535985,"model":"grok-beta",` +
982
- `"choices":[{"index":0,"delta":{"content":null,"tool_calls":[{"id":"call_yfBEybNYi","type":"function","function":{"name":"test-tool","arguments":` +
983
- `"{\\"value\\":\\"Sparkle Day\\"}"` +
984
- `}}]},"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":183,"total_tokens":316,"completion_tokens":133},"system_fingerprint":"fp_13a6dc65a6"}\n\n`,
985
- 'data: [DONE]\n\n',
986
- ],
987
- };
988
-
989
- const { stream } = await model.doStream({
990
- tools: [
991
- {
992
- type: 'function',
993
- name: 'test-tool',
994
- inputSchema: {
995
- type: 'object',
996
- properties: { value: { type: 'string' } },
997
- required: ['value'],
998
- additionalProperties: false,
999
- $schema: 'http://json-schema.org/draft-07/schema#',
1000
- },
1001
- },
1002
- ],
1003
- prompt: TEST_PROMPT,
1004
- includeRawChunks: false,
1005
- });
1006
-
1007
- expect(await convertReadableStreamToArray(stream)).toMatchInlineSnapshot(`
1008
- [
1009
- {
1010
- "type": "stream-start",
1011
- "warnings": [],
1012
- },
1013
- {
1014
- "id": "a9648117-740c-4270-9e07-6a8457f23b7a",
1015
- "modelId": "grok-beta",
1016
- "timestamp": 2025-06-21T19:59:45.000Z,
1017
- "type": "response-metadata",
1018
- },
1019
- {
1020
- "id": "call_yfBEybNYi",
1021
- "toolName": "test-tool",
1022
- "type": "tool-input-start",
1023
- },
1024
- {
1025
- "delta": "{"value":"Sparkle Day"}",
1026
- "id": "call_yfBEybNYi",
1027
- "type": "tool-input-delta",
1028
- },
1029
- {
1030
- "id": "call_yfBEybNYi",
1031
- "type": "tool-input-end",
1032
- },
1033
- {
1034
- "input": "{"value":"Sparkle Day"}",
1035
- "toolCallId": "call_yfBEybNYi",
1036
- "toolName": "test-tool",
1037
- "type": "tool-call",
1038
- },
1039
- {
1040
- "finishReason": {
1041
- "raw": "tool_calls",
1042
- "unified": "tool-calls",
1043
- },
1044
- "type": "finish",
1045
- "usage": {
1046
- "inputTokens": {
1047
- "cacheRead": 0,
1048
- "cacheWrite": undefined,
1049
- "noCache": 183,
1050
- "total": 183,
1051
- },
1052
- "outputTokens": {
1053
- "reasoning": 0,
1054
- "text": 133,
1055
- "total": 133,
1056
- },
1057
- "raw": {
1058
- "completion_tokens": 133,
1059
- "prompt_tokens": 183,
1060
- "total_tokens": 316,
1061
- },
1062
- },
1063
- },
1064
- ]
1065
- `);
1066
- });
1067
-
1068
- it('should expose the raw response headers', async () => {
1069
- prepareStreamResponse({
1070
- content: [],
1071
- headers: { 'test-header': 'test-value' },
1072
- });
1073
-
1074
- const { response } = await model.doStream({
1075
- prompt: TEST_PROMPT,
1076
- includeRawChunks: false,
1077
- });
1078
-
1079
- expect(response?.headers).toStrictEqual({
1080
- // default headers:
1081
- 'content-type': 'text/event-stream',
1082
- 'cache-control': 'no-cache',
1083
- connection: 'keep-alive',
1084
-
1085
- // custom header
1086
- 'test-header': 'test-value',
1087
- });
1088
- });
1089
-
1090
- it('should pass the messages', async () => {
1091
- prepareStreamResponse({ content: [''] });
1092
-
1093
- await model.doStream({
1094
- prompt: TEST_PROMPT,
1095
- includeRawChunks: false,
1096
- });
1097
-
1098
- expect(await server.calls[0].requestBodyJson).toStrictEqual({
1099
- stream: true,
1100
- model: 'grok-beta',
1101
- messages: [{ role: 'user', content: 'Hello' }],
1102
- stream_options: {
1103
- include_usage: true,
1104
- },
1105
- });
1106
- });
1107
-
1108
- it('should pass headers', async () => {
1109
- prepareStreamResponse({ content: [] });
1110
-
1111
- const modelWithHeaders = new XaiChatLanguageModel('grok-beta', {
1112
- provider: 'xai.chat',
1113
- baseURL: 'https://api.x.ai/v1',
1114
- headers: () => ({
1115
- authorization: 'Bearer test-api-key',
1116
- 'Custom-Provider-Header': 'provider-header-value',
1117
- }),
1118
- generateId: () => 'test-id',
1119
- });
1120
-
1121
- await modelWithHeaders.doStream({
1122
- prompt: TEST_PROMPT,
1123
- includeRawChunks: false,
1124
- headers: {
1125
- 'Custom-Request-Header': 'request-header-value',
1126
- },
1127
- });
1128
-
1129
- expect(server.calls[0].requestHeaders).toStrictEqual({
1130
- authorization: 'Bearer test-api-key',
1131
- 'content-type': 'application/json',
1132
- 'custom-provider-header': 'provider-header-value',
1133
- 'custom-request-header': 'request-header-value',
1134
- });
1135
- });
1136
-
1137
- it('should send request body', async () => {
1138
- prepareStreamResponse({ content: [] });
1139
-
1140
- const { request } = await model.doStream({
1141
- prompt: TEST_PROMPT,
1142
- includeRawChunks: false,
1143
- });
1144
-
1145
- expect(request).toMatchInlineSnapshot(`
1146
- {
1147
- "body": {
1148
- "max_completion_tokens": undefined,
1149
- "messages": [
1150
- {
1151
- "content": "Hello",
1152
- "role": "user",
1153
- },
1154
- ],
1155
- "model": "grok-beta",
1156
- "parallel_function_calling": undefined,
1157
- "reasoning_effort": undefined,
1158
- "response_format": undefined,
1159
- "search_parameters": undefined,
1160
- "seed": undefined,
1161
- "stream": true,
1162
- "stream_options": {
1163
- "include_usage": true,
1164
- },
1165
- "temperature": undefined,
1166
- "tool_choice": undefined,
1167
- "tools": undefined,
1168
- "top_p": undefined,
1169
- },
1170
- }
1171
- `);
1172
- });
1173
-
1174
- it('should stream citations as sources', async () => {
1175
- server.urls['https://api.x.ai/v1/chat/completions'].response = {
1176
- type: 'stream-chunks',
1177
- chunks: [
1178
- `data: {"id":"c8e45f92-7a3b-4d8e-9c1f-5e6a8b9d2f4c","object":"chat.completion.chunk","created":1750538200,"model":"grok-beta",` +
1179
- `"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}],"system_fingerprint":"fp_13a6dc65a6"}\n\n`,
1180
- `data: {"id":"c8e45f92-7a3b-4d8e-9c1f-5e6a8b9d2f4c","object":"chat.completion.chunk","created":1750538200,"model":"grok-beta",` +
1181
- `"choices":[{"index":0,"delta":{"content":"Latest AI news"},"finish_reason":null}],"system_fingerprint":"fp_13a6dc65a6"}\n\n`,
1182
- `data: {"id":"c8e45f92-7a3b-4d8e-9c1f-5e6a8b9d2f4c","object":"chat.completion.chunk","created":1750538200,"model":"grok-beta",` +
1183
- `"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],` +
1184
- `"usage":{"prompt_tokens":4,"total_tokens":34,"completion_tokens":30},` +
1185
- `"citations":["https://example.com/source1","https://example.com/source2"],"system_fingerprint":"fp_13a6dc65a6"}\n\n`,
1186
- `data: [DONE]\n\n`,
1187
- ],
1188
- };
1189
-
1190
- const { stream } = await model.doStream({
1191
- prompt: TEST_PROMPT,
1192
- includeRawChunks: false,
1193
- providerOptions: {
1194
- xai: {
1195
- searchParameters: {
1196
- mode: 'auto',
1197
- returnCitations: true,
1198
- },
1199
- },
1200
- },
1201
- });
1202
-
1203
- expect(await convertReadableStreamToArray(stream)).toMatchInlineSnapshot(`
1204
- [
1205
- {
1206
- "type": "stream-start",
1207
- "warnings": [],
1208
- },
1209
- {
1210
- "id": "c8e45f92-7a3b-4d8e-9c1f-5e6a8b9d2f4c",
1211
- "modelId": "grok-beta",
1212
- "timestamp": 2025-06-21T20:36:40.000Z,
1213
- "type": "response-metadata",
1214
- },
1215
- {
1216
- "id": "text-c8e45f92-7a3b-4d8e-9c1f-5e6a8b9d2f4c",
1217
- "type": "text-start",
1218
- },
1219
- {
1220
- "delta": "Latest AI news",
1221
- "id": "text-c8e45f92-7a3b-4d8e-9c1f-5e6a8b9d2f4c",
1222
- "type": "text-delta",
1223
- },
1224
- {
1225
- "id": "test-id",
1226
- "sourceType": "url",
1227
- "type": "source",
1228
- "url": "https://example.com/source1",
1229
- },
1230
- {
1231
- "id": "test-id",
1232
- "sourceType": "url",
1233
- "type": "source",
1234
- "url": "https://example.com/source2",
1235
- },
1236
- {
1237
- "id": "text-c8e45f92-7a3b-4d8e-9c1f-5e6a8b9d2f4c",
1238
- "type": "text-end",
1239
- },
1240
- {
1241
- "finishReason": {
1242
- "raw": "stop",
1243
- "unified": "stop",
1244
- },
1245
- "type": "finish",
1246
- "usage": {
1247
- "inputTokens": {
1248
- "cacheRead": 0,
1249
- "cacheWrite": undefined,
1250
- "noCache": 4,
1251
- "total": 4,
1252
- },
1253
- "outputTokens": {
1254
- "reasoning": 0,
1255
- "text": 30,
1256
- "total": 30,
1257
- },
1258
- "raw": {
1259
- "completion_tokens": 30,
1260
- "prompt_tokens": 4,
1261
- "total_tokens": 34,
1262
- },
1263
- },
1264
- },
1265
- ]
1266
- `);
1267
- });
1268
- });
1269
-
1270
- describe('reasoning models', () => {
1271
- const reasoningModel = new XaiChatLanguageModel('grok-3-mini', testConfig);
1272
-
1273
- function prepareReasoningResponse({
1274
- content = 'The result is 303.',
1275
- reasoning_content = 'Let me calculate 101 multiplied by 3: 101 * 3 = 303.',
1276
- usage = {
1277
- prompt_tokens: 15,
1278
- total_tokens: 35,
1279
- completion_tokens: 20,
1280
- completion_tokens_details: {
1281
- reasoning_tokens: 10,
1282
- },
1283
- },
1284
- }: {
1285
- content?: string;
1286
- reasoning_content?: string;
1287
- usage?: {
1288
- prompt_tokens: number;
1289
- total_tokens: number;
1290
- completion_tokens: number;
1291
- completion_tokens_details?: {
1292
- reasoning_tokens?: number;
1293
- };
1294
- };
1295
- }) {
1296
- server.urls['https://api.x.ai/v1/chat/completions'].response = {
1297
- type: 'json-value',
1298
- body: {
1299
- id: 'chatcmpl-reasoning-test',
1300
- object: 'chat.completion',
1301
- created: 1699472111,
1302
- model: 'grok-3-mini',
1303
- choices: [
1304
- {
1305
- index: 0,
1306
- message: {
1307
- role: 'assistant',
1308
- content,
1309
- reasoning_content,
1310
- tool_calls: null,
1311
- },
1312
- finish_reason: 'stop',
1313
- },
1314
- ],
1315
- usage,
1316
- },
1317
- };
1318
- }
1319
-
1320
- it('should pass reasoning_effort parameter', async () => {
1321
- prepareReasoningResponse({});
1322
-
1323
- await reasoningModel.doGenerate({
1324
- prompt: TEST_PROMPT,
1325
- providerOptions: {
1326
- xai: { reasoningEffort: 'high' },
1327
- },
1328
- });
1329
-
1330
- expect(await server.calls[0].requestBodyJson).toStrictEqual({
1331
- model: 'grok-3-mini',
1332
- messages: [{ role: 'user', content: 'Hello' }],
1333
- reasoning_effort: 'high',
1334
- });
1335
- });
1336
-
1337
- it('should extract reasoning content', async () => {
1338
- prepareReasoningResponse({
1339
- content: 'The answer is 303.',
1340
- reasoning_content: 'Let me think: 101 * 3 = 303.',
1341
- });
1342
-
1343
- const { content } = await reasoningModel.doGenerate({
1344
- prompt: TEST_PROMPT,
1345
- providerOptions: {
1346
- xai: { reasoningEffort: 'low' },
1347
- },
1348
- });
1349
-
1350
- expect(content).toMatchInlineSnapshot(`
1351
- [
1352
- {
1353
- "text": "The answer is 303.",
1354
- "type": "text",
1355
- },
1356
- {
1357
- "text": "Let me think: 101 * 3 = 303.",
1358
- "type": "reasoning",
1359
- },
1360
- ]
1361
- `);
1362
- });
1363
-
1364
- it('should extract reasoning tokens from usage', async () => {
1365
- prepareReasoningResponse({
1366
- usage: {
1367
- prompt_tokens: 15,
1368
- completion_tokens: 20,
1369
- total_tokens: 35,
1370
- completion_tokens_details: {
1371
- reasoning_tokens: 10,
1372
- },
1373
- },
1374
- });
1375
-
1376
- const { usage } = await reasoningModel.doGenerate({
1377
- prompt: TEST_PROMPT,
1378
- providerOptions: {
1379
- xai: { reasoningEffort: 'high' },
1380
- },
1381
- });
1382
-
1383
- expect(usage).toMatchInlineSnapshot(`
1384
- {
1385
- "inputTokens": {
1386
- "cacheRead": 0,
1387
- "cacheWrite": undefined,
1388
- "noCache": 15,
1389
- "total": 15,
1390
- },
1391
- "outputTokens": {
1392
- "reasoning": 10,
1393
- "text": 10,
1394
- "total": 20,
1395
- },
1396
- "raw": {
1397
- "completion_tokens": 20,
1398
- "completion_tokens_details": {
1399
- "reasoning_tokens": 10,
1400
- },
1401
- "prompt_tokens": 15,
1402
- "total_tokens": 35,
1403
- },
1404
- }
1405
- `);
1406
- });
1407
-
1408
- it('should handle reasoning streaming', async () => {
1409
- server.urls['https://api.x.ai/v1/chat/completions'].response = {
1410
- type: 'stream-chunks',
1411
- chunks: [
1412
- `data: {"id":"b7f32e89-8d6c-4a1e-9f5b-2c8e7a9d4f6b","object":"chat.completion.chunk","created":1750538120,"model":"grok-3-mini",` +
1413
- `"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}],"system_fingerprint":"fp_reasoning_v1"}\n\n`,
1414
- `data: {"id":"b7f32e89-8d6c-4a1e-9f5b-2c8e7a9d4f6b","object":"chat.completion.chunk","created":1750538120,"model":"grok-3-mini",` +
1415
- `"choices":[{"index":0,"delta":{"reasoning_content":"Let me calculate: "},"finish_reason":null}],"system_fingerprint":"fp_reasoning_v1"}\n\n`,
1416
- `data: {"id":"b7f32e89-8d6c-4a1e-9f5b-2c8e7a9d4f6b","object":"chat.completion.chunk","created":1750538120,"model":"grok-3-mini",` +
1417
- `"choices":[{"index":0,"delta":{"reasoning_content":"101 * 3 = 303"},"finish_reason":null}],"system_fingerprint":"fp_reasoning_v1"}\n\n`,
1418
- `data: {"id":"b7f32e89-8d6c-4a1e-9f5b-2c8e7a9d4f6b","object":"chat.completion.chunk","created":1750538120,"model":"grok-3-mini",` +
1419
- `"choices":[{"index":0,"delta":{"content":"The answer is 303."},"finish_reason":null}],"system_fingerprint":"fp_reasoning_v1"}\n\n`,
1420
- `data: {"id":"b7f32e89-8d6c-4a1e-9f5b-2c8e7a9d4f6b","object":"chat.completion.chunk","created":1750538120,"model":"grok-3-mini",` +
1421
- `"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],` +
1422
- `"usage":{"prompt_tokens":15,"total_tokens":35,"completion_tokens":20,"completion_tokens_details":{"reasoning_tokens":10}},"system_fingerprint":"fp_reasoning_v1"}\n\n`,
1423
- `data: [DONE]\n\n`,
1424
- ],
1425
- };
1426
-
1427
- const { stream } = await reasoningModel.doStream({
1428
- prompt: TEST_PROMPT,
1429
- includeRawChunks: false,
1430
- providerOptions: {
1431
- xai: { reasoningEffort: 'low' },
1432
- },
1433
- });
1434
-
1435
- expect(await convertReadableStreamToArray(stream)).toMatchInlineSnapshot(`
1436
- [
1437
- {
1438
- "type": "stream-start",
1439
- "warnings": [],
1440
- },
1441
- {
1442
- "id": "b7f32e89-8d6c-4a1e-9f5b-2c8e7a9d4f6b",
1443
- "modelId": "grok-3-mini",
1444
- "timestamp": 2025-06-21T20:35:20.000Z,
1445
- "type": "response-metadata",
1446
- },
1447
- {
1448
- "id": "reasoning-b7f32e89-8d6c-4a1e-9f5b-2c8e7a9d4f6b",
1449
- "type": "reasoning-start",
1450
- },
1451
- {
1452
- "delta": "Let me calculate: ",
1453
- "id": "reasoning-b7f32e89-8d6c-4a1e-9f5b-2c8e7a9d4f6b",
1454
- "type": "reasoning-delta",
1455
- },
1456
- {
1457
- "delta": "101 * 3 = 303",
1458
- "id": "reasoning-b7f32e89-8d6c-4a1e-9f5b-2c8e7a9d4f6b",
1459
- "type": "reasoning-delta",
1460
- },
1461
- {
1462
- "id": "reasoning-b7f32e89-8d6c-4a1e-9f5b-2c8e7a9d4f6b",
1463
- "type": "reasoning-end",
1464
- },
1465
- {
1466
- "id": "text-b7f32e89-8d6c-4a1e-9f5b-2c8e7a9d4f6b",
1467
- "type": "text-start",
1468
- },
1469
- {
1470
- "delta": "The answer is 303.",
1471
- "id": "text-b7f32e89-8d6c-4a1e-9f5b-2c8e7a9d4f6b",
1472
- "type": "text-delta",
1473
- },
1474
- {
1475
- "id": "text-b7f32e89-8d6c-4a1e-9f5b-2c8e7a9d4f6b",
1476
- "type": "text-end",
1477
- },
1478
- {
1479
- "finishReason": {
1480
- "raw": "stop",
1481
- "unified": "stop",
1482
- },
1483
- "type": "finish",
1484
- "usage": {
1485
- "inputTokens": {
1486
- "cacheRead": 0,
1487
- "cacheWrite": undefined,
1488
- "noCache": 15,
1489
- "total": 15,
1490
- },
1491
- "outputTokens": {
1492
- "reasoning": 10,
1493
- "text": 10,
1494
- "total": 20,
1495
- },
1496
- "raw": {
1497
- "completion_tokens": 20,
1498
- "completion_tokens_details": {
1499
- "reasoning_tokens": 10,
1500
- },
1501
- "prompt_tokens": 15,
1502
- "total_tokens": 35,
1503
- },
1504
- },
1505
- },
1506
- ]
1507
- `);
1508
- });
1509
-
1510
- it('should deduplicate repetitive reasoning deltas', async () => {
1511
- server.urls['https://api.x.ai/v1/chat/completions'].response = {
1512
- type: 'stream-chunks',
1513
- chunks: [
1514
- `data: {"id":"grok-4-test","object":"chat.completion.chunk","created":1750538120,"model":"grok-4-0709",` +
1515
- `"choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}],"system_fingerprint":"fp_reasoning_v1"}\n\n`,
1516
- // Multiple identical "Thinking..." deltas (simulating Grok 4 issue)
1517
- `data: {"id":"grok-4-test","object":"chat.completion.chunk","created":1750538120,"model":"grok-4-0709",` +
1518
- `"choices":[{"index":0,"delta":{"reasoning_content":"Thinking... "},"finish_reason":null}],"system_fingerprint":"fp_reasoning_v1"}\n\n`,
1519
- `data: {"id":"grok-4-test","object":"chat.completion.chunk","created":1750538120,"model":"grok-4-0709",` +
1520
- `"choices":[{"index":0,"delta":{"reasoning_content":"Thinking... "},"finish_reason":null}],"system_fingerprint":"fp_reasoning_v1"}\n\n`,
1521
- `data: {"id":"grok-4-test","object":"chat.completion.chunk","created":1750538120,"model":"grok-4-0709",` +
1522
- `"choices":[{"index":0,"delta":{"reasoning_content":"Thinking... "},"finish_reason":null}],"system_fingerprint":"fp_reasoning_v1"}\n\n`,
1523
- // Different reasoning content should still come through
1524
- `data: {"id":"grok-4-test","object":"chat.completion.chunk","created":1750538120,"model":"grok-4-0709",` +
1525
- `"choices":[{"index":0,"delta":{"reasoning_content":"Actually calculating now..."},"finish_reason":null}],"system_fingerprint":"fp_reasoning_v1"}\n\n`,
1526
- `data: {"id":"grok-4-test","object":"chat.completion.chunk","created":1750538120,"model":"grok-4-0709",` +
1527
- `"choices":[{"index":0,"delta":{"content":"The answer is 42."},"finish_reason":null}],"system_fingerprint":"fp_reasoning_v1"}\n\n`,
1528
- `data: {"id":"grok-4-test","object":"chat.completion.chunk","created":1750538120,"model":"grok-4-0709",` +
1529
- `"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],` +
1530
- `"usage":{"prompt_tokens":15,"total_tokens":35,"completion_tokens":20,"completion_tokens_details":{"reasoning_tokens":10}},"system_fingerprint":"fp_reasoning_v1"}\n\n`,
1531
- `data: [DONE]\n\n`,
1532
- ],
1533
- };
1534
-
1535
- const { stream } = await reasoningModel.doStream({
1536
- prompt: TEST_PROMPT,
1537
- includeRawChunks: false,
1538
- providerOptions: {
1539
- xai: { reasoningEffort: 'low' },
1540
- },
1541
- });
1542
-
1543
- expect(await convertReadableStreamToArray(stream)).toMatchInlineSnapshot(`
1544
- [
1545
- {
1546
- "type": "stream-start",
1547
- "warnings": [],
1548
- },
1549
- {
1550
- "id": "grok-4-test",
1551
- "modelId": "grok-4-0709",
1552
- "timestamp": 2025-06-21T20:35:20.000Z,
1553
- "type": "response-metadata",
1554
- },
1555
- {
1556
- "id": "reasoning-grok-4-test",
1557
- "type": "reasoning-start",
1558
- },
1559
- {
1560
- "delta": "Thinking... ",
1561
- "id": "reasoning-grok-4-test",
1562
- "type": "reasoning-delta",
1563
- },
1564
- {
1565
- "delta": "Actually calculating now...",
1566
- "id": "reasoning-grok-4-test",
1567
- "type": "reasoning-delta",
1568
- },
1569
- {
1570
- "id": "reasoning-grok-4-test",
1571
- "type": "reasoning-end",
1572
- },
1573
- {
1574
- "id": "text-grok-4-test",
1575
- "type": "text-start",
1576
- },
1577
- {
1578
- "delta": "The answer is 42.",
1579
- "id": "text-grok-4-test",
1580
- "type": "text-delta",
1581
- },
1582
- {
1583
- "id": "text-grok-4-test",
1584
- "type": "text-end",
1585
- },
1586
- {
1587
- "finishReason": {
1588
- "raw": "stop",
1589
- "unified": "stop",
1590
- },
1591
- "type": "finish",
1592
- "usage": {
1593
- "inputTokens": {
1594
- "cacheRead": 0,
1595
- "cacheWrite": undefined,
1596
- "noCache": 15,
1597
- "total": 15,
1598
- },
1599
- "outputTokens": {
1600
- "reasoning": 10,
1601
- "text": 10,
1602
- "total": 20,
1603
- },
1604
- "raw": {
1605
- "completion_tokens": 20,
1606
- "completion_tokens_details": {
1607
- "reasoning_tokens": 10,
1608
- },
1609
- "prompt_tokens": 15,
1610
- "total_tokens": 35,
1611
- },
1612
- },
1613
- },
1614
- ]
1615
- `);
1616
- });
1617
- });
1618
- });
1619
-
1620
- describe('doStream with raw chunks', () => {
1621
- it('should stream raw chunks when includeRawChunks is true', async () => {
1622
- server.urls['https://api.x.ai/v1/chat/completions'].response = {
1623
- type: 'stream-chunks',
1624
- chunks: [
1625
- `data: {"id":"d9f56e23-8b4c-4e7a-9d2f-6c8a9b5e3f7d","object":"chat.completion.chunk","created":1750538300,"model":"grok-beta","choices":[{"index":0,"delta":{"role":"assistant","content":"Hello"},"finish_reason":null}],"system_fingerprint":"fp_13a6dc65a6"}\n\n`,
1626
- `data: {"id":"e2a47b89-3f6d-4c8e-9a1b-7d5f8c9e2a4b","object":"chat.completion.chunk","created":1750538301,"model":"grok-beta","choices":[{"index":0,"delta":{"content":" world"},"finish_reason":null}],"system_fingerprint":"fp_13a6dc65a6"}\n\n`,
1627
- `data: {"id":"f3b58c9a-4e7f-5d9e-ab2c-8e6f9d0e3b5c","object":"chat.completion.chunk","created":1750538302,"model":"grok-beta","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":10,"completion_tokens":5,"total_tokens":15},"citations":["https://example.com"],"system_fingerprint":"fp_13a6dc65a6"}\n\n`,
1628
- 'data: [DONE]\n\n',
1629
- ],
1630
- };
1631
-
1632
- const { stream } = await model.doStream({
1633
- prompt: TEST_PROMPT,
1634
- includeRawChunks: true,
1635
- });
1636
-
1637
- const chunks = await convertReadableStreamToArray(stream);
1638
-
1639
- expect(chunks).toMatchInlineSnapshot(`
1640
- [
1641
- {
1642
- "type": "stream-start",
1643
- "warnings": [],
1644
- },
1645
- {
1646
- "rawValue": {
1647
- "choices": [
1648
- {
1649
- "delta": {
1650
- "content": "Hello",
1651
- "role": "assistant",
1652
- },
1653
- "finish_reason": null,
1654
- "index": 0,
1655
- },
1656
- ],
1657
- "created": 1750538300,
1658
- "id": "d9f56e23-8b4c-4e7a-9d2f-6c8a9b5e3f7d",
1659
- "model": "grok-beta",
1660
- "object": "chat.completion.chunk",
1661
- "system_fingerprint": "fp_13a6dc65a6",
1662
- },
1663
- "type": "raw",
1664
- },
1665
- {
1666
- "id": "d9f56e23-8b4c-4e7a-9d2f-6c8a9b5e3f7d",
1667
- "modelId": "grok-beta",
1668
- "timestamp": 2025-06-21T20:38:20.000Z,
1669
- "type": "response-metadata",
1670
- },
1671
- {
1672
- "id": "text-d9f56e23-8b4c-4e7a-9d2f-6c8a9b5e3f7d",
1673
- "type": "text-start",
1674
- },
1675
- {
1676
- "delta": "Hello",
1677
- "id": "text-d9f56e23-8b4c-4e7a-9d2f-6c8a9b5e3f7d",
1678
- "type": "text-delta",
1679
- },
1680
- {
1681
- "rawValue": {
1682
- "choices": [
1683
- {
1684
- "delta": {
1685
- "content": " world",
1686
- },
1687
- "finish_reason": null,
1688
- "index": 0,
1689
- },
1690
- ],
1691
- "created": 1750538301,
1692
- "id": "e2a47b89-3f6d-4c8e-9a1b-7d5f8c9e2a4b",
1693
- "model": "grok-beta",
1694
- "object": "chat.completion.chunk",
1695
- "system_fingerprint": "fp_13a6dc65a6",
1696
- },
1697
- "type": "raw",
1698
- },
1699
- {
1700
- "id": "text-e2a47b89-3f6d-4c8e-9a1b-7d5f8c9e2a4b",
1701
- "type": "text-start",
1702
- },
1703
- {
1704
- "delta": " world",
1705
- "id": "text-e2a47b89-3f6d-4c8e-9a1b-7d5f8c9e2a4b",
1706
- "type": "text-delta",
1707
- },
1708
- {
1709
- "rawValue": {
1710
- "choices": [
1711
- {
1712
- "delta": {},
1713
- "finish_reason": "stop",
1714
- "index": 0,
1715
- },
1716
- ],
1717
- "citations": [
1718
- "https://example.com",
1719
- ],
1720
- "created": 1750538302,
1721
- "id": "f3b58c9a-4e7f-5d9e-ab2c-8e6f9d0e3b5c",
1722
- "model": "grok-beta",
1723
- "object": "chat.completion.chunk",
1724
- "system_fingerprint": "fp_13a6dc65a6",
1725
- "usage": {
1726
- "completion_tokens": 5,
1727
- "prompt_tokens": 10,
1728
- "total_tokens": 15,
1729
- },
1730
- },
1731
- "type": "raw",
1732
- },
1733
- {
1734
- "id": "test-id",
1735
- "sourceType": "url",
1736
- "type": "source",
1737
- "url": "https://example.com",
1738
- },
1739
- {
1740
- "id": "text-d9f56e23-8b4c-4e7a-9d2f-6c8a9b5e3f7d",
1741
- "type": "text-end",
1742
- },
1743
- {
1744
- "id": "text-e2a47b89-3f6d-4c8e-9a1b-7d5f8c9e2a4b",
1745
- "type": "text-end",
1746
- },
1747
- {
1748
- "finishReason": {
1749
- "raw": "stop",
1750
- "unified": "stop",
1751
- },
1752
- "type": "finish",
1753
- "usage": {
1754
- "inputTokens": {
1755
- "cacheRead": 0,
1756
- "cacheWrite": undefined,
1757
- "noCache": 10,
1758
- "total": 10,
1759
- },
1760
- "outputTokens": {
1761
- "reasoning": 0,
1762
- "text": 5,
1763
- "total": 5,
1764
- },
1765
- "raw": {
1766
- "completion_tokens": 5,
1767
- "prompt_tokens": 10,
1768
- "total_tokens": 15,
1769
- },
1770
- },
1771
- },
1772
- ]
1773
- `);
1774
- });
1775
-
1776
- describe('error handling', () => {
1777
- it('should throw APICallError when xai returns error with 200 status (doGenerate)', async () => {
1778
- server.urls['https://api.x.ai/v1/chat/completions'].response = {
1779
- type: 'json-value',
1780
- body: {
1781
- code: 'The service is currently unavailable',
1782
- error: 'Timed out waiting for first token',
1783
- },
1784
- };
1785
-
1786
- await expect(model.doGenerate({ prompt: TEST_PROMPT })).rejects.toThrow(
1787
- 'Timed out waiting for first token',
1788
- );
1789
- });
1790
-
1791
- it('should throw APICallError when xai returns error with 200 status (doStream)', async () => {
1792
- server.urls['https://api.x.ai/v1/chat/completions'].response = {
1793
- type: 'json-value',
1794
- body: {
1795
- code: 'The service is currently unavailable',
1796
- error: 'Timed out waiting for first token',
1797
- },
1798
- };
1799
-
1800
- await expect(model.doStream({ prompt: TEST_PROMPT })).rejects.toThrow(
1801
- 'Timed out waiting for first token',
1802
- );
1803
- });
1804
- });
1805
- });