@ai-sdk/azure 3.0.14 → 3.0.15

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,1869 @@
1
+ import {
2
+ EmbeddingModelV3Embedding,
3
+ LanguageModelV3,
4
+ LanguageModelV3FunctionTool,
5
+ LanguageModelV3GenerateResult,
6
+ LanguageModelV3Prompt,
7
+ } from '@ai-sdk/provider';
8
+ import {
9
+ convertReadableStreamToArray,
10
+ mockId,
11
+ } from '@ai-sdk/provider-utils/test';
12
+ import { createTestServer } from '@ai-sdk/test-server/with-vitest';
13
+ import fs from 'node:fs';
14
+ import { beforeEach, describe, it, expect, vi } from 'vitest';
15
+ import { OpenAIResponsesLanguageModel } from '@ai-sdk/openai/internal';
16
+ import { createAzure } from './azure-openai-provider';
17
+
18
+ vi.mock('./version', () => ({
19
+ VERSION: '0.0.0-test',
20
+ }));
21
+
22
+ const TEST_PROMPT: LanguageModelV3Prompt = [
23
+ { role: 'user', content: [{ type: 'text', text: 'Hello' }] },
24
+ ];
25
+
26
+ const TEST_TOOLS: Array<LanguageModelV3FunctionTool> = [
27
+ {
28
+ type: 'function',
29
+ name: 'weather',
30
+ inputSchema: {
31
+ type: 'object',
32
+ properties: { location: { type: 'string' } },
33
+ required: ['location'],
34
+ additionalProperties: false,
35
+ },
36
+ },
37
+ {
38
+ type: 'function',
39
+ name: 'cityAttractions',
40
+ inputSchema: {
41
+ type: 'object',
42
+ properties: { city: { type: 'string' } },
43
+ required: ['city'],
44
+ additionalProperties: false,
45
+ },
46
+ },
47
+ ];
48
+
49
+ function prepareJsonFixtureResponse(filename: string) {
50
+ server.urls[
51
+ 'https://test-resource.openai.azure.com/openai/v1/responses'
52
+ ].response = {
53
+ type: 'json-value',
54
+ body: JSON.parse(
55
+ fs.readFileSync(`src/__fixtures__/${filename}.json`, 'utf8'),
56
+ ),
57
+ };
58
+ return;
59
+ }
60
+
61
+ function prepareChunksFixtureResponse(filename: string) {
62
+ const chunks = fs
63
+ .readFileSync(`src/__fixtures__/${filename}.chunks.txt`, 'utf8')
64
+ .split('\n')
65
+ .map(line => `data: ${line}\n\n`);
66
+ chunks.push('data: [DONE]\n\n');
67
+
68
+ server.urls[
69
+ 'https://test-resource.openai.azure.com/openai/v1/responses'
70
+ ].response = {
71
+ type: 'stream-chunks',
72
+ chunks,
73
+ };
74
+ }
75
+
76
+ function createModel(modelId: string) {
77
+ return new OpenAIResponsesLanguageModel(modelId, {
78
+ provider: 'azure.responses',
79
+ url: ({ path }) =>
80
+ `https://test-resource.openai.azure.com/openai/v1${path}`,
81
+ //headers: () => ({ Authorization: `Bearer APIKEY` }),
82
+ headers: () => ({ Authorization: `Bearer APIKEY` }),
83
+ generateId: mockId(),
84
+ fileIdPrefixes: ['assistant-'],
85
+ });
86
+ }
87
+
88
+ const provider = createAzure({
89
+ resourceName: 'test-resource',
90
+ apiKey: 'test-api-key',
91
+ });
92
+
93
+ const providerApiVersionChanged = createAzure({
94
+ resourceName: 'test-resource',
95
+ apiKey: 'test-api-key',
96
+ apiVersion: '2025-04-01-preview',
97
+ });
98
+
99
+ const server = createTestServer({
100
+ 'https://test-resource.openai.azure.com/openai/v1/chat/completions': {},
101
+ 'https://test-resource.openai.azure.com/openai/v1/completions': {},
102
+ 'https://test-resource.openai.azure.com/openai/v1/embeddings': {},
103
+ 'https://test-resource.openai.azure.com/openai/v1/images/generations': {},
104
+ 'https://test-resource.openai.azure.com/openai/v1/responses': {},
105
+ 'https://test-resource.openai.azure.com/openai/v1/audio/transcriptions': {},
106
+ 'https://test-resource.openai.azure.com/openai/v1/audio/speech': {},
107
+ 'https://test-resource.openai.azure.com/openai/deployments/whisper-1/audio/transcriptions':
108
+ {},
109
+ });
110
+
111
+ describe('responses (default language model)', () => {
112
+ describe('doGenerate', () => {
113
+ function prepareJsonResponse({
114
+ content = '',
115
+ usage = {
116
+ input_tokens: 4,
117
+ output_tokens: 30,
118
+ total_tokens: 34,
119
+ },
120
+ } = {}) {
121
+ server.urls[
122
+ 'https://test-resource.openai.azure.com/openai/v1/responses'
123
+ ].response = {
124
+ type: 'json-value',
125
+ body: {
126
+ id: 'resp_67c97c0203188190a025beb4a75242bc',
127
+ object: 'response',
128
+ created_at: 1741257730,
129
+ status: 'completed',
130
+ model: 'test-deployment',
131
+ output: [
132
+ {
133
+ id: 'msg_67c97c02656c81908e080dfdf4a03cd1',
134
+ type: 'message',
135
+ status: 'completed',
136
+ role: 'assistant',
137
+ content: [
138
+ {
139
+ type: 'output_text',
140
+ text: content,
141
+ annotations: [],
142
+ },
143
+ ],
144
+ },
145
+ ],
146
+ usage,
147
+ incomplete_details: null,
148
+ },
149
+ };
150
+ }
151
+
152
+ it('should set the correct default api version', async () => {
153
+ prepareJsonResponse();
154
+
155
+ await provider('test-deployment').doGenerate({
156
+ prompt: TEST_PROMPT,
157
+ });
158
+
159
+ expect(
160
+ server.calls[0].requestUrlSearchParams.get('api-version'),
161
+ ).toStrictEqual('v1');
162
+ });
163
+
164
+ it('should set the correct modified api version', async () => {
165
+ prepareJsonResponse();
166
+
167
+ await providerApiVersionChanged('test-deployment').doGenerate({
168
+ prompt: TEST_PROMPT,
169
+ });
170
+
171
+ expect(
172
+ server.calls[0].requestUrlSearchParams.get('api-version'),
173
+ ).toStrictEqual('2025-04-01-preview');
174
+ });
175
+
176
+ it('should pass headers', async () => {
177
+ prepareJsonResponse();
178
+
179
+ const provider = createAzure({
180
+ resourceName: 'test-resource',
181
+ apiKey: 'test-api-key',
182
+ headers: {
183
+ 'Custom-Provider-Header': 'provider-header-value',
184
+ },
185
+ });
186
+
187
+ await provider('test-deployment').doGenerate({
188
+ prompt: TEST_PROMPT,
189
+ headers: {
190
+ 'Custom-Request-Header': 'request-header-value',
191
+ },
192
+ });
193
+
194
+ expect(server.calls[0].requestHeaders).toStrictEqual({
195
+ 'api-key': 'test-api-key',
196
+ 'content-type': 'application/json',
197
+ 'custom-provider-header': 'provider-header-value',
198
+ 'custom-request-header': 'request-header-value',
199
+ });
200
+ expect(server.calls[0].requestUserAgent).toContain(
201
+ `ai-sdk/azure/0.0.0-test`,
202
+ );
203
+ });
204
+
205
+ it('should use the baseURL correctly', async () => {
206
+ prepareJsonResponse();
207
+
208
+ const provider = createAzure({
209
+ baseURL: 'https://test-resource.openai.azure.com/openai',
210
+ apiKey: 'test-api-key',
211
+ });
212
+
213
+ await provider('test-deployment').doGenerate({
214
+ prompt: TEST_PROMPT,
215
+ });
216
+ expect(server.calls[0].requestUrl).toStrictEqual(
217
+ 'https://test-resource.openai.azure.com/openai/v1/responses?api-version=v1',
218
+ );
219
+ });
220
+ });
221
+ });
222
+
223
+ describe('chat', () => {
224
+ describe('doGenerate', () => {
225
+ function prepareJsonResponse({ content = '' }: { content?: string } = {}) {
226
+ server.urls[
227
+ 'https://test-resource.openai.azure.com/openai/v1/chat/completions'
228
+ ].response = {
229
+ type: 'json-value',
230
+ body: {
231
+ id: 'chatcmpl-95ZTZkhr0mHNKqerQfiwkuox3PHAd',
232
+ object: 'chat.completion',
233
+ created: 1711115037,
234
+ model: 'gpt-3.5-turbo-0125',
235
+ choices: [
236
+ {
237
+ index: 0,
238
+ message: {
239
+ role: 'assistant',
240
+ content,
241
+ },
242
+ finish_reason: 'stop',
243
+ },
244
+ ],
245
+ usage: {
246
+ prompt_tokens: 4,
247
+ total_tokens: 34,
248
+ completion_tokens: 30,
249
+ },
250
+ system_fingerprint: 'fp_3bc1b5746c',
251
+ },
252
+ };
253
+ }
254
+
255
+ it('should set the correct default api version', async () => {
256
+ prepareJsonResponse();
257
+
258
+ await provider.chat('test-deployment').doGenerate({
259
+ prompt: TEST_PROMPT,
260
+ });
261
+
262
+ expect(
263
+ server.calls[0].requestUrlSearchParams.get('api-version'),
264
+ ).toStrictEqual('v1');
265
+ });
266
+
267
+ it('should set the correct modified api version', async () => {
268
+ prepareJsonResponse();
269
+
270
+ await providerApiVersionChanged.chat('test-deployment').doGenerate({
271
+ prompt: TEST_PROMPT,
272
+ });
273
+
274
+ expect(
275
+ server.calls[0].requestUrlSearchParams.get('api-version'),
276
+ ).toStrictEqual('2025-04-01-preview');
277
+ });
278
+
279
+ it('should pass headers', async () => {
280
+ prepareJsonResponse();
281
+
282
+ const provider = createAzure({
283
+ resourceName: 'test-resource',
284
+ apiKey: 'test-api-key',
285
+ headers: {
286
+ 'Custom-Provider-Header': 'provider-header-value',
287
+ },
288
+ });
289
+
290
+ await provider.chat('test-deployment').doGenerate({
291
+ prompt: TEST_PROMPT,
292
+ headers: {
293
+ 'Custom-Request-Header': 'request-header-value',
294
+ },
295
+ });
296
+
297
+ expect(server.calls[0].requestHeaders).toStrictEqual({
298
+ 'api-key': 'test-api-key',
299
+ 'content-type': 'application/json',
300
+ 'custom-provider-header': 'provider-header-value',
301
+ 'custom-request-header': 'request-header-value',
302
+ });
303
+ expect(server.calls[0].requestUserAgent).toContain(
304
+ `ai-sdk/azure/0.0.0-test`,
305
+ );
306
+ });
307
+
308
+ it('should use the baseURL correctly', async () => {
309
+ prepareJsonResponse();
310
+
311
+ const provider = createAzure({
312
+ baseURL: 'https://test-resource.openai.azure.com/openai',
313
+ apiKey: 'test-api-key',
314
+ });
315
+
316
+ await provider.chat('test-deployment').doGenerate({
317
+ prompt: TEST_PROMPT,
318
+ });
319
+ expect(server.calls[0].requestUrl).toStrictEqual(
320
+ 'https://test-resource.openai.azure.com/openai/v1/chat/completions?api-version=v1',
321
+ );
322
+ });
323
+ });
324
+ });
325
+
326
+ describe('completion', () => {
327
+ describe('doGenerate', () => {
328
+ function prepareJsonCompletionResponse({
329
+ content = '',
330
+ usage = {
331
+ prompt_tokens: 4,
332
+ total_tokens: 34,
333
+ completion_tokens: 30,
334
+ },
335
+ finish_reason = 'stop',
336
+ }: {
337
+ content?: string;
338
+ usage?: {
339
+ prompt_tokens: number;
340
+ total_tokens: number;
341
+ completion_tokens: number;
342
+ };
343
+ finish_reason?: string;
344
+ }) {
345
+ server.urls[
346
+ 'https://test-resource.openai.azure.com/openai/v1/completions'
347
+ ].response = {
348
+ type: 'json-value',
349
+ body: {
350
+ id: 'cmpl-96cAM1v77r4jXa4qb2NSmRREV5oWB',
351
+ object: 'text_completion',
352
+ created: 1711363706,
353
+ model: 'test-deployment',
354
+ choices: [
355
+ {
356
+ text: content,
357
+ index: 0,
358
+ finish_reason,
359
+ },
360
+ ],
361
+ usage,
362
+ },
363
+ };
364
+ }
365
+
366
+ it('should set the correct api version', async () => {
367
+ prepareJsonCompletionResponse({ content: 'Hello World!' });
368
+
369
+ await provider.completion('test-deployment').doGenerate({
370
+ prompt: TEST_PROMPT,
371
+ });
372
+ expect(
373
+ server.calls[0].requestUrlSearchParams.get('api-version'),
374
+ ).toStrictEqual('v1');
375
+ });
376
+
377
+ it('should pass headers', async () => {
378
+ prepareJsonCompletionResponse({ content: 'Hello World!' });
379
+
380
+ const provider = createAzure({
381
+ resourceName: 'test-resource',
382
+ apiKey: 'test-api-key',
383
+ headers: {
384
+ 'Custom-Provider-Header': 'provider-header-value',
385
+ },
386
+ });
387
+
388
+ await provider.completion('test-deployment').doGenerate({
389
+ prompt: TEST_PROMPT,
390
+ headers: {
391
+ 'Custom-Request-Header': 'request-header-value',
392
+ },
393
+ });
394
+
395
+ expect(server.calls[0].requestHeaders).toStrictEqual({
396
+ 'api-key': 'test-api-key',
397
+ 'content-type': 'application/json',
398
+ 'custom-provider-header': 'provider-header-value',
399
+ 'custom-request-header': 'request-header-value',
400
+ });
401
+ expect(server.calls[0].requestUserAgent).toContain(
402
+ `ai-sdk/azure/0.0.0-test`,
403
+ );
404
+ });
405
+ });
406
+ });
407
+
408
+ describe('transcription', () => {
409
+ describe('doGenerate', () => {
410
+ it('should use correct URL format', async () => {
411
+ server.urls[
412
+ 'https://test-resource.openai.azure.com/openai/v1/audio/transcriptions'
413
+ ].response = {
414
+ type: 'json-value',
415
+ body: {
416
+ text: 'Hello, world!',
417
+ segments: [],
418
+ language: 'en',
419
+ duration: 5.0,
420
+ },
421
+ };
422
+
423
+ await provider.transcription('whisper-1').doGenerate({
424
+ audio: new Uint8Array(),
425
+ mediaType: 'audio/wav',
426
+ });
427
+
428
+ expect(server.calls[0].requestUrl).toStrictEqual(
429
+ 'https://test-resource.openai.azure.com/openai/v1/audio/transcriptions?api-version=v1',
430
+ );
431
+ });
432
+
433
+ it('should use deployment-based URL format when useDeploymentBasedUrls is true', async () => {
434
+ const providerWithDeploymentUrls = createAzure({
435
+ resourceName: 'test-resource',
436
+ apiKey: 'test-api-key',
437
+ useDeploymentBasedUrls: true,
438
+ });
439
+
440
+ server.urls[
441
+ 'https://test-resource.openai.azure.com/openai/deployments/whisper-1/audio/transcriptions'
442
+ ].response = {
443
+ type: 'json-value',
444
+ body: {
445
+ text: 'Hello, world!',
446
+ segments: [],
447
+ language: 'en',
448
+ duration: 5.0,
449
+ },
450
+ };
451
+
452
+ await providerWithDeploymentUrls.transcription('whisper-1').doGenerate({
453
+ audio: new Uint8Array(),
454
+ mediaType: 'audio/wav',
455
+ });
456
+
457
+ expect(server.calls[0].requestUrl).toStrictEqual(
458
+ 'https://test-resource.openai.azure.com/openai/deployments/whisper-1/audio/transcriptions?api-version=v1',
459
+ );
460
+ });
461
+ });
462
+ });
463
+
464
+ describe('speech', () => {
465
+ describe('doGenerate', () => {
466
+ it('should use correct URL format', async () => {
467
+ server.urls[
468
+ 'https://test-resource.openai.azure.com/openai/v1/audio/speech'
469
+ ].response = {
470
+ type: 'json-value',
471
+ body: new Uint8Array([1, 2, 3]),
472
+ };
473
+
474
+ await provider.speech('tts-1').doGenerate({
475
+ text: 'Hello, world!',
476
+ });
477
+
478
+ expect(server.calls[0].requestUrl).toStrictEqual(
479
+ 'https://test-resource.openai.azure.com/openai/v1/audio/speech?api-version=v1',
480
+ );
481
+ });
482
+ });
483
+ });
484
+
485
+ describe('embedding', () => {
486
+ const dummyEmbeddings = [
487
+ [0.1, 0.2, 0.3, 0.4, 0.5],
488
+ [0.6, 0.7, 0.8, 0.9, 1.0],
489
+ ];
490
+ const testValues = ['sunny day at the beach', 'rainy day in the city'];
491
+
492
+ describe('doEmbed', () => {
493
+ const model = provider.embedding('my-embedding');
494
+
495
+ function prepareJsonResponse({
496
+ embeddings = dummyEmbeddings,
497
+ }: {
498
+ embeddings?: EmbeddingModelV3Embedding[];
499
+ } = {}) {
500
+ server.urls[
501
+ 'https://test-resource.openai.azure.com/openai/v1/embeddings'
502
+ ].response = {
503
+ type: 'json-value',
504
+ body: {
505
+ object: 'list',
506
+ data: embeddings.map((embedding, i) => ({
507
+ object: 'embedding',
508
+ index: i,
509
+ embedding,
510
+ })),
511
+ model: 'my-embedding',
512
+ usage: { prompt_tokens: 8, total_tokens: 8 },
513
+ },
514
+ };
515
+ }
516
+
517
+ it('should set the correct api version', async () => {
518
+ prepareJsonResponse();
519
+
520
+ await model.doEmbed({
521
+ values: testValues,
522
+ });
523
+ expect(
524
+ server.calls[0].requestUrlSearchParams.get('api-version'),
525
+ ).toStrictEqual('v1');
526
+ });
527
+
528
+ it('should pass headers', async () => {
529
+ prepareJsonResponse();
530
+
531
+ const provider = createAzure({
532
+ resourceName: 'test-resource',
533
+ apiKey: 'test-api-key',
534
+ headers: {
535
+ 'Custom-Provider-Header': 'provider-header-value',
536
+ },
537
+ });
538
+
539
+ await provider.embedding('my-embedding').doEmbed({
540
+ values: testValues,
541
+ headers: {
542
+ 'Custom-Request-Header': 'request-header-value',
543
+ },
544
+ });
545
+
546
+ expect(server.calls[0].requestHeaders).toStrictEqual({
547
+ 'api-key': 'test-api-key',
548
+ 'content-type': 'application/json',
549
+ 'custom-provider-header': 'provider-header-value',
550
+ 'custom-request-header': 'request-header-value',
551
+ });
552
+ expect(server.calls[0].requestUserAgent).toContain(
553
+ `ai-sdk/azure/0.0.0-test`,
554
+ );
555
+ });
556
+ });
557
+ });
558
+
559
+ describe('image', () => {
560
+ const prompt = 'A cute baby sea otter';
561
+
562
+ describe('doGenerate', () => {
563
+ function prepareJsonResponse() {
564
+ server.urls[
565
+ 'https://test-resource.openai.azure.com/openai/v1/images/generations'
566
+ ].response = {
567
+ type: 'json-value',
568
+ body: {
569
+ created: 1733837122,
570
+ data: [
571
+ {
572
+ revised_prompt:
573
+ 'A charming visual illustration of a baby sea otter swimming joyously.',
574
+ b64_json: 'base64-image-1',
575
+ },
576
+ {
577
+ b64_json: 'base64-image-2',
578
+ },
579
+ ],
580
+ },
581
+ };
582
+ }
583
+
584
+ it('should set the correct default api version', async () => {
585
+ prepareJsonResponse();
586
+
587
+ await provider.imageModel('dalle-deployment').doGenerate({
588
+ prompt,
589
+ files: undefined,
590
+ mask: undefined,
591
+ n: 1,
592
+ size: '1024x1024',
593
+ aspectRatio: undefined,
594
+ seed: undefined,
595
+ providerOptions: {},
596
+ });
597
+
598
+ expect(
599
+ server.calls[0].requestUrlSearchParams.get('api-version'),
600
+ ).toStrictEqual('v1');
601
+ });
602
+
603
+ it('should set the correct modified api version', async () => {
604
+ prepareJsonResponse();
605
+
606
+ await providerApiVersionChanged
607
+ .imageModel('dalle-deployment')
608
+ .doGenerate({
609
+ prompt,
610
+ files: undefined,
611
+ mask: undefined,
612
+ n: 1,
613
+ size: '1024x1024',
614
+ aspectRatio: undefined,
615
+ seed: undefined,
616
+ providerOptions: {},
617
+ });
618
+
619
+ expect(
620
+ server.calls[0].requestUrlSearchParams.get('api-version'),
621
+ ).toStrictEqual('2025-04-01-preview');
622
+ });
623
+
624
+ it('should pass headers', async () => {
625
+ prepareJsonResponse();
626
+
627
+ const provider = createAzure({
628
+ resourceName: 'test-resource',
629
+ apiKey: 'test-api-key',
630
+ headers: {
631
+ 'Custom-Provider-Header': 'provider-header-value',
632
+ },
633
+ });
634
+
635
+ await provider.imageModel('dalle-deployment').doGenerate({
636
+ prompt,
637
+ files: undefined,
638
+ mask: undefined,
639
+ n: 1,
640
+ size: '1024x1024',
641
+ aspectRatio: undefined,
642
+ seed: undefined,
643
+ providerOptions: {},
644
+ headers: {
645
+ 'Custom-Request-Header': 'request-header-value',
646
+ },
647
+ });
648
+
649
+ expect(server.calls[0].requestHeaders).toStrictEqual({
650
+ 'api-key': 'test-api-key',
651
+ 'content-type': 'application/json',
652
+ 'custom-provider-header': 'provider-header-value',
653
+ 'custom-request-header': 'request-header-value',
654
+ });
655
+ expect(server.calls[0].requestUserAgent).toContain(
656
+ `ai-sdk/azure/0.0.0-test`,
657
+ );
658
+ });
659
+
660
+ it('should use the baseURL correctly', async () => {
661
+ prepareJsonResponse();
662
+
663
+ const provider = createAzure({
664
+ baseURL: 'https://test-resource.openai.azure.com/openai',
665
+ apiKey: 'test-api-key',
666
+ });
667
+
668
+ await provider.imageModel('dalle-deployment').doGenerate({
669
+ prompt,
670
+ files: undefined,
671
+ mask: undefined,
672
+ n: 1,
673
+ size: '1024x1024',
674
+ aspectRatio: undefined,
675
+ seed: undefined,
676
+ providerOptions: {},
677
+ });
678
+
679
+ expect(server.calls[0].requestUrl).toStrictEqual(
680
+ 'https://test-resource.openai.azure.com/openai/v1/images/generations?api-version=v1',
681
+ );
682
+ });
683
+
684
+ it('should extract the generated images', async () => {
685
+ prepareJsonResponse();
686
+
687
+ const result = await provider.imageModel('dalle-deployment').doGenerate({
688
+ prompt,
689
+ files: undefined,
690
+ mask: undefined,
691
+ n: 1,
692
+ size: '1024x1024',
693
+ aspectRatio: undefined,
694
+ seed: undefined,
695
+ providerOptions: {},
696
+ });
697
+
698
+ expect(result.images).toStrictEqual(['base64-image-1', 'base64-image-2']);
699
+ });
700
+
701
+ it('should send the correct request body', async () => {
702
+ prepareJsonResponse();
703
+
704
+ await provider.imageModel('dalle-deployment').doGenerate({
705
+ prompt,
706
+ files: undefined,
707
+ mask: undefined,
708
+ n: 2,
709
+ size: '1024x1024',
710
+ aspectRatio: undefined,
711
+ seed: undefined,
712
+ providerOptions: { openai: { style: 'natural' } },
713
+ });
714
+
715
+ expect(await server.calls[0].requestBodyJson).toStrictEqual({
716
+ model: 'dalle-deployment',
717
+ prompt,
718
+ n: 2,
719
+ size: '1024x1024',
720
+ style: 'natural',
721
+ response_format: 'b64_json',
722
+ });
723
+ });
724
+ });
725
+
726
+ describe('imageModel method', () => {
727
+ it('should create the same model as image method', () => {
728
+ const imageModel = provider.imageModel('dalle-deployment');
729
+ const imageModelAlias = provider.imageModel('dalle-deployment');
730
+
731
+ expect(imageModel.provider).toBe(imageModelAlias.provider);
732
+ expect(imageModel.modelId).toBe(imageModelAlias.modelId);
733
+ });
734
+ });
735
+ });
736
+
737
+ describe('responses', () => {
738
+ describe('doGenerate', () => {
739
+ function prepareJsonResponse({
740
+ content = '',
741
+ usage = {
742
+ input_tokens: 4,
743
+ output_tokens: 30,
744
+ total_tokens: 34,
745
+ },
746
+ } = {}) {
747
+ server.urls[
748
+ 'https://test-resource.openai.azure.com/openai/v1/responses'
749
+ ].response = {
750
+ type: 'json-value',
751
+ body: {
752
+ id: 'resp_67c97c0203188190a025beb4a75242bc',
753
+ object: 'response',
754
+ created_at: 1741257730,
755
+ status: 'completed',
756
+ model: 'test-deployment',
757
+ output: [
758
+ {
759
+ id: 'msg_67c97c02656c81908e080dfdf4a03cd1',
760
+ type: 'message',
761
+ status: 'completed',
762
+ role: 'assistant',
763
+ content: [
764
+ {
765
+ type: 'output_text',
766
+ text: content,
767
+ annotations: [],
768
+ },
769
+ ],
770
+ },
771
+ ],
772
+ usage,
773
+ incomplete_details: null,
774
+ },
775
+ };
776
+ }
777
+
778
+ it('should set the correct api version', async () => {
779
+ prepareJsonResponse();
780
+
781
+ await provider.responses('test-deployment').doGenerate({
782
+ prompt: TEST_PROMPT,
783
+ });
784
+
785
+ expect(
786
+ server.calls[0].requestUrlSearchParams.get('api-version'),
787
+ ).toStrictEqual('v1');
788
+ });
789
+
790
+ it('should pass headers', async () => {
791
+ prepareJsonResponse();
792
+
793
+ const provider = createAzure({
794
+ resourceName: 'test-resource',
795
+ apiKey: 'test-api-key',
796
+ headers: {
797
+ 'Custom-Provider-Header': 'provider-header-value',
798
+ },
799
+ });
800
+
801
+ await provider.responses('test-deployment').doGenerate({
802
+ prompt: TEST_PROMPT,
803
+ headers: {
804
+ 'Custom-Request-Header': 'request-header-value',
805
+ },
806
+ });
807
+
808
+ expect(server.calls[0].requestHeaders).toStrictEqual({
809
+ 'api-key': 'test-api-key',
810
+ 'content-type': 'application/json',
811
+ 'custom-provider-header': 'provider-header-value',
812
+ 'custom-request-header': 'request-header-value',
813
+ });
814
+ expect(server.calls[0].requestUserAgent).toContain(
815
+ `ai-sdk/azure/0.0.0-test`,
816
+ );
817
+ });
818
+
819
+ it('should use the baseURL correctly', async () => {
820
+ prepareJsonResponse();
821
+
822
+ const provider = createAzure({
823
+ baseURL: 'https://test-resource.openai.azure.com/openai',
824
+ apiKey: 'test-api-key',
825
+ });
826
+
827
+ await provider.responses('test-deployment').doGenerate({
828
+ prompt: TEST_PROMPT,
829
+ });
830
+
831
+ expect(server.calls[0].requestUrl).toStrictEqual(
832
+ 'https://test-resource.openai.azure.com/openai/v1/responses?api-version=v1',
833
+ );
834
+ });
835
+
836
+ it('should handle Azure file IDs with assistant- prefix', async () => {
837
+ prepareJsonResponse({ content: 'I can see the image.' });
838
+
839
+ const TEST_PROMPT_WITH_AZURE_FILE: LanguageModelV3Prompt = [
840
+ {
841
+ role: 'user',
842
+ content: [
843
+ { type: 'text', text: 'Analyze this image' },
844
+ {
845
+ type: 'file',
846
+ mediaType: 'image/jpeg',
847
+ data: 'assistant-abc123',
848
+ },
849
+ ],
850
+ },
851
+ ];
852
+
853
+ await provider.responses('test-deployment').doGenerate({
854
+ prompt: TEST_PROMPT_WITH_AZURE_FILE,
855
+ });
856
+
857
+ const requestBody = await server.calls[0].requestBodyJson;
858
+ expect(requestBody.input).toEqual([
859
+ {
860
+ role: 'user',
861
+ content: [
862
+ { type: 'input_text', text: 'Analyze this image' },
863
+ { type: 'input_image', file_id: 'assistant-abc123' },
864
+ ],
865
+ },
866
+ ]);
867
+ });
868
+
869
+ it('should handle PDF files with assistant- prefix', async () => {
870
+ prepareJsonResponse({ content: 'I can analyze the PDF.' });
871
+
872
+ const TEST_PROMPT_WITH_AZURE_PDF: LanguageModelV3Prompt = [
873
+ {
874
+ role: 'user',
875
+ content: [
876
+ { type: 'text', text: 'Analyze this PDF' },
877
+ {
878
+ type: 'file',
879
+ mediaType: 'application/pdf',
880
+ data: 'assistant-pdf123',
881
+ },
882
+ ],
883
+ },
884
+ ];
885
+
886
+ await provider.responses('test-deployment').doGenerate({
887
+ prompt: TEST_PROMPT_WITH_AZURE_PDF,
888
+ });
889
+
890
+ const requestBody = await server.calls[0].requestBodyJson;
891
+ expect(requestBody.input).toEqual([
892
+ {
893
+ role: 'user',
894
+ content: [
895
+ { type: 'input_text', text: 'Analyze this PDF' },
896
+ { type: 'input_file', file_id: 'assistant-pdf123' },
897
+ ],
898
+ },
899
+ ]);
900
+ });
901
+
902
+ it('should fall back to base64 for non-assistant file IDs', async () => {
903
+ prepareJsonResponse({ content: 'I can see the image.' });
904
+
905
+ const TEST_PROMPT_WITH_OPENAI_FILE: LanguageModelV3Prompt = [
906
+ {
907
+ role: 'user',
908
+ content: [
909
+ { type: 'text', text: 'Analyze this image' },
910
+ {
911
+ type: 'file',
912
+ mediaType: 'image/jpeg',
913
+ data: 'file-abc123', // OpenAI prefix, should fall back to base64
914
+ },
915
+ ],
916
+ },
917
+ ];
918
+
919
+ await provider.responses('test-deployment').doGenerate({
920
+ prompt: TEST_PROMPT_WITH_OPENAI_FILE,
921
+ });
922
+
923
+ const requestBody = await server.calls[0].requestBodyJson;
924
+ expect(requestBody.input).toEqual([
925
+ {
926
+ role: 'user',
927
+ content: [
928
+ { type: 'input_text', text: 'Analyze this image' },
929
+ {
930
+ type: 'input_image',
931
+ image_url: 'data:image/jpeg;base64,file-abc123',
932
+ },
933
+ ],
934
+ },
935
+ ]);
936
+ });
937
+
938
+ it('should send include provider option for file search results', async () => {
939
+ prepareJsonResponse();
940
+
941
+ const { warnings } = await provider
942
+ .responses('test-deployment')
943
+ .doGenerate({
944
+ prompt: TEST_PROMPT,
945
+ tools: [
946
+ {
947
+ type: 'provider',
948
+ id: 'openai.file_search',
949
+ name: 'file_search',
950
+ args: {
951
+ vectorStoreIds: ['vs_123', 'vs_456'],
952
+ maxNumResults: 10,
953
+ ranking: {
954
+ ranker: 'auto',
955
+ },
956
+ },
957
+ },
958
+ ],
959
+ });
960
+
961
+ expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
962
+ {
963
+ "input": [
964
+ {
965
+ "content": [
966
+ {
967
+ "text": "Hello",
968
+ "type": "input_text",
969
+ },
970
+ ],
971
+ "role": "user",
972
+ },
973
+ ],
974
+ "model": "test-deployment",
975
+ "tools": [
976
+ {
977
+ "max_num_results": 10,
978
+ "ranking_options": {
979
+ "ranker": "auto",
980
+ },
981
+ "type": "file_search",
982
+ "vector_store_ids": [
983
+ "vs_123",
984
+ "vs_456",
985
+ ],
986
+ },
987
+ ],
988
+ }
989
+ `);
990
+
991
+ expect(warnings).toStrictEqual([]);
992
+ });
993
+
994
+ it('should forward include provider options to request body', async () => {
995
+ prepareJsonResponse();
996
+
997
+ const { warnings } = await provider
998
+ .responses('test-deployment')
999
+ .doGenerate({
1000
+ prompt: TEST_PROMPT,
1001
+ providerOptions: {
1002
+ azure: {
1003
+ include: ['file_search_call.results'],
1004
+ },
1005
+ },
1006
+ });
1007
+
1008
+ expect(await server.calls[0].requestBodyJson).toStrictEqual({
1009
+ model: 'test-deployment',
1010
+ input: [
1011
+ { role: 'user', content: [{ type: 'input_text', text: 'Hello' }] },
1012
+ ],
1013
+ include: ['file_search_call.results'],
1014
+ });
1015
+
1016
+ expect(warnings).toStrictEqual([]);
1017
+ });
1018
+
1019
+ describe('code interpreter tool', () => {
1020
+ let result: LanguageModelV3GenerateResult;
1021
+
1022
+ beforeEach(async () => {
1023
+ prepareJsonFixtureResponse('azure-code-interpreter-tool.1');
1024
+
1025
+ result = await createModel('test-deployment').doGenerate({
1026
+ prompt: TEST_PROMPT,
1027
+ tools: [
1028
+ {
1029
+ type: 'provider',
1030
+ id: 'openai.code_interpreter',
1031
+ name: 'code_interpreter',
1032
+ args: {},
1033
+ },
1034
+ ],
1035
+ });
1036
+ });
1037
+
1038
+ it('should send request body with include and tool', async () => {
1039
+ expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
1040
+ {
1041
+ "include": [
1042
+ "code_interpreter_call.outputs",
1043
+ ],
1044
+ "input": [
1045
+ {
1046
+ "content": [
1047
+ {
1048
+ "text": "Hello",
1049
+ "type": "input_text",
1050
+ },
1051
+ ],
1052
+ "role": "user",
1053
+ },
1054
+ ],
1055
+ "model": "test-deployment",
1056
+ "tools": [
1057
+ {
1058
+ "container": {
1059
+ "type": "auto",
1060
+ },
1061
+ "type": "code_interpreter",
1062
+ },
1063
+ ],
1064
+ }
1065
+ `);
1066
+ });
1067
+
1068
+ it('should include code interpreter tool call and result in content', async () => {
1069
+ expect(result.content).toMatchSnapshot();
1070
+ });
1071
+ });
1072
+
1073
+ describe('file search tool', () => {
1074
+ let result: LanguageModelV3GenerateResult;
1075
+
1076
+ describe('without results include', () => {
1077
+ beforeEach(async () => {
1078
+ prepareJsonFixtureResponse('openai-file-search-tool.1');
1079
+
1080
+ result = await createModel('test-deployment').doGenerate({
1081
+ prompt: TEST_PROMPT,
1082
+ tools: [
1083
+ {
1084
+ type: 'provider',
1085
+ id: 'openai.file_search',
1086
+ name: 'file_search',
1087
+ args: {
1088
+ vectorStoreIds: ['vs_68caad8bd5d88191ab766cf043d89a18'],
1089
+ maxNumResults: 5,
1090
+ filters: {
1091
+ key: 'author',
1092
+ type: 'eq',
1093
+ value: 'Jane Smith',
1094
+ },
1095
+ ranking: {
1096
+ ranker: 'auto',
1097
+ scoreThreshold: 0.5,
1098
+ },
1099
+ },
1100
+ },
1101
+ ],
1102
+ });
1103
+ });
1104
+
1105
+ it('should send request body with tool', async () => {
1106
+ expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
1107
+ {
1108
+ "input": [
1109
+ {
1110
+ "content": [
1111
+ {
1112
+ "text": "Hello",
1113
+ "type": "input_text",
1114
+ },
1115
+ ],
1116
+ "role": "user",
1117
+ },
1118
+ ],
1119
+ "model": "test-deployment",
1120
+ "tools": [
1121
+ {
1122
+ "filters": {
1123
+ "key": "author",
1124
+ "type": "eq",
1125
+ "value": "Jane Smith",
1126
+ },
1127
+ "max_num_results": 5,
1128
+ "ranking_options": {
1129
+ "ranker": "auto",
1130
+ "score_threshold": 0.5,
1131
+ },
1132
+ "type": "file_search",
1133
+ "vector_store_ids": [
1134
+ "vs_68caad8bd5d88191ab766cf043d89a18",
1135
+ ],
1136
+ },
1137
+ ],
1138
+ }
1139
+ `);
1140
+ });
1141
+
1142
+ it('should include file search tool call and result in content', async () => {
1143
+ expect(result.content).toMatchSnapshot();
1144
+ });
1145
+ });
1146
+
1147
+ describe('with results include', () => {
1148
+ beforeEach(async () => {
1149
+ prepareJsonFixtureResponse('openai-file-search-tool.2');
1150
+
1151
+ result = await createModel('test-deployment').doGenerate({
1152
+ prompt: TEST_PROMPT,
1153
+ tools: [
1154
+ {
1155
+ type: 'provider',
1156
+ id: 'openai.file_search',
1157
+ name: 'file_search',
1158
+ args: {
1159
+ vectorStoreIds: ['vs_68caad8bd5d88191ab766cf043d89a18'],
1160
+ maxNumResults: 5,
1161
+ filters: {
1162
+ key: 'author',
1163
+ type: 'eq',
1164
+ value: 'Jane Smith',
1165
+ },
1166
+ ranking: {
1167
+ ranker: 'auto',
1168
+ scoreThreshold: 0.5,
1169
+ },
1170
+ },
1171
+ },
1172
+ ],
1173
+ providerOptions: {
1174
+ azure: {
1175
+ include: ['file_search_call.results'],
1176
+ },
1177
+ },
1178
+ });
1179
+ });
1180
+
1181
+ it('should send request body with tool', async () => {
1182
+ expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
1183
+ {
1184
+ "include": [
1185
+ "file_search_call.results",
1186
+ ],
1187
+ "input": [
1188
+ {
1189
+ "content": [
1190
+ {
1191
+ "text": "Hello",
1192
+ "type": "input_text",
1193
+ },
1194
+ ],
1195
+ "role": "user",
1196
+ },
1197
+ ],
1198
+ "model": "test-deployment",
1199
+ "tools": [
1200
+ {
1201
+ "filters": {
1202
+ "key": "author",
1203
+ "type": "eq",
1204
+ "value": "Jane Smith",
1205
+ },
1206
+ "max_num_results": 5,
1207
+ "ranking_options": {
1208
+ "ranker": "auto",
1209
+ "score_threshold": 0.5,
1210
+ },
1211
+ "type": "file_search",
1212
+ "vector_store_ids": [
1213
+ "vs_68caad8bd5d88191ab766cf043d89a18",
1214
+ ],
1215
+ },
1216
+ ],
1217
+ }
1218
+ `);
1219
+ });
1220
+
1221
+ it('should include file search tool call and result in content', async () => {
1222
+ expect(result.content).toMatchSnapshot();
1223
+ });
1224
+ });
1225
+ });
1226
+
1227
+ describe('web search preview tool', () => {
1228
+ let result: LanguageModelV3GenerateResult;
1229
+
1230
+ beforeEach(async () => {
1231
+ prepareJsonFixtureResponse('azure-web-search-preview-tool.1');
1232
+
1233
+ result = await createModel('test-deployment').doGenerate({
1234
+ prompt: TEST_PROMPT,
1235
+ tools: [
1236
+ {
1237
+ type: 'provider',
1238
+ id: 'openai.web_search_preview',
1239
+ name: 'web_search_preview',
1240
+ args: {},
1241
+ },
1242
+ ],
1243
+ });
1244
+ });
1245
+ it('should stream web search preview results include', async () => {
1246
+ expect(result.content).toMatchSnapshot();
1247
+ });
1248
+ });
1249
+
1250
+ describe('reasoning', async () => {
1251
+ let result: Awaited<ReturnType<LanguageModelV3['doGenerate']>>;
1252
+ beforeEach(async () => {
1253
+ prepareJsonFixtureResponse('azure-reasoning-encrypted-content.1');
1254
+
1255
+ result = await createModel('test-deployment').doGenerate({
1256
+ prompt: TEST_PROMPT,
1257
+ tools: [
1258
+ {
1259
+ type: 'function',
1260
+ name: 'calculator',
1261
+ inputSchema: {
1262
+ type: 'object',
1263
+ properties: {
1264
+ a: { type: 'number' },
1265
+ b: { type: 'number' },
1266
+ op: { type: 'string' },
1267
+ },
1268
+ required: ['a', 'b'],
1269
+ additionalProperties: false,
1270
+ },
1271
+ },
1272
+ ],
1273
+ providerOptions: {
1274
+ azure: {
1275
+ reasoningEffort: 'high',
1276
+ maxCompletionTokens: 32_000,
1277
+ store: false,
1278
+ include: ['reasoning.encrypted_content'],
1279
+ reasoningSummary: 'auto',
1280
+ forceReasoning: true,
1281
+ },
1282
+ },
1283
+ });
1284
+ });
1285
+ it('should generate with reasoning encrypted content', async () => {
1286
+ expect(result).toMatchSnapshot();
1287
+ });
1288
+ });
1289
+ });
1290
+
1291
+ describe('image generation tool', () => {
1292
+ let result: LanguageModelV3GenerateResult;
1293
+
1294
+ beforeEach(async () => {
1295
+ prepareJsonFixtureResponse('azure-image-generation-tool.1');
1296
+
1297
+ result = await createModel('test-deployment').doGenerate({
1298
+ prompt: TEST_PROMPT,
1299
+ tools: [
1300
+ {
1301
+ type: 'provider',
1302
+ id: 'openai.image_generation',
1303
+ name: 'image_generation',
1304
+ args: {
1305
+ outputFormat: 'webp',
1306
+ quality: 'low',
1307
+ size: '1024x1024',
1308
+ partialImages: 2,
1309
+ },
1310
+ },
1311
+ ],
1312
+ });
1313
+ });
1314
+
1315
+ it('should send request body with include and tool', async () => {
1316
+ expect(await server.calls[0].requestBodyJson).toMatchInlineSnapshot(`
1317
+ {
1318
+ "input": [
1319
+ {
1320
+ "content": [
1321
+ {
1322
+ "text": "Hello",
1323
+ "type": "input_text",
1324
+ },
1325
+ ],
1326
+ "role": "user",
1327
+ },
1328
+ ],
1329
+ "model": "test-deployment",
1330
+ "tools": [
1331
+ {
1332
+ "output_format": "webp",
1333
+ "partial_images": 2,
1334
+ "quality": "low",
1335
+ "size": "1024x1024",
1336
+ "type": "image_generation",
1337
+ },
1338
+ ],
1339
+ }
1340
+ `);
1341
+ });
1342
+
1343
+ it('should include generate image tool call and result in content', async () => {
1344
+ expect(result.content).toMatchSnapshot();
1345
+ });
1346
+ });
1347
+
1348
+ describe('doStream', () => {
1349
+ it('should stream text deltas', async () => {
1350
+ server.urls[
1351
+ 'https://test-resource.openai.azure.com/openai/v1/responses'
1352
+ ].response = {
1353
+ type: 'stream-chunks',
1354
+ chunks: [
1355
+ `data:{"type":"response.created","response":{"id":"resp_67c9a81b6a048190a9ee441c5755a4e8","object":"response","created_at":1741269019,"status":"in_progress","error":null,"incomplete_details":null,"input":[],"instructions":null,"max_output_tokens":null,"model":"test-deployment","output":[],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":null,"summary":null},"store":true,"temperature":0.3,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_p":1,"truncation":"disabled","usage":null,"user":null,"metadata":{}}}\n\n`,
1356
+ `data:{"type":"response.in_progress","response":{"id":"resp_67c9a81b6a048190a9ee441c5755a4e8","object":"response","created_at":1741269019,"status":"in_progress","error":null,"incomplete_details":null,"input":[],"instructions":null,"max_output_tokens":null,"model":"test-deployment","output":[],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":null,"summary":null},"store":true,"temperature":0.3,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_p":1,"truncation":"disabled","usage":null,"user":null,"metadata":{}}}\n\n`,
1357
+ `data:{"type":"response.output_item.added","output_index":0,"item":{"id":"msg_67c9a81dea8c8190b79651a2b3adf91e","type":"message","status":"in_progress","role":"assistant","content":[]}}\n\n`,
1358
+ `data:{"type":"response.content_part.added","item_id":"msg_67c9a81dea8c8190b79651a2b3adf91e","output_index":0,"content_index":0,"part":{"type":"output_text","text":"","annotations":[],"logprobs": []}}\n\n`,
1359
+ `data:{"type":"response.output_text.delta","item_id":"msg_67c9a81dea8c8190b79651a2b3adf91e","output_index":0,"content_index":0,"delta":"Hello,","logprobs": []}\n\n`,
1360
+ `data:{"type":"response.output_text.delta","item_id":"msg_67c9a81dea8c8190b79651a2b3adf91e","output_index":0,"content_index":0,"delta":" World!","logprobs": []}\n\n`,
1361
+ `data:{"type":"response.output_text.done","item_id":"msg_67c9a8787f4c8190b49c858d4c1cf20c","output_index":0,"content_index":0,"text":"Hello, World!"}\n\n`,
1362
+ `data:{"type":"response.content_part.done","item_id":"msg_67c9a8787f4c8190b49c858d4c1cf20c","output_index":0,"content_index":0,"part":{"type":"output_text","text":"Hello, World!","annotations":[],"logprobs": []}}\n\n`,
1363
+ `data:{"type":"response.output_item.done","output_index":0,"item":{"id":"msg_67c9a8787f4c8190b49c858d4c1cf20c","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"Hello, World!","annotations":[],"logprobs": []}]}}\n\n`,
1364
+ `data:{"type":"response.completed","response":{"id":"resp_67c9a878139c8190aa2e3105411b408b","object":"response","created_at":1741269112,"status":"completed","error":null,"incomplete_details":null,"input":[],"instructions":null,"max_output_tokens":null,"model":"test-deployment","output":[{"id":"msg_67c9a8787f4c8190b49c858d4c1cf20c","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"Hello, World!","annotations":[]}]}],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":null,"summary":null},"store":true,"temperature":0.3,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_p":1,"truncation":"disabled","usage":{"input_tokens":543,"input_tokens_details":{"cached_tokens":234},"output_tokens":478,"output_tokens_details":{"reasoning_tokens":123},"total_tokens":512},"user":null,"metadata":{}}}\n\n`,
1365
+ ],
1366
+ };
1367
+
1368
+ const { stream } = await createModel('test-deployment').doStream({
1369
+ prompt: TEST_PROMPT,
1370
+ includeRawChunks: false,
1371
+ });
1372
+
1373
+ expect(await convertReadableStreamToArray(stream)).toMatchInlineSnapshot(`
1374
+ [
1375
+ {
1376
+ "type": "stream-start",
1377
+ "warnings": [],
1378
+ },
1379
+ {
1380
+ "id": "resp_67c9a81b6a048190a9ee441c5755a4e8",
1381
+ "modelId": "test-deployment",
1382
+ "timestamp": 2025-03-06T13:50:19.000Z,
1383
+ "type": "response-metadata",
1384
+ },
1385
+ {
1386
+ "id": "msg_67c9a81dea8c8190b79651a2b3adf91e",
1387
+ "providerMetadata": {
1388
+ "azure": {
1389
+ "itemId": "msg_67c9a81dea8c8190b79651a2b3adf91e",
1390
+ },
1391
+ },
1392
+ "type": "text-start",
1393
+ },
1394
+ {
1395
+ "delta": "Hello,",
1396
+ "id": "msg_67c9a81dea8c8190b79651a2b3adf91e",
1397
+ "type": "text-delta",
1398
+ },
1399
+ {
1400
+ "delta": " World!",
1401
+ "id": "msg_67c9a81dea8c8190b79651a2b3adf91e",
1402
+ "type": "text-delta",
1403
+ },
1404
+ {
1405
+ "id": "msg_67c9a8787f4c8190b49c858d4c1cf20c",
1406
+ "providerMetadata": {
1407
+ "azure": {
1408
+ "itemId": "msg_67c9a8787f4c8190b49c858d4c1cf20c",
1409
+ },
1410
+ },
1411
+ "type": "text-end",
1412
+ },
1413
+ {
1414
+ "finishReason": {
1415
+ "raw": undefined,
1416
+ "unified": "stop",
1417
+ },
1418
+ "providerMetadata": {
1419
+ "azure": {
1420
+ "responseId": "resp_67c9a81b6a048190a9ee441c5755a4e8",
1421
+ },
1422
+ },
1423
+ "type": "finish",
1424
+ "usage": {
1425
+ "inputTokens": {
1426
+ "cacheRead": 234,
1427
+ "cacheWrite": undefined,
1428
+ "noCache": 309,
1429
+ "total": 543,
1430
+ },
1431
+ "outputTokens": {
1432
+ "reasoning": 123,
1433
+ "text": 355,
1434
+ "total": 478,
1435
+ },
1436
+ "raw": {
1437
+ "input_tokens": 543,
1438
+ "input_tokens_details": {
1439
+ "cached_tokens": 234,
1440
+ },
1441
+ "output_tokens": 478,
1442
+ "output_tokens_details": {
1443
+ "reasoning_tokens": 123,
1444
+ },
1445
+ },
1446
+ },
1447
+ },
1448
+ ]
1449
+ `);
1450
+ });
1451
+
1452
+ it('should send streaming tool calls', async () => {
1453
+ server.urls[
1454
+ 'https://test-resource.openai.azure.com/openai/v1/responses'
1455
+ ].response = {
1456
+ type: 'stream-chunks',
1457
+ chunks: [
1458
+ `data:{"type":"response.created","response":{"id":"resp_67cb13a755c08190acbe3839a49632fc","object":"response","created_at":1741362087,"status":"in_progress","error":null,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"model":"test-deployment","output":[],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":null,"summary":null},"store":true,"temperature":0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[{"type":"function","description":"Get the current location.","name":"currentLocation","parameters":{"type":"object","properties":{},"additionalProperties":false},"strict":true},{"type":"function","description":"Get the weather in a location","name":"weather","parameters":{"type":"object","properties":{"location":{"type":"string","description":"The location to get the weather for"}},"required":["location"],"additionalProperties":false},"strict":true}],"top_p":1,"truncation":"disabled","usage":null,"user":null,"metadata":{}}}\n\n`,
1459
+ `data:{"type":"response.in_progress","response":{"id":"resp_67cb13a755c08190acbe3839a49632fc","object":"response","created_at":1741362087,"status":"in_progress","error":null,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"model":"test-deployment","output":[],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":null,"summary":null},"store":true,"temperature":0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[{"type":"function","description":"Get the current location.","name":"currentLocation","parameters":{"type":"object","properties":{},"additionalProperties":false},"strict":true},{"type":"function","description":"Get the weather in a location","name":"weather","parameters":{"type":"object","properties":{"location":{"type":"string","description":"The location to get the weather for"}},"required":["location"],"additionalProperties":false},"strict":true}],"top_p":1,"truncation":"disabled","usage":null,"user":null,"metadata":{}}}\n\n`,
1460
+ `data:{"type":"response.output_item.added","output_index":0,"item":{"type":"function_call","id":"fc_67cb13a838088190be08eb3927c87501","call_id":"call_6KxSghkb4MVnunFH2TxPErLP","name":"currentLocation","arguments":"","status":"completed"}}\n\n`,
1461
+ `data:{"type":"response.function_call_arguments.delta","item_id":"fc_67cb13a838088190be08eb3927c87501","output_index":0,"delta":"{}"}\n\n`,
1462
+ `data:{"type":"response.function_call_arguments.done","item_id":"fc_67cb13a838088190be08eb3927c87501","output_index":0,"arguments":"{}"}\n\n`,
1463
+ `data:{"type":"response.output_item.done","output_index":0,"item":{"type":"function_call","id":"fc_67cb13a838088190be08eb3927c87501","call_id":"call_pgjcAI4ZegMkP6bsAV7sfrJA","name":"currentLocation","arguments":"{}","status":"completed"}}\n\n`,
1464
+ `data:{"type":"response.output_item.added","output_index":1,"item":{"type":"function_call","id":"fc_67cb13a858f081908a600343fa040f47","call_id":"call_Dg6WUmFHNeR5JxX1s53s1G4b","name":"weather","arguments":"","status":"in_progress"}}\n\n`,
1465
+ `data:{"type":"response.function_call_arguments.delta","item_id":"fc_67cb13a858f081908a600343fa040f47","output_index":1,"delta":"{"}\n\n`,
1466
+ `data:{"type":"response.function_call_arguments.delta","item_id":"fc_67cb13a858f081908a600343fa040f47","output_index":1,"delta":"\\"location"}\n\n`,
1467
+ `data:{"type":"response.function_call_arguments.delta","item_id":"fc_67cb13a858f081908a600343fa040f47","output_index":1,"delta":"\\":"}\n\n`,
1468
+ `data:{"type":"response.function_call_arguments.delta","item_id":"fc_67cb13a858f081908a600343fa040f47","output_index":1,"delta":"\\"Rome"}\n\n`,
1469
+ `data:{"type":"response.function_call_arguments.delta","item_id":"fc_67cb13a858f081908a600343fa040f47","output_index":1,"delta":"\\"}"}\n\n`,
1470
+ `data:{"type":"response.function_call_arguments.done","item_id":"fc_67cb13a858f081908a600343fa040f47","output_index":1,"arguments":"{\\"location\\":\\"Rome\\"}"}\n\n`,
1471
+ `data:{"type":"response.output_item.done","output_index":1,"item":{"type":"function_call","id":"fc_67cb13a858f081908a600343fa040f47","call_id":"call_X2PAkDJInno9VVnNkDrfhboW","name":"weather","arguments":"{\\"location\\":\\"Rome\\"}","status":"completed"}}\n\n`,
1472
+ `data:{"type":"response.completed","response":{"id":"resp_67cb13a755c08190acbe3839a49632fc","object":"response","created_at":1741362087,"status":"completed","error":null,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"model":"test-deployment","output":[{"type":"function_call","id":"fc_67cb13a838088190be08eb3927c87501","call_id":"call_KsVqaVAf3alAtCCkQe4itE7W","name":"currentLocation","arguments":"{}","status":"completed"},{"type":"function_call","id":"fc_67cb13a858f081908a600343fa040f47","call_id":"call_X2PAkDJInno9VVnNkDrfhboW","name":"weather","arguments":"{\\"location\\":\\"Rome\\"}","status":"completed"}],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":null,"summary":null},"store":true,"temperature":0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[{"type":"function","description":"Get the current location.","name":"currentLocation","parameters":{"type":"object","properties":{},"additionalProperties":false},"strict":true},{"type":"function","description":"Get the weather in a location","name":"weather","parameters":{"type":"object","properties":{"location":{"type":"string","description":"The location to get the weather for"}},"required":["location"],"additionalProperties":false},"strict":true}],"top_p":1,"truncation":"disabled","usage":{"input_tokens":0,"input_tokens_details":{"cached_tokens":0},"output_tokens":0,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":0},"user":null,"metadata":{}}}\n\n`,
1473
+ ],
1474
+ };
1475
+
1476
+ const { stream } = await createModel('test-deployment').doStream({
1477
+ tools: TEST_TOOLS,
1478
+ prompt: TEST_PROMPT,
1479
+ includeRawChunks: false,
1480
+ });
1481
+
1482
+ expect(await convertReadableStreamToArray(stream)).toMatchInlineSnapshot(`
1483
+ [
1484
+ {
1485
+ "type": "stream-start",
1486
+ "warnings": [],
1487
+ },
1488
+ {
1489
+ "id": "resp_67cb13a755c08190acbe3839a49632fc",
1490
+ "modelId": "test-deployment",
1491
+ "timestamp": 2025-03-07T15:41:27.000Z,
1492
+ "type": "response-metadata",
1493
+ },
1494
+ {
1495
+ "id": "call_6KxSghkb4MVnunFH2TxPErLP",
1496
+ "toolName": "currentLocation",
1497
+ "type": "tool-input-start",
1498
+ },
1499
+ {
1500
+ "delta": "{}",
1501
+ "id": "call_6KxSghkb4MVnunFH2TxPErLP",
1502
+ "type": "tool-input-delta",
1503
+ },
1504
+ {
1505
+ "id": "call_pgjcAI4ZegMkP6bsAV7sfrJA",
1506
+ "type": "tool-input-end",
1507
+ },
1508
+ {
1509
+ "input": "{}",
1510
+ "providerMetadata": {
1511
+ "azure": {
1512
+ "itemId": "fc_67cb13a838088190be08eb3927c87501",
1513
+ },
1514
+ },
1515
+ "toolCallId": "call_pgjcAI4ZegMkP6bsAV7sfrJA",
1516
+ "toolName": "currentLocation",
1517
+ "type": "tool-call",
1518
+ },
1519
+ {
1520
+ "id": "call_Dg6WUmFHNeR5JxX1s53s1G4b",
1521
+ "toolName": "weather",
1522
+ "type": "tool-input-start",
1523
+ },
1524
+ {
1525
+ "delta": "{",
1526
+ "id": "call_Dg6WUmFHNeR5JxX1s53s1G4b",
1527
+ "type": "tool-input-delta",
1528
+ },
1529
+ {
1530
+ "delta": ""location",
1531
+ "id": "call_Dg6WUmFHNeR5JxX1s53s1G4b",
1532
+ "type": "tool-input-delta",
1533
+ },
1534
+ {
1535
+ "delta": "":",
1536
+ "id": "call_Dg6WUmFHNeR5JxX1s53s1G4b",
1537
+ "type": "tool-input-delta",
1538
+ },
1539
+ {
1540
+ "delta": ""Rome",
1541
+ "id": "call_Dg6WUmFHNeR5JxX1s53s1G4b",
1542
+ "type": "tool-input-delta",
1543
+ },
1544
+ {
1545
+ "delta": ""}",
1546
+ "id": "call_Dg6WUmFHNeR5JxX1s53s1G4b",
1547
+ "type": "tool-input-delta",
1548
+ },
1549
+ {
1550
+ "id": "call_X2PAkDJInno9VVnNkDrfhboW",
1551
+ "type": "tool-input-end",
1552
+ },
1553
+ {
1554
+ "input": "{"location":"Rome"}",
1555
+ "providerMetadata": {
1556
+ "azure": {
1557
+ "itemId": "fc_67cb13a858f081908a600343fa040f47",
1558
+ },
1559
+ },
1560
+ "toolCallId": "call_X2PAkDJInno9VVnNkDrfhboW",
1561
+ "toolName": "weather",
1562
+ "type": "tool-call",
1563
+ },
1564
+ {
1565
+ "finishReason": {
1566
+ "raw": undefined,
1567
+ "unified": "tool-calls",
1568
+ },
1569
+ "providerMetadata": {
1570
+ "azure": {
1571
+ "responseId": "resp_67cb13a755c08190acbe3839a49632fc",
1572
+ },
1573
+ },
1574
+ "type": "finish",
1575
+ "usage": {
1576
+ "inputTokens": {
1577
+ "cacheRead": 0,
1578
+ "cacheWrite": undefined,
1579
+ "noCache": 0,
1580
+ "total": 0,
1581
+ },
1582
+ "outputTokens": {
1583
+ "reasoning": 0,
1584
+ "text": 0,
1585
+ "total": 0,
1586
+ },
1587
+ "raw": {
1588
+ "input_tokens": 0,
1589
+ "input_tokens_details": {
1590
+ "cached_tokens": 0,
1591
+ },
1592
+ "output_tokens": 0,
1593
+ "output_tokens_details": {
1594
+ "reasoning_tokens": 0,
1595
+ },
1596
+ },
1597
+ },
1598
+ },
1599
+ ]
1600
+ `);
1601
+ });
1602
+
1603
+ it('should handle file_citation annotations without optional fields in streaming', async () => {
1604
+ server.urls[
1605
+ 'https://test-resource.openai.azure.com/openai/v1/responses'
1606
+ ].response = {
1607
+ type: 'stream-chunks',
1608
+ chunks: [
1609
+ `data:{"type":"response.content_part.added","item_id":"msg_456","output_index":0,"content_index":0,"part":{"type":"output_text","text":"","annotations":[]}}\n\n`,
1610
+ `data:{"type":"response.output_text.annotation.added","item_id":"msg_456","output_index":0,"content_index":0,"annotation_index":0,"annotation":{"type":"file_citation","file_id":"assistant-YRcoCqn3Fo2K4JgraG","filename":"resource1.json","index":145}}\n\n`,
1611
+ `data:{"type":"response.output_text.annotation.added","item_id":"msg_456","output_index":0,"content_index":0,"annotation_index":1,"annotation":{"type":"file_citation","file_id":"assistant-YRcoCqn3Fo2K4JgraG","filename":"resource1.json","index":192}}\n\n`,
1612
+ `data:{"type":"response.content_part.done","item_id":"msg_456","output_index":0,"content_index":0,"part":{"type":"output_text","text":"Answer for the specified years....","annotations":[{"type":"file_citation","file_id":"assistant-YRcoCqn3Fo2K4JgraG","filename":"resource1.json","index":145},{"type":"file_citation","file_id":"assistant-YRcoCqn3Fo2K4JgraG","filename":"resource1.json","index":192}]}}\n\n`,
1613
+ `data:{"type":"response.output_item.done","output_index":0,"item":{"id":"msg_456","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"Answer for the specified years....","annotations":[{"type":"file_citation","file_id":"assistant-YRcoCqn3Fo2K4JgraG","filename":"resource1.json","index":145},{"type":"file_citation","file_id":"assistant-YRcoCqn3Fo2K4JgraG","filename":"resource1.json","index":192}]}]}}\n\n`,
1614
+ `data:{"type":"response.completed","response":{"id":"resp_456","object":"response","created_at":1234567890,"status":"completed","error":null,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"model":"test-deployment","output":[{"id":"msg_456","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"Answer for the specified years....","annotations":[{"type":"file_citation","file_id":"assistant-YRcoCqn3Fo2K4JgraG","filename":"resource1.json","index":145},{"type":"file_citation","file_id":"assistant-YRcoCqn3Fo2K4JgraG","filename":"resource1.json","index":192}]}]}],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":null,"summary":null},"store":true,"temperature":0,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_p":1,"truncation":"disabled","usage":{"input_tokens":50,"input_tokens_details":{"cached_tokens":0},"output_tokens":25,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":75},"user":null,"metadata":{}}}\n\n`,
1615
+ 'data: [DONE]\n\n',
1616
+ ],
1617
+ };
1618
+
1619
+ const { stream } = await createModel('test-deployment').doStream({
1620
+ prompt: TEST_PROMPT,
1621
+ includeRawChunks: false,
1622
+ });
1623
+
1624
+ expect(await convertReadableStreamToArray(stream)).toMatchInlineSnapshot(`
1625
+ [
1626
+ {
1627
+ "type": "stream-start",
1628
+ "warnings": [],
1629
+ },
1630
+ {
1631
+ "filename": "resource1.json",
1632
+ "id": "id-0",
1633
+ "mediaType": "text/plain",
1634
+ "providerMetadata": {
1635
+ "azure": {
1636
+ "fileId": "assistant-YRcoCqn3Fo2K4JgraG",
1637
+ "index": 145,
1638
+ "type": "file_citation",
1639
+ },
1640
+ },
1641
+ "sourceType": "document",
1642
+ "title": "resource1.json",
1643
+ "type": "source",
1644
+ },
1645
+ {
1646
+ "filename": "resource1.json",
1647
+ "id": "id-1",
1648
+ "mediaType": "text/plain",
1649
+ "providerMetadata": {
1650
+ "azure": {
1651
+ "fileId": "assistant-YRcoCqn3Fo2K4JgraG",
1652
+ "index": 192,
1653
+ "type": "file_citation",
1654
+ },
1655
+ },
1656
+ "sourceType": "document",
1657
+ "title": "resource1.json",
1658
+ "type": "source",
1659
+ },
1660
+ {
1661
+ "id": "msg_456",
1662
+ "providerMetadata": {
1663
+ "azure": {
1664
+ "annotations": [
1665
+ {
1666
+ "file_id": "assistant-YRcoCqn3Fo2K4JgraG",
1667
+ "filename": "resource1.json",
1668
+ "index": 145,
1669
+ "type": "file_citation",
1670
+ },
1671
+ {
1672
+ "file_id": "assistant-YRcoCqn3Fo2K4JgraG",
1673
+ "filename": "resource1.json",
1674
+ "index": 192,
1675
+ "type": "file_citation",
1676
+ },
1677
+ ],
1678
+ "itemId": "msg_456",
1679
+ },
1680
+ },
1681
+ "type": "text-end",
1682
+ },
1683
+ {
1684
+ "finishReason": {
1685
+ "raw": undefined,
1686
+ "unified": "stop",
1687
+ },
1688
+ "providerMetadata": {
1689
+ "azure": {
1690
+ "responseId": null,
1691
+ },
1692
+ },
1693
+ "type": "finish",
1694
+ "usage": {
1695
+ "inputTokens": {
1696
+ "cacheRead": 0,
1697
+ "cacheWrite": undefined,
1698
+ "noCache": 50,
1699
+ "total": 50,
1700
+ },
1701
+ "outputTokens": {
1702
+ "reasoning": 0,
1703
+ "text": 25,
1704
+ "total": 25,
1705
+ },
1706
+ "raw": {
1707
+ "input_tokens": 50,
1708
+ "input_tokens_details": {
1709
+ "cached_tokens": 0,
1710
+ },
1711
+ "output_tokens": 25,
1712
+ "output_tokens_details": {
1713
+ "reasoning_tokens": 0,
1714
+ },
1715
+ },
1716
+ },
1717
+ },
1718
+ ]
1719
+ `);
1720
+ });
1721
+
1722
+ it('should send code interpreter calls', async () => {
1723
+ prepareChunksFixtureResponse('azure-code-interpreter-tool.1');
1724
+
1725
+ const result = await createModel('test-deployment').doStream({
1726
+ prompt: TEST_PROMPT,
1727
+ tools: [
1728
+ {
1729
+ type: 'provider',
1730
+ id: 'openai.code_interpreter',
1731
+ name: 'code_interpreter',
1732
+ args: {},
1733
+ },
1734
+ ],
1735
+ });
1736
+
1737
+ expect(
1738
+ await convertReadableStreamToArray(result.stream),
1739
+ ).toMatchSnapshot();
1740
+ });
1741
+
1742
+ it('should stream with reasoning encrypted content include reasoning-delta part', async () => {
1743
+ prepareChunksFixtureResponse('azure-reasoning-encrypted-content.1');
1744
+
1745
+ const result = await createModel('test-deployment').doStream({
1746
+ prompt: TEST_PROMPT,
1747
+ tools: [
1748
+ {
1749
+ type: 'function',
1750
+ name: 'calculator',
1751
+ inputSchema: {
1752
+ type: 'object',
1753
+ properties: {
1754
+ a: { type: 'number' },
1755
+ b: { type: 'number' },
1756
+ op: { type: 'string' },
1757
+ },
1758
+ required: ['a', 'b'],
1759
+ additionalProperties: false,
1760
+ },
1761
+ },
1762
+ ],
1763
+ providerOptions: {
1764
+ openai: {
1765
+ reasoningEffort: 'high',
1766
+ maxCompletionTokens: 32_000,
1767
+ store: false,
1768
+ include: ['reasoning.encrypted_content'],
1769
+ reasoningSummary: 'auto',
1770
+ forceReasoning: true,
1771
+ },
1772
+ },
1773
+ });
1774
+
1775
+ expect(
1776
+ await convertReadableStreamToArray(result.stream),
1777
+ ).toMatchSnapshot();
1778
+ });
1779
+ });
1780
+ describe('file search tool', () => {
1781
+ it('should stream file search results without results include', async () => {
1782
+ prepareChunksFixtureResponse('openai-file-search-tool.1');
1783
+
1784
+ const result = await createModel('test-deployment').doStream({
1785
+ prompt: TEST_PROMPT,
1786
+ tools: [
1787
+ {
1788
+ type: 'provider',
1789
+ id: 'openai.file_search',
1790
+ name: 'file_search',
1791
+ args: {
1792
+ vectorStoreIds: ['vs_68caad8bd5d88191ab766cf043d89a18'],
1793
+ },
1794
+ },
1795
+ ],
1796
+ });
1797
+
1798
+ expect(
1799
+ await convertReadableStreamToArray(result.stream),
1800
+ ).toMatchSnapshot();
1801
+ });
1802
+
1803
+ it('should stream file search results with results include', async () => {
1804
+ prepareChunksFixtureResponse('openai-file-search-tool.2');
1805
+
1806
+ const result = await createModel('test-deployment').doStream({
1807
+ prompt: TEST_PROMPT,
1808
+ tools: [
1809
+ {
1810
+ type: 'provider',
1811
+ id: 'openai.file_search',
1812
+ name: 'file_search',
1813
+ args: {
1814
+ vectorStoreIds: ['vs_68caad8bd5d88191ab766cf043d89a18'],
1815
+ },
1816
+ },
1817
+ ],
1818
+ providerOptions: {
1819
+ openai: {
1820
+ include: ['file_search_call.results'],
1821
+ },
1822
+ },
1823
+ });
1824
+
1825
+ expect(
1826
+ await convertReadableStreamToArray(result.stream),
1827
+ ).toMatchSnapshot();
1828
+ });
1829
+ });
1830
+ describe('web search preview tool', () => {
1831
+ it('should stream web search preview results include', async () => {
1832
+ prepareChunksFixtureResponse('azure-web-search-preview-tool.1');
1833
+ const result = await createModel('test-deployment').doStream({
1834
+ prompt: TEST_PROMPT,
1835
+ tools: [
1836
+ {
1837
+ type: 'provider',
1838
+ id: 'openai.web_search_preview',
1839
+ name: 'web_search_preview',
1840
+ args: {},
1841
+ },
1842
+ ],
1843
+ });
1844
+ expect(
1845
+ await convertReadableStreamToArray(result.stream),
1846
+ ).toMatchSnapshot();
1847
+ });
1848
+ });
1849
+ describe('image generation tool', () => {
1850
+ it('should stream image generation tool results include', async () => {
1851
+ prepareChunksFixtureResponse('azure-image-generation-tool.1');
1852
+ const result = await createModel('test-deployment').doStream({
1853
+ prompt: TEST_PROMPT,
1854
+ tools: [
1855
+ {
1856
+ type: 'provider',
1857
+ id: 'openai.image_generation',
1858
+ name: 'image_generation',
1859
+ args: {},
1860
+ },
1861
+ ],
1862
+ });
1863
+
1864
+ expect(
1865
+ await convertReadableStreamToArray(result.stream),
1866
+ ).toMatchSnapshot();
1867
+ });
1868
+ });
1869
+ });