@ai-sdk/xai 0.0.0-64aae7dd-20260114144918 → 0.0.0-98261322-20260122142521

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +64 -5
  2. package/dist/index.js +1 -1
  3. package/dist/index.mjs +1 -1
  4. package/docs/01-xai.mdx +697 -0
  5. package/package.json +11 -6
  6. package/src/convert-to-xai-chat-messages.test.ts +243 -0
  7. package/src/convert-to-xai-chat-messages.ts +142 -0
  8. package/src/convert-xai-chat-usage.test.ts +240 -0
  9. package/src/convert-xai-chat-usage.ts +23 -0
  10. package/src/get-response-metadata.ts +19 -0
  11. package/src/index.ts +14 -0
  12. package/src/map-xai-finish-reason.ts +19 -0
  13. package/src/responses/__fixtures__/xai-code-execution-tool.1.json +68 -0
  14. package/src/responses/__fixtures__/xai-text-streaming.1.chunks.txt +698 -0
  15. package/src/responses/__fixtures__/xai-text-with-reasoning-streaming-store-false.1.chunks.txt +655 -0
  16. package/src/responses/__fixtures__/xai-text-with-reasoning-streaming.1.chunks.txt +679 -0
  17. package/src/responses/__fixtures__/xai-web-search-tool.1.chunks.txt +274 -0
  18. package/src/responses/__fixtures__/xai-web-search-tool.1.json +90 -0
  19. package/src/responses/__fixtures__/xai-x-search-tool.1.json +149 -0
  20. package/src/responses/__fixtures__/xai-x-search-tool.chunks.txt +1757 -0
  21. package/src/responses/__snapshots__/xai-responses-language-model.test.ts.snap +21929 -0
  22. package/src/responses/convert-to-xai-responses-input.test.ts +463 -0
  23. package/src/responses/convert-to-xai-responses-input.ts +206 -0
  24. package/src/responses/convert-xai-responses-usage.ts +24 -0
  25. package/src/responses/map-xai-responses-finish-reason.ts +20 -0
  26. package/src/responses/xai-responses-api.ts +393 -0
  27. package/src/responses/xai-responses-language-model.test.ts +1803 -0
  28. package/src/responses/xai-responses-language-model.ts +732 -0
  29. package/src/responses/xai-responses-options.ts +34 -0
  30. package/src/responses/xai-responses-prepare-tools.test.ts +497 -0
  31. package/src/responses/xai-responses-prepare-tools.ts +226 -0
  32. package/src/tool/code-execution.ts +17 -0
  33. package/src/tool/index.ts +15 -0
  34. package/src/tool/view-image.ts +20 -0
  35. package/src/tool/view-x-video.ts +18 -0
  36. package/src/tool/web-search.ts +56 -0
  37. package/src/tool/x-search.ts +63 -0
  38. package/src/version.ts +6 -0
  39. package/src/xai-chat-language-model.test.ts +1805 -0
  40. package/src/xai-chat-language-model.ts +681 -0
  41. package/src/xai-chat-options.ts +131 -0
  42. package/src/xai-chat-prompt.ts +44 -0
  43. package/src/xai-error.ts +19 -0
  44. package/src/xai-image-settings.ts +1 -0
  45. package/src/xai-prepare-tools.ts +95 -0
  46. package/src/xai-provider.test.ts +167 -0
  47. package/src/xai-provider.ts +162 -0
@@ -0,0 +1,732 @@
1
+ import {
2
+ LanguageModelV3,
3
+ LanguageModelV3CallOptions,
4
+ LanguageModelV3Content,
5
+ LanguageModelV3FinishReason,
6
+ LanguageModelV3GenerateResult,
7
+ LanguageModelV3StreamPart,
8
+ LanguageModelV3StreamResult,
9
+ LanguageModelV3Usage,
10
+ SharedV3Warning,
11
+ } from '@ai-sdk/provider';
12
+ import {
13
+ combineHeaders,
14
+ createEventSourceResponseHandler,
15
+ createJsonResponseHandler,
16
+ FetchFunction,
17
+ parseProviderOptions,
18
+ ParseResult,
19
+ postJsonToApi,
20
+ } from '@ai-sdk/provider-utils';
21
+ import { z } from 'zod/v4';
22
+ import { getResponseMetadata } from '../get-response-metadata';
23
+ import { xaiFailedResponseHandler } from '../xai-error';
24
+ import { convertToXaiResponsesInput } from './convert-to-xai-responses-input';
25
+ import { convertXaiResponsesUsage } from './convert-xai-responses-usage';
26
+ import { mapXaiResponsesFinishReason } from './map-xai-responses-finish-reason';
27
+ import {
28
+ xaiResponsesChunkSchema,
29
+ xaiResponsesResponseSchema,
30
+ } from './xai-responses-api';
31
+ import {
32
+ XaiResponsesModelId,
33
+ xaiResponsesProviderOptions,
34
+ } from './xai-responses-options';
35
+ import { prepareResponsesTools } from './xai-responses-prepare-tools';
36
+
37
+ type XaiResponsesConfig = {
38
+ provider: string;
39
+ baseURL: string | undefined;
40
+ headers: () => Record<string, string | undefined>;
41
+ generateId: () => string;
42
+ fetch?: FetchFunction;
43
+ };
44
+
45
+ export class XaiResponsesLanguageModel implements LanguageModelV3 {
46
+ readonly specificationVersion = 'v3';
47
+
48
+ readonly modelId: XaiResponsesModelId;
49
+
50
+ private readonly config: XaiResponsesConfig;
51
+
52
+ constructor(modelId: XaiResponsesModelId, config: XaiResponsesConfig) {
53
+ this.modelId = modelId;
54
+ this.config = config;
55
+ }
56
+
57
+ get provider(): string {
58
+ return this.config.provider;
59
+ }
60
+
61
+ readonly supportedUrls: Record<string, RegExp[]> = {
62
+ 'image/*': [/^https?:\/\/.*$/],
63
+ };
64
+
65
+ private async getArgs({
66
+ prompt,
67
+ maxOutputTokens,
68
+ temperature,
69
+ topP,
70
+ stopSequences,
71
+ seed,
72
+ responseFormat,
73
+ providerOptions,
74
+ tools,
75
+ toolChoice,
76
+ }: LanguageModelV3CallOptions) {
77
+ const warnings: SharedV3Warning[] = [];
78
+
79
+ const options =
80
+ (await parseProviderOptions({
81
+ provider: 'xai',
82
+ providerOptions,
83
+ schema: xaiResponsesProviderOptions,
84
+ })) ?? {};
85
+
86
+ if (stopSequences != null) {
87
+ warnings.push({ type: 'unsupported', feature: 'stopSequences' });
88
+ }
89
+
90
+ const webSearchToolName = tools?.find(
91
+ tool => tool.type === 'provider' && tool.id === 'xai.web_search',
92
+ )?.name;
93
+
94
+ const xSearchToolName = tools?.find(
95
+ tool => tool.type === 'provider' && tool.id === 'xai.x_search',
96
+ )?.name;
97
+
98
+ const codeExecutionToolName = tools?.find(
99
+ tool => tool.type === 'provider' && tool.id === 'xai.code_execution',
100
+ )?.name;
101
+
102
+ const { input, inputWarnings } = await convertToXaiResponsesInput({
103
+ prompt,
104
+ store: true,
105
+ });
106
+ warnings.push(...inputWarnings);
107
+
108
+ const {
109
+ tools: xaiTools,
110
+ toolChoice: xaiToolChoice,
111
+ toolWarnings,
112
+ } = await prepareResponsesTools({
113
+ tools,
114
+ toolChoice,
115
+ });
116
+ warnings.push(...toolWarnings);
117
+
118
+ const baseArgs: Record<string, unknown> = {
119
+ model: this.modelId,
120
+ input,
121
+ max_output_tokens: maxOutputTokens,
122
+ temperature,
123
+ top_p: topP,
124
+ seed,
125
+ ...(responseFormat?.type === 'json' && {
126
+ text: {
127
+ format:
128
+ responseFormat.schema != null
129
+ ? {
130
+ type: 'json_schema',
131
+ strict: true,
132
+ name: responseFormat.name ?? 'response',
133
+ description: responseFormat.description,
134
+ schema: responseFormat.schema,
135
+ }
136
+ : { type: 'json_object' },
137
+ },
138
+ }),
139
+ ...(options.reasoningEffort != null && {
140
+ reasoning: { effort: options.reasoningEffort },
141
+ }),
142
+ ...(options.store === false && {
143
+ store: options.store,
144
+ include: ['reasoning.encrypted_content'],
145
+ }),
146
+ ...(options.previousResponseId != null && {
147
+ previous_response_id: options.previousResponseId,
148
+ }),
149
+ };
150
+
151
+ if (xaiTools && xaiTools.length > 0) {
152
+ baseArgs.tools = xaiTools;
153
+ }
154
+
155
+ if (xaiToolChoice != null) {
156
+ baseArgs.tool_choice = xaiToolChoice;
157
+ }
158
+
159
+ return {
160
+ args: baseArgs,
161
+ warnings,
162
+ webSearchToolName,
163
+ xSearchToolName,
164
+ codeExecutionToolName,
165
+ };
166
+ }
167
+
168
+ async doGenerate(
169
+ options: LanguageModelV3CallOptions,
170
+ ): Promise<LanguageModelV3GenerateResult> {
171
+ const {
172
+ args: body,
173
+ warnings,
174
+ webSearchToolName,
175
+ xSearchToolName,
176
+ codeExecutionToolName,
177
+ } = await this.getArgs(options);
178
+
179
+ const {
180
+ responseHeaders,
181
+ value: response,
182
+ rawValue: rawResponse,
183
+ } = await postJsonToApi({
184
+ url: `${this.config.baseURL ?? 'https://api.x.ai/v1'}/responses`,
185
+ headers: combineHeaders(this.config.headers(), options.headers),
186
+ body,
187
+ failedResponseHandler: xaiFailedResponseHandler,
188
+ successfulResponseHandler: createJsonResponseHandler(
189
+ xaiResponsesResponseSchema,
190
+ ),
191
+ abortSignal: options.abortSignal,
192
+ fetch: this.config.fetch,
193
+ });
194
+
195
+ const content: Array<LanguageModelV3Content> = [];
196
+
197
+ const webSearchSubTools = [
198
+ 'web_search',
199
+ 'web_search_with_snippets',
200
+ 'browse_page',
201
+ ];
202
+ const xSearchSubTools = [
203
+ 'x_user_search',
204
+ 'x_keyword_search',
205
+ 'x_semantic_search',
206
+ 'x_thread_fetch',
207
+ ];
208
+
209
+ for (const part of response.output) {
210
+ if (
211
+ part.type === 'web_search_call' ||
212
+ part.type === 'x_search_call' ||
213
+ part.type === 'code_interpreter_call' ||
214
+ part.type === 'code_execution_call' ||
215
+ part.type === 'view_image_call' ||
216
+ part.type === 'view_x_video_call' ||
217
+ part.type === 'custom_tool_call'
218
+ ) {
219
+ let toolName = part.name ?? '';
220
+ if (
221
+ webSearchSubTools.includes(part.name ?? '') ||
222
+ part.type === 'web_search_call'
223
+ ) {
224
+ toolName = webSearchToolName ?? 'web_search';
225
+ } else if (
226
+ xSearchSubTools.includes(part.name ?? '') ||
227
+ part.type === 'x_search_call'
228
+ ) {
229
+ toolName = xSearchToolName ?? 'x_search';
230
+ } else if (
231
+ part.name === 'code_execution' ||
232
+ part.type === 'code_interpreter_call' ||
233
+ part.type === 'code_execution_call'
234
+ ) {
235
+ toolName = codeExecutionToolName ?? 'code_execution';
236
+ }
237
+
238
+ // custom_tool_call uses 'input' field, others use 'arguments'
239
+ const toolInput =
240
+ part.type === 'custom_tool_call'
241
+ ? (part.input ?? '')
242
+ : (part.arguments ?? '');
243
+
244
+ content.push({
245
+ type: 'tool-call',
246
+ toolCallId: part.id,
247
+ toolName,
248
+ input: toolInput,
249
+ providerExecuted: true,
250
+ });
251
+
252
+ continue;
253
+ }
254
+
255
+ switch (part.type) {
256
+ case 'message': {
257
+ for (const contentPart of part.content) {
258
+ if (contentPart.text) {
259
+ content.push({
260
+ type: 'text',
261
+ text: contentPart.text,
262
+ });
263
+ }
264
+
265
+ if (contentPart.annotations) {
266
+ for (const annotation of contentPart.annotations) {
267
+ if (annotation.type === 'url_citation' && 'url' in annotation) {
268
+ content.push({
269
+ type: 'source',
270
+ sourceType: 'url',
271
+ id: this.config.generateId(),
272
+ url: annotation.url,
273
+ title: annotation.title ?? annotation.url,
274
+ });
275
+ }
276
+ }
277
+ }
278
+ }
279
+
280
+ break;
281
+ }
282
+
283
+ case 'function_call': {
284
+ content.push({
285
+ type: 'tool-call',
286
+ toolCallId: part.call_id,
287
+ toolName: part.name,
288
+ input: part.arguments,
289
+ });
290
+ break;
291
+ }
292
+
293
+ case 'reasoning': {
294
+ const summaryTexts = part.summary
295
+ .map(s => s.text)
296
+ .filter(text => text && text.length > 0);
297
+
298
+ if (summaryTexts.length > 0) {
299
+ const reasoningText = summaryTexts.join('');
300
+ if (part.encrypted_content || part.id) {
301
+ content.push({
302
+ type: 'reasoning',
303
+ text: reasoningText,
304
+ providerMetadata: {
305
+ xai: {
306
+ ...(part.encrypted_content && {
307
+ reasoningEncryptedContent: part.encrypted_content,
308
+ }),
309
+ ...(part.id && { itemId: part.id }),
310
+ },
311
+ },
312
+ });
313
+ } else {
314
+ content.push({
315
+ type: 'reasoning',
316
+ text: reasoningText,
317
+ });
318
+ }
319
+ }
320
+ break;
321
+ }
322
+
323
+ default: {
324
+ break;
325
+ }
326
+ }
327
+ }
328
+
329
+ return {
330
+ content,
331
+ finishReason: {
332
+ unified: mapXaiResponsesFinishReason(response.status),
333
+ raw: response.status ?? undefined,
334
+ },
335
+ usage: convertXaiResponsesUsage(response.usage),
336
+ request: { body },
337
+ response: {
338
+ ...getResponseMetadata(response),
339
+ headers: responseHeaders,
340
+ body: rawResponse,
341
+ },
342
+ warnings,
343
+ };
344
+ }
345
+
346
+ async doStream(
347
+ options: LanguageModelV3CallOptions,
348
+ ): Promise<LanguageModelV3StreamResult> {
349
+ const {
350
+ args,
351
+ warnings,
352
+ webSearchToolName,
353
+ xSearchToolName,
354
+ codeExecutionToolName,
355
+ } = await this.getArgs(options);
356
+ const body = {
357
+ ...args,
358
+ stream: true,
359
+ };
360
+
361
+ const { responseHeaders, value: response } = await postJsonToApi({
362
+ url: `${this.config.baseURL ?? 'https://api.x.ai/v1'}/responses`,
363
+ headers: combineHeaders(this.config.headers(), options.headers),
364
+ body,
365
+ failedResponseHandler: xaiFailedResponseHandler,
366
+ successfulResponseHandler: createEventSourceResponseHandler(
367
+ xaiResponsesChunkSchema,
368
+ ),
369
+ abortSignal: options.abortSignal,
370
+ fetch: this.config.fetch,
371
+ });
372
+
373
+ let finishReason: LanguageModelV3FinishReason = {
374
+ unified: 'other',
375
+ raw: undefined,
376
+ };
377
+ let usage: LanguageModelV3Usage | undefined = undefined;
378
+ let isFirstChunk = true;
379
+ const contentBlocks: Record<string, { type: 'text' }> = {};
380
+ const seenToolCalls = new Set<string>();
381
+
382
+ const activeReasoning: Record<
383
+ string,
384
+ { encryptedContent?: string | null }
385
+ > = {};
386
+
387
+ const self = this;
388
+
389
+ return {
390
+ stream: response.pipeThrough(
391
+ new TransformStream<
392
+ ParseResult<z.infer<typeof xaiResponsesChunkSchema>>,
393
+ LanguageModelV3StreamPart
394
+ >({
395
+ start(controller) {
396
+ controller.enqueue({ type: 'stream-start', warnings });
397
+ },
398
+
399
+ transform(chunk, controller) {
400
+ if (options.includeRawChunks) {
401
+ controller.enqueue({ type: 'raw', rawValue: chunk.rawValue });
402
+ }
403
+
404
+ if (!chunk.success) {
405
+ controller.enqueue({ type: 'error', error: chunk.error });
406
+ return;
407
+ }
408
+
409
+ const event = chunk.value;
410
+
411
+ if (
412
+ event.type === 'response.created' ||
413
+ event.type === 'response.in_progress'
414
+ ) {
415
+ if (isFirstChunk) {
416
+ controller.enqueue({
417
+ type: 'response-metadata',
418
+ ...getResponseMetadata(event.response),
419
+ });
420
+ isFirstChunk = false;
421
+ }
422
+ return;
423
+ }
424
+
425
+ if (event.type === 'response.reasoning_summary_part.added') {
426
+ const blockId = `reasoning-${event.item_id}`;
427
+
428
+ controller.enqueue({
429
+ type: 'reasoning-start',
430
+ id: blockId,
431
+ providerMetadata: {
432
+ xai: {
433
+ itemId: event.item_id,
434
+ },
435
+ },
436
+ });
437
+ }
438
+
439
+ if (event.type === 'response.reasoning_summary_text.delta') {
440
+ const blockId = `reasoning-${event.item_id}`;
441
+
442
+ controller.enqueue({
443
+ type: 'reasoning-delta',
444
+ id: blockId,
445
+ delta: event.delta,
446
+ providerMetadata: {
447
+ xai: {
448
+ itemId: event.item_id,
449
+ },
450
+ },
451
+ });
452
+
453
+ return;
454
+ }
455
+
456
+ if (event.type === 'response.reasoning_summary_text.done') {
457
+ return;
458
+ }
459
+
460
+ if (event.type === 'response.output_text.delta') {
461
+ const blockId = `text-${event.item_id}`;
462
+
463
+ if (contentBlocks[blockId] == null) {
464
+ contentBlocks[blockId] = { type: 'text' };
465
+ controller.enqueue({
466
+ type: 'text-start',
467
+ id: blockId,
468
+ });
469
+ }
470
+
471
+ controller.enqueue({
472
+ type: 'text-delta',
473
+ id: blockId,
474
+ delta: event.delta,
475
+ });
476
+
477
+ return;
478
+ }
479
+
480
+ if (event.type === 'response.output_text.done') {
481
+ if (event.annotations) {
482
+ for (const annotation of event.annotations) {
483
+ if (
484
+ annotation.type === 'url_citation' &&
485
+ 'url' in annotation
486
+ ) {
487
+ controller.enqueue({
488
+ type: 'source',
489
+ sourceType: 'url',
490
+ id: self.config.generateId(),
491
+ url: annotation.url,
492
+ title: annotation.title ?? annotation.url,
493
+ });
494
+ }
495
+ }
496
+ }
497
+
498
+ return;
499
+ }
500
+
501
+ if (event.type === 'response.output_text.annotation.added') {
502
+ const annotation = event.annotation;
503
+ if (annotation.type === 'url_citation' && 'url' in annotation) {
504
+ controller.enqueue({
505
+ type: 'source',
506
+ sourceType: 'url',
507
+ id: self.config.generateId(),
508
+ url: annotation.url,
509
+ title: annotation.title ?? annotation.url,
510
+ });
511
+ }
512
+
513
+ return;
514
+ }
515
+
516
+ if (
517
+ event.type === 'response.done' ||
518
+ event.type === 'response.completed'
519
+ ) {
520
+ const response = event.response;
521
+
522
+ if (response.usage) {
523
+ usage = convertXaiResponsesUsage(response.usage);
524
+ }
525
+
526
+ if (response.status) {
527
+ finishReason = {
528
+ unified: mapXaiResponsesFinishReason(response.status),
529
+ raw: response.status,
530
+ };
531
+ }
532
+
533
+ return;
534
+ }
535
+
536
+ if (
537
+ event.type === 'response.output_item.added' ||
538
+ event.type === 'response.output_item.done'
539
+ ) {
540
+ const part = event.item;
541
+ if (part.type === 'reasoning') {
542
+ if (event.type === 'response.output_item.done') {
543
+ controller.enqueue({
544
+ type: 'reasoning-end',
545
+ id: `reasoning-${part.id}`,
546
+ providerMetadata: {
547
+ xai: {
548
+ ...(part.encrypted_content && {
549
+ reasoningEncryptedContent: part.encrypted_content,
550
+ }),
551
+ ...(part.id && { itemId: part.id }),
552
+ },
553
+ },
554
+ });
555
+ delete activeReasoning[part.id];
556
+ }
557
+ return;
558
+ }
559
+
560
+ if (
561
+ part.type === 'web_search_call' ||
562
+ part.type === 'x_search_call' ||
563
+ part.type === 'code_interpreter_call' ||
564
+ part.type === 'code_execution_call' ||
565
+ part.type === 'view_image_call' ||
566
+ part.type === 'view_x_video_call' ||
567
+ part.type === 'custom_tool_call'
568
+ ) {
569
+ const webSearchSubTools = [
570
+ 'web_search',
571
+ 'web_search_with_snippets',
572
+ 'browse_page',
573
+ ];
574
+ const xSearchSubTools = [
575
+ 'x_user_search',
576
+ 'x_keyword_search',
577
+ 'x_semantic_search',
578
+ 'x_thread_fetch',
579
+ ];
580
+
581
+ let toolName = part.name ?? '';
582
+ if (
583
+ webSearchSubTools.includes(part.name ?? '') ||
584
+ part.type === 'web_search_call'
585
+ ) {
586
+ toolName = webSearchToolName ?? 'web_search';
587
+ } else if (
588
+ xSearchSubTools.includes(part.name ?? '') ||
589
+ part.type === 'x_search_call'
590
+ ) {
591
+ toolName = xSearchToolName ?? 'x_search';
592
+ } else if (
593
+ part.name === 'code_execution' ||
594
+ part.type === 'code_interpreter_call' ||
595
+ part.type === 'code_execution_call'
596
+ ) {
597
+ toolName = codeExecutionToolName ?? 'code_execution';
598
+ }
599
+
600
+ // custom_tool_call uses 'input' field, others use 'arguments'
601
+ const toolInput =
602
+ part.type === 'custom_tool_call'
603
+ ? (part.input ?? '')
604
+ : (part.arguments ?? '');
605
+
606
+ // for custom_tool_call, input is only available on 'done' event
607
+ // for other types, input is available on 'added' event
608
+ const shouldEmit =
609
+ part.type === 'custom_tool_call'
610
+ ? event.type === 'response.output_item.done'
611
+ : !seenToolCalls.has(part.id);
612
+
613
+ if (shouldEmit && !seenToolCalls.has(part.id)) {
614
+ seenToolCalls.add(part.id);
615
+
616
+ controller.enqueue({
617
+ type: 'tool-input-start',
618
+ id: part.id,
619
+ toolName,
620
+ });
621
+
622
+ controller.enqueue({
623
+ type: 'tool-input-delta',
624
+ id: part.id,
625
+ delta: toolInput,
626
+ });
627
+
628
+ controller.enqueue({
629
+ type: 'tool-input-end',
630
+ id: part.id,
631
+ });
632
+
633
+ controller.enqueue({
634
+ type: 'tool-call',
635
+ toolCallId: part.id,
636
+ toolName,
637
+ input: toolInput,
638
+ providerExecuted: true,
639
+ });
640
+ }
641
+
642
+ return;
643
+ }
644
+
645
+ if (part.type === 'message') {
646
+ for (const contentPart of part.content) {
647
+ if (contentPart.text && contentPart.text.length > 0) {
648
+ const blockId = `text-${part.id}`;
649
+
650
+ if (contentBlocks[blockId] == null) {
651
+ contentBlocks[blockId] = { type: 'text' };
652
+ controller.enqueue({
653
+ type: 'text-start',
654
+ id: blockId,
655
+ });
656
+
657
+ controller.enqueue({
658
+ type: 'text-delta',
659
+ id: blockId,
660
+ delta: contentPart.text,
661
+ });
662
+ }
663
+ }
664
+
665
+ if (contentPart.annotations) {
666
+ for (const annotation of contentPart.annotations) {
667
+ if (
668
+ annotation.type === 'url_citation' &&
669
+ 'url' in annotation
670
+ ) {
671
+ controller.enqueue({
672
+ type: 'source',
673
+ sourceType: 'url',
674
+ id: self.config.generateId(),
675
+ url: annotation.url,
676
+ title: annotation.title ?? annotation.url,
677
+ });
678
+ }
679
+ }
680
+ }
681
+ }
682
+ } else if (part.type === 'function_call') {
683
+ if (!seenToolCalls.has(part.call_id)) {
684
+ seenToolCalls.add(part.call_id);
685
+
686
+ controller.enqueue({
687
+ type: 'tool-input-start',
688
+ id: part.call_id,
689
+ toolName: part.name,
690
+ });
691
+
692
+ controller.enqueue({
693
+ type: 'tool-input-delta',
694
+ id: part.call_id,
695
+ delta: part.arguments,
696
+ });
697
+
698
+ controller.enqueue({
699
+ type: 'tool-input-end',
700
+ id: part.call_id,
701
+ });
702
+
703
+ controller.enqueue({
704
+ type: 'tool-call',
705
+ toolCallId: part.call_id,
706
+ toolName: part.name,
707
+ input: part.arguments,
708
+ });
709
+ }
710
+ }
711
+ }
712
+ },
713
+
714
+ flush(controller) {
715
+ for (const [blockId, block] of Object.entries(contentBlocks)) {
716
+ if (block.type === 'text') {
717
+ controller.enqueue({
718
+ type: 'text-end',
719
+ id: blockId,
720
+ });
721
+ }
722
+ }
723
+
724
+ controller.enqueue({ type: 'finish', finishReason, usage: usage! });
725
+ },
726
+ }),
727
+ ),
728
+ request: { body },
729
+ response: { headers: responseHeaders },
730
+ };
731
+ }
732
+ }