@ai-sdk/mistral 4.0.59 → 4.0.61

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,730 @@
1
+ import type {
2
+ LanguageModelV4,
3
+ LanguageModelV4CallOptions,
4
+ LanguageModelV4Content,
5
+ LanguageModelV4GenerateResult,
6
+ LanguageModelV4Reasoning,
7
+ LanguageModelV4Source,
8
+ LanguageModelV4StreamPart,
9
+ LanguageModelV4StreamResult,
10
+ LanguageModelV4Text,
11
+ LanguageModelV4ToolCall,
12
+ LanguageModelV4ToolResult,
13
+ SharedV4Warning,
14
+ } from '@ai-sdk/provider';
15
+ import {
16
+ combineHeaders,
17
+ createEventSourceResponseHandler,
18
+ createJsonResponseHandler,
19
+ createToolNameMapping,
20
+ generateId,
21
+ injectJsonInstructionIntoMessages,
22
+ isCustomReasoning,
23
+ mapReasoningToProviderEffort,
24
+ parseProviderOptions,
25
+ postJsonToApi,
26
+ serializeModelOptions,
27
+ StreamingToolCallTracker,
28
+ WORKFLOW_SERIALIZE,
29
+ WORKFLOW_DESERIALIZE,
30
+ type FetchFunction,
31
+ type ParseResult,
32
+ } from '@ai-sdk/provider-utils';
33
+ import { z } from 'zod/v4';
34
+ import {
35
+ convertMistralUsage,
36
+ mistralUsageSchema,
37
+ type MistralUsage,
38
+ } from './convert-mistral-usage';
39
+ import { convertToMistralConversationInputs } from './convert-to-mistral-conversation-inputs';
40
+ import {
41
+ mistralLanguageModelConversationOptions,
42
+ type MistralConversationModelId,
43
+ } from './mistral-conversation-language-model-options';
44
+ import { mistralFailedResponseHandler } from './mistral-error';
45
+ import {
46
+ mistralProviderToolNames,
47
+ prepareConversationTools,
48
+ } from './mistral-conversation-prepare-tools';
49
+
50
+ type MistralConversationConfig = {
51
+ provider: string;
52
+ baseURL: string;
53
+ headers?: () => Record<string, string | undefined>;
54
+ fetch?: FetchFunction;
55
+ generateId?: () => string;
56
+ };
57
+
58
+ // https://api.mistral.ai/v1/models (2026-10-06)
59
+ const reasoningEffortModelIds = new Set<MistralConversationModelId>([
60
+ 'glm-5-2',
61
+ 'labs-leanstral-1-5',
62
+ 'labs-leanstral-1-5-1',
63
+ 'magistral-medium-latest',
64
+ 'magistral-small-latest',
65
+ 'mistral-large-4',
66
+ 'mistral-large-4-0',
67
+ 'mistral-medium',
68
+ 'mistral-medium-2604',
69
+ 'mistral-medium-3',
70
+ 'mistral-medium-3-5',
71
+ 'mistral-medium-3.5',
72
+ 'mistral-medium-latest',
73
+ 'mistral-small-2603',
74
+ 'mistral-small-latest',
75
+ 'mistral-vibe-cli-fast',
76
+ 'mistral-vibe-cli-latest',
77
+ 'mistral-vibe-cli-with-tools',
78
+ 'zai-glm-5-2',
79
+ ]);
80
+
81
+ export class MistralConversationLanguageModel implements LanguageModelV4 {
82
+ readonly specificationVersion = 'v4';
83
+
84
+ readonly modelId: MistralConversationModelId;
85
+
86
+ private readonly config: MistralConversationConfig;
87
+ private readonly generateId: () => string;
88
+
89
+ static [WORKFLOW_SERIALIZE](model: MistralConversationLanguageModel) {
90
+ return serializeModelOptions({
91
+ modelId: model.modelId,
92
+ config: model.config,
93
+ });
94
+ }
95
+
96
+ static [WORKFLOW_DESERIALIZE](options: {
97
+ modelId: MistralConversationModelId;
98
+ config: MistralConversationConfig;
99
+ }) {
100
+ return new MistralConversationLanguageModel(
101
+ options.modelId,
102
+ options.config,
103
+ );
104
+ }
105
+
106
+ constructor(
107
+ modelId: MistralConversationModelId,
108
+ config: MistralConversationConfig,
109
+ ) {
110
+ this.modelId = modelId;
111
+ this.config = config;
112
+ this.generateId = config.generateId ?? generateId;
113
+ }
114
+
115
+ get provider(): string {
116
+ return this.config.provider;
117
+ }
118
+
119
+ readonly supportedUrls: Record<string, RegExp[]> = {
120
+ 'application/pdf': [/^https:\/\/.*$/],
121
+ };
122
+
123
+ private async getArgs({
124
+ prompt,
125
+ maxOutputTokens,
126
+ temperature,
127
+ topP,
128
+ topK,
129
+ frequencyPenalty,
130
+ presencePenalty,
131
+ reasoning,
132
+ stopSequences,
133
+ responseFormat,
134
+ seed,
135
+ providerOptions,
136
+ tools,
137
+ toolChoice,
138
+ }: LanguageModelV4CallOptions) {
139
+ const warnings: SharedV4Warning[] = [];
140
+
141
+ const options =
142
+ (await parseProviderOptions({
143
+ provider: 'mistral',
144
+ providerOptions,
145
+ schema: mistralLanguageModelConversationOptions,
146
+ })) ?? {};
147
+
148
+ if (topK != null) {
149
+ warnings.push({ type: 'unsupported', feature: 'topK' });
150
+ }
151
+
152
+ const supportsReasoningEffort = reasoningEffortModelIds.has(this.modelId);
153
+
154
+ let resolvedReasoningEffort: string | undefined;
155
+ if (supportsReasoningEffort) {
156
+ resolvedReasoningEffort =
157
+ options.reasoningEffort ??
158
+ (isCustomReasoning(reasoning)
159
+ ? reasoning === 'none'
160
+ ? 'none'
161
+ : mapReasoningToProviderEffort({
162
+ reasoning,
163
+ effortMap: {
164
+ minimal: 'high',
165
+ low: 'high',
166
+ medium: 'high',
167
+ high: 'high',
168
+ xhigh: 'high',
169
+ max: 'high',
170
+ },
171
+ warnings,
172
+ })
173
+ : undefined);
174
+ } else if (isCustomReasoning(reasoning)) {
175
+ warnings.push({
176
+ type: 'unsupported',
177
+ feature: 'reasoning',
178
+ details: 'This model does not support reasoning configuration.',
179
+ });
180
+ }
181
+
182
+ const structuredOutputs = options.structuredOutputs ?? true;
183
+ const strictJsonSchema = options.strictJsonSchema ?? false;
184
+
185
+ // For Mistral we need to instruct the model when using JSON Object mode.
186
+ // https://docs.mistral.ai/capabilities/structured-output/structured_output_overview/
187
+ if (
188
+ responseFormat?.type === 'json' &&
189
+ (!structuredOutputs || responseFormat.schema == null)
190
+ ) {
191
+ prompt = injectJsonInstructionIntoMessages({
192
+ messages: prompt,
193
+ schema: responseFormat.schema,
194
+ });
195
+ }
196
+
197
+ const baseArgs = {
198
+ // standardized settings:
199
+ max_tokens: maxOutputTokens,
200
+ temperature,
201
+ top_p: topP,
202
+ ...(frequencyPenalty != null
203
+ ? { frequency_penalty: frequencyPenalty }
204
+ : {}),
205
+ ...(presencePenalty != null ? { presence_penalty: presencePenalty } : {}),
206
+ stop: stopSequences,
207
+ random_seed: seed,
208
+ reasoning_effort: resolvedReasoningEffort,
209
+
210
+ // response format:
211
+ response_format:
212
+ responseFormat?.type === 'json'
213
+ ? structuredOutputs && responseFormat?.schema != null
214
+ ? {
215
+ type: 'json_schema',
216
+ json_schema: {
217
+ schema: responseFormat.schema,
218
+ strict: strictJsonSchema,
219
+ name: responseFormat.name ?? 'response',
220
+ description: responseFormat.description,
221
+ },
222
+ }
223
+ : { type: 'json_object' }
224
+ : undefined,
225
+ };
226
+
227
+ const {
228
+ tools: mistralTools,
229
+ toolChoice: mistralToolChoice,
230
+ toolWarnings,
231
+ } = prepareConversationTools({ tools, toolChoice });
232
+
233
+ for (const [feature, value] of Object.entries({
234
+ safePrompt: options.safePrompt,
235
+ documentImageLimit: options.documentImageLimit,
236
+ documentPageLimit: options.documentPageLimit,
237
+ promptCacheKey: options.promptCacheKey,
238
+ parallelToolCalls: options.parallelToolCalls,
239
+ })) {
240
+ if (value != null) {
241
+ warnings.push({
242
+ type: 'unsupported',
243
+ feature,
244
+ details: 'Not supported by the Mistral Conversations API.',
245
+ });
246
+ }
247
+ }
248
+
249
+ return {
250
+ args: {
251
+ model: this.modelId,
252
+ store: false,
253
+ ...convertToMistralConversationInputs(
254
+ prompt,
255
+ createToolNameMapping({
256
+ tools,
257
+ providerToolNames: mistralProviderToolNames,
258
+ }),
259
+ ),
260
+ tools: mistralTools,
261
+ completion_args: { ...baseArgs, tool_choice: mistralToolChoice },
262
+ },
263
+ warnings: [...warnings, ...toolWarnings],
264
+ };
265
+ }
266
+
267
+ async doGenerate(
268
+ options: LanguageModelV4CallOptions,
269
+ ): Promise<LanguageModelV4GenerateResult> {
270
+ const { args: body, warnings } = await this.getArgs(options);
271
+
272
+ const {
273
+ value: response,
274
+ rawValue: rawResponse,
275
+ responseHeaders,
276
+ } = await postJsonToApi({
277
+ url: `${this.config.baseURL}/conversations`,
278
+ headers: combineHeaders(this.config.headers?.(), options.headers),
279
+ body,
280
+ failedResponseHandler: mistralFailedResponseHandler,
281
+ successfulResponseHandler: createJsonResponseHandler(
282
+ mistralConversationResponseSchema,
283
+ ),
284
+ abortSignal: options.abortSignal,
285
+ fetch: this.config.fetch,
286
+ });
287
+
288
+ const converter = createContentConverter({
289
+ tools: options.tools,
290
+ generateId: this.generateId,
291
+ });
292
+ const content: LanguageModelV4Content[] = [];
293
+ let hasFunctionCalls = false;
294
+ let modelId: string | undefined;
295
+
296
+ for (const output of response.outputs) {
297
+ switch (output.type) {
298
+ case 'tool.execution':
299
+ content.push(...converter.convertTool(output));
300
+ break;
301
+ case 'function.call':
302
+ hasFunctionCalls = true;
303
+ content.push({
304
+ type: 'tool-call',
305
+ toolCallId: output.tool_call_id,
306
+ toolName: output.name,
307
+ input:
308
+ typeof output.arguments === 'string'
309
+ ? output.arguments
310
+ : JSON.stringify(output.arguments),
311
+ });
312
+ break;
313
+ case 'message.output':
314
+ modelId = output.model ?? modelId;
315
+ for (const chunk of typeof output.content === 'string'
316
+ ? [{ type: 'text' as const, text: output.content }]
317
+ : output.content) {
318
+ content.push(...converter.convertChunk(chunk));
319
+ }
320
+ break;
321
+ }
322
+ }
323
+
324
+ return {
325
+ content,
326
+ finishReason: {
327
+ unified: hasFunctionCalls ? 'tool-calls' : 'stop',
328
+ raw: undefined,
329
+ },
330
+ usage: convertMistralUsage(response.usage),
331
+ request: { body },
332
+ response: {
333
+ id: response.conversation_id,
334
+ modelId,
335
+ headers: responseHeaders,
336
+ body: rawResponse,
337
+ },
338
+ warnings,
339
+ };
340
+ }
341
+
342
+ async doStream(
343
+ options: LanguageModelV4CallOptions,
344
+ ): Promise<LanguageModelV4StreamResult> {
345
+ const { args, warnings } = await this.getArgs(options);
346
+ const body = { ...args, stream: true };
347
+
348
+ const { value: response, responseHeaders } = await postJsonToApi({
349
+ url: `${this.config.baseURL}/conversations`,
350
+ headers: combineHeaders(this.config.headers?.(), options.headers),
351
+ body,
352
+ failedResponseHandler: mistralFailedResponseHandler,
353
+ successfulResponseHandler: createEventSourceResponseHandler(
354
+ mistralConversationChunkSchema,
355
+ ),
356
+ abortSignal: options.abortSignal,
357
+ fetch: this.config.fetch,
358
+ });
359
+
360
+ const converter = createContentConverter({
361
+ tools: options.tools,
362
+ generateId: this.generateId,
363
+ });
364
+ const executions = new Map<
365
+ string,
366
+ { name: string; arguments: string; function?: string | null }
367
+ >();
368
+ let toolCallTracker: StreamingToolCallTracker;
369
+ let active: { id: string; type: 'text' | 'reasoning' } | undefined;
370
+ let usage: MistralUsage | undefined;
371
+ let hasFunctionCalls = false;
372
+ let hasError = false;
373
+ let isDone = false;
374
+ let hasModelId = false;
375
+ const closeContent = (
376
+ controller: TransformStreamDefaultController<LanguageModelV4StreamPart>,
377
+ ) => {
378
+ if (active != null) {
379
+ controller.enqueue({
380
+ type: active.type === 'text' ? 'text-end' : 'reasoning-end',
381
+ id: active.id,
382
+ });
383
+ active = undefined;
384
+ }
385
+ };
386
+
387
+ const generateId = this.generateId;
388
+
389
+ return {
390
+ stream: response.pipeThrough(
391
+ new TransformStream<
392
+ ParseResult<z.infer<typeof mistralConversationChunkSchema>>,
393
+ LanguageModelV4StreamPart
394
+ >({
395
+ start(controller) {
396
+ toolCallTracker = new StreamingToolCallTracker(controller, {
397
+ generateId,
398
+ });
399
+ controller.enqueue({ type: 'stream-start', warnings });
400
+ },
401
+
402
+ transform(chunk, controller) {
403
+ if (options.includeRawChunks) {
404
+ controller.enqueue({ type: 'raw', rawValue: chunk.rawValue });
405
+ }
406
+
407
+ if (!chunk.success) {
408
+ hasError = true;
409
+ controller.enqueue({ type: 'error', error: chunk.error });
410
+ return;
411
+ }
412
+ const event = chunk.value;
413
+ switch (event.type) {
414
+ case 'conversation.response.started':
415
+ controller.enqueue({
416
+ type: 'response-metadata',
417
+ id: event.conversation_id,
418
+ ...(event.created_at != null
419
+ ? { timestamp: new Date(event.created_at) }
420
+ : {}),
421
+ });
422
+ break;
423
+ case 'conversation.response.done':
424
+ usage = event.usage ?? undefined;
425
+ isDone = true;
426
+ break;
427
+ case 'conversation.response.error':
428
+ hasError = true;
429
+ controller.enqueue({ type: 'error', error: event });
430
+ break;
431
+ case 'message.output.delta': {
432
+ if (!hasModelId && event.model != null) {
433
+ hasModelId = true;
434
+ controller.enqueue({
435
+ type: 'response-metadata',
436
+ modelId: event.model,
437
+ });
438
+ }
439
+ for (const part of converter.convertChunk(
440
+ typeof event.content === 'string'
441
+ ? { type: 'text', text: event.content }
442
+ : event.content,
443
+ )) {
444
+ if (part.type === 'text' || part.type === 'reasoning') {
445
+ const id = `${event.id}-${event.content_index ?? 0}`;
446
+ if (active?.id !== id || active.type !== part.type) {
447
+ closeContent(controller);
448
+ active = { id, type: part.type };
449
+ controller.enqueue({
450
+ type:
451
+ part.type === 'text'
452
+ ? 'text-start'
453
+ : 'reasoning-start',
454
+ id,
455
+ });
456
+ }
457
+ controller.enqueue({
458
+ type:
459
+ part.type === 'text' ? 'text-delta' : 'reasoning-delta',
460
+ id,
461
+ delta: part.text,
462
+ });
463
+ } else if (part.type === 'source') {
464
+ controller.enqueue(part);
465
+ }
466
+ }
467
+ break;
468
+ }
469
+ case 'function.call.delta':
470
+ closeContent(controller);
471
+ hasFunctionCalls = true;
472
+ toolCallTracker.processDelta({
473
+ id: event.tool_call_id,
474
+ index: event.output_index,
475
+ function: { name: event.name, arguments: event.arguments },
476
+ });
477
+ break;
478
+ case 'tool.execution.started':
479
+ case 'tool.execution.delta': {
480
+ closeContent(controller);
481
+ const execution = executions.get(event.id);
482
+ if (execution == null) {
483
+ executions.set(event.id, {
484
+ name: event.name,
485
+ arguments: event.arguments,
486
+ function: event.function,
487
+ });
488
+ controller.enqueue({
489
+ type: 'tool-input-start',
490
+ id: event.id,
491
+ toolName: converter.toolNameMapping.toCustomToolName(
492
+ event.name,
493
+ ),
494
+ providerExecuted: true,
495
+ });
496
+ } else {
497
+ execution.arguments += event.arguments;
498
+ }
499
+ break;
500
+ }
501
+ case 'tool.execution.done': {
502
+ const execution = executions.get(event.id);
503
+ if (execution == null) {
504
+ hasError = true;
505
+ controller.enqueue({
506
+ type: 'error',
507
+ error: new Error(
508
+ `Missing tool execution start for ${event.id}`,
509
+ ),
510
+ });
511
+ break;
512
+ }
513
+ controller.enqueue({
514
+ type: 'tool-input-delta',
515
+ id: event.id,
516
+ delta: JSON.stringify({ arguments: execution.arguments }),
517
+ });
518
+ controller.enqueue({ type: 'tool-input-end', id: event.id });
519
+ for (const part of converter.convertTool({
520
+ type: 'tool.execution',
521
+ id: event.id,
522
+ name: execution.name,
523
+ arguments: execution.arguments,
524
+ function: event.function ?? execution.function,
525
+ info: event.info,
526
+ })) {
527
+ controller.enqueue(part);
528
+ }
529
+ executions.delete(event.id);
530
+ break;
531
+ }
532
+ }
533
+ },
534
+
535
+ flush(controller) {
536
+ closeContent(controller);
537
+ toolCallTracker.flush();
538
+ controller.enqueue({
539
+ type: 'finish',
540
+ finishReason: {
541
+ unified:
542
+ hasError || !isDone || executions.size > 0
543
+ ? 'error'
544
+ : hasFunctionCalls
545
+ ? 'tool-calls'
546
+ : 'stop',
547
+ raw: undefined,
548
+ },
549
+ usage: convertMistralUsage(usage),
550
+ });
551
+ },
552
+ }),
553
+ ),
554
+ request: { body },
555
+ response: { headers: responseHeaders },
556
+ };
557
+ }
558
+ }
559
+
560
+ function createContentConverter({
561
+ tools,
562
+ generateId,
563
+ }: {
564
+ tools: LanguageModelV4CallOptions['tools'];
565
+ generateId: () => string;
566
+ }) {
567
+ const toolNameMapping = createToolNameMapping({
568
+ tools,
569
+ providerToolNames: mistralProviderToolNames,
570
+ });
571
+ const sourceUrls = new Set<string>();
572
+
573
+ return {
574
+ toolNameMapping,
575
+ convertChunk(
576
+ chunk: z.infer<typeof mistralConversationContentSchema>,
577
+ ): Array<
578
+ LanguageModelV4Text | LanguageModelV4Reasoning | LanguageModelV4Source
579
+ > {
580
+ switch (chunk.type) {
581
+ case 'text':
582
+ return chunk.text.length > 0
583
+ ? [{ type: 'text', text: chunk.text }]
584
+ : [];
585
+ case 'thinking': {
586
+ const text = chunk.thinking.map(part => part.text).join('');
587
+ return text.length > 0 ? [{ type: 'reasoning', text }] : [];
588
+ }
589
+ case 'tool_reference':
590
+ if (chunk.url == null || sourceUrls.has(chunk.url)) {
591
+ return [];
592
+ }
593
+ const id = generateId();
594
+ sourceUrls.add(chunk.url);
595
+ return [
596
+ {
597
+ type: 'source',
598
+ sourceType: 'url',
599
+ id,
600
+ url: chunk.url,
601
+ title: chunk.title ?? chunk.url,
602
+ },
603
+ ];
604
+ }
605
+ },
606
+ convertTool(
607
+ output: z.infer<typeof mistralConversationToolExecutionSchema>,
608
+ ): [LanguageModelV4ToolCall, LanguageModelV4ToolResult] {
609
+ const toolName = toolNameMapping.toCustomToolName(output.name);
610
+ return [
611
+ {
612
+ type: 'tool-call',
613
+ toolCallId: output.id,
614
+ toolName,
615
+ input: JSON.stringify({ arguments: output.arguments }),
616
+ providerExecuted: true,
617
+ providerMetadata: {
618
+ mistral: {
619
+ type: 'tool.execution',
620
+ name: output.name,
621
+ ...(output.function != null ? { function: output.function } : {}),
622
+ },
623
+ },
624
+ },
625
+ {
626
+ type: 'tool-result',
627
+ toolCallId: output.id,
628
+ toolName,
629
+ result: { ...(output.info != null ? { info: output.info } : {}) },
630
+ },
631
+ ];
632
+ },
633
+ };
634
+ }
635
+
636
+ const mistralConversationContentSchema = z.discriminatedUnion('type', [
637
+ z.object({ type: z.literal('text'), text: z.string() }),
638
+ z.object({
639
+ type: z.literal('thinking'),
640
+ thinking: z.array(z.object({ type: z.literal('text'), text: z.string() })),
641
+ }),
642
+ z.object({
643
+ type: z.literal('tool_reference'),
644
+ tool: z.string().nullish(),
645
+ title: z.string().nullish(),
646
+ url: z.string().nullish(),
647
+ }),
648
+ ]);
649
+ const mistralConversationMessageContentSchema = z.union([
650
+ z.string(),
651
+ z.array(mistralConversationContentSchema),
652
+ ]);
653
+ const mistralConversationToolExecutionSchema = z.object({
654
+ type: z.literal('tool.execution'),
655
+ id: z.string(),
656
+ name: z.string(),
657
+ arguments: z.string(),
658
+ function: z.string().nullish(),
659
+ info: z.record(z.string(), z.json()).nullish(),
660
+ });
661
+ const mistralConversationFunctionCallSchema = z.object({
662
+ type: z.literal('function.call'),
663
+ tool_call_id: z.string(),
664
+ name: z.string(),
665
+ arguments: z.union([z.string(), z.record(z.string(), z.json())]),
666
+ });
667
+ const mistralConversationUsageSchema = mistralUsageSchema.extend({
668
+ prompt_tokens: z.number().default(0),
669
+ completion_tokens: z.number().default(0),
670
+ total_tokens: z.number().default(0),
671
+ });
672
+ const mistralConversationResponseSchema = z.object({
673
+ conversation_id: z.string(),
674
+ outputs: z.array(
675
+ z.discriminatedUnion('type', [
676
+ mistralConversationToolExecutionSchema,
677
+ mistralConversationFunctionCallSchema,
678
+ z.object({
679
+ type: z.literal('message.output'),
680
+ content: mistralConversationMessageContentSchema,
681
+ model: z.string().nullish(),
682
+ }),
683
+ ]),
684
+ ),
685
+ usage: mistralConversationUsageSchema.nullish(),
686
+ });
687
+ const mistralConversationChunkSchema = z.discriminatedUnion('type', [
688
+ z.object({
689
+ type: z.literal('conversation.response.started'),
690
+ conversation_id: z.string(),
691
+ created_at: z.string().nullish(),
692
+ }),
693
+ z.object({
694
+ type: z.literal('conversation.response.done'),
695
+ usage: mistralConversationUsageSchema.nullish(),
696
+ }),
697
+ z.object({
698
+ type: z.literal('conversation.response.error'),
699
+ message: z.string(),
700
+ code: z.number(),
701
+ }),
702
+ z.object({
703
+ type: z.literal('message.output.delta'),
704
+ id: z.string(),
705
+ content_index: z.number().nullish(),
706
+ content: z.union([z.string(), mistralConversationContentSchema]),
707
+ model: z.string().nullish(),
708
+ }),
709
+ z.object({
710
+ type: z.literal('function.call.delta'),
711
+ tool_call_id: z.string(),
712
+ name: z.string(),
713
+ arguments: z.string(),
714
+ output_index: z.number().nullish(),
715
+ }),
716
+ z.object({
717
+ type: z.enum(['tool.execution.started', 'tool.execution.delta']),
718
+ id: z.string(),
719
+ name: z.string(),
720
+ arguments: z.string(),
721
+ function: z.string().nullish(),
722
+ }),
723
+ z.object({
724
+ type: z.literal('tool.execution.done'),
725
+ id: z.string(),
726
+ name: z.string(),
727
+ function: z.string().nullish(),
728
+ info: z.record(z.string(), z.json()).nullish(),
729
+ }),
730
+ ]);