@mastra/client-js 0.0.0-storage-20250225005900 → 0.0.0-support-d1-client-20250701191943

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,19 @@
1
- import type { GenerateReturn, StreamReturn } from '@mastra/core';
1
+ import {
2
+ parsePartialJson,
3
+ processDataStream,
4
+ type JSONValue,
5
+ type ReasoningUIPart,
6
+ type TextUIPart,
7
+ type ToolInvocation,
8
+ type ToolInvocationUIPart,
9
+ type UIMessage,
10
+ type UseChatOptions,
11
+ } from '@ai-sdk/ui-utils';
12
+ import { Tool, type CoreMessage, type GenerateReturn } from '@mastra/core';
2
13
  import type { JSONSchema7 } from 'json-schema';
3
14
  import { ZodSchema } from 'zod';
4
- import { zodToJsonSchema } from 'zod-to-json-schema';
15
+ import { zodToJsonSchema } from '../utils/zod-to-json-schema';
16
+ import { processClientTools } from '../utils/process-client-tools';
5
17
 
6
18
  import type {
7
19
  GenerateParams,
@@ -13,35 +25,82 @@ import type {
13
25
  } from '../types';
14
26
 
15
27
  import { BaseResource } from './base';
28
+ import type { RuntimeContext } from '@mastra/core/runtime-context';
29
+ import { parseClientRuntimeContext } from '../utils';
30
+ import { MessageList } from '@mastra/core/agent';
16
31
 
17
- export class AgentTool extends BaseResource {
32
+ export class AgentVoice extends BaseResource {
18
33
  constructor(
19
34
  options: ClientOptions,
20
35
  private agentId: string,
21
- private toolId: string,
22
36
  ) {
23
37
  super(options);
38
+ this.agentId = agentId;
24
39
  }
25
40
 
26
41
  /**
27
- * Executes a specific tool for an agent
28
- * @param params - Parameters required for tool execution
29
- * @returns Promise containing tool execution results
42
+ * Convert text to speech using the agent's voice provider
43
+ * @param text - Text to convert to speech
44
+ * @param options - Optional provider-specific options for speech generation
45
+ * @returns Promise containing the audio data
30
46
  */
31
- execute(params: { data: any }): Promise<any> {
32
- return this.request(`/api/agents/${this.agentId}/tools/${this.toolId}/execute`, {
47
+ async speak(text: string, options?: { speaker?: string; [key: string]: any }): Promise<Response> {
48
+ return this.request<Response>(`/api/agents/${this.agentId}/voice/speak`, {
33
49
  method: 'POST',
34
- body: params,
50
+ headers: {
51
+ 'Content-Type': 'application/json',
52
+ },
53
+ body: { input: text, options },
54
+ stream: true,
35
55
  });
36
56
  }
57
+
58
+ /**
59
+ * Convert speech to text using the agent's voice provider
60
+ * @param audio - Audio data to transcribe
61
+ * @param options - Optional provider-specific options
62
+ * @returns Promise containing the transcribed text
63
+ */
64
+ listen(audio: Blob, options?: Record<string, any>): Promise<{ text: string }> {
65
+ const formData = new FormData();
66
+ formData.append('audio', audio);
67
+
68
+ if (options) {
69
+ formData.append('options', JSON.stringify(options));
70
+ }
71
+
72
+ return this.request(`/api/agents/${this.agentId}/voice/listen`, {
73
+ method: 'POST',
74
+ body: formData,
75
+ });
76
+ }
77
+
78
+ /**
79
+ * Get available speakers for the agent's voice provider
80
+ * @returns Promise containing list of available speakers
81
+ */
82
+ getSpeakers(): Promise<Array<{ voiceId: string; [key: string]: any }>> {
83
+ return this.request(`/api/agents/${this.agentId}/voice/speakers`);
84
+ }
85
+
86
+ /**
87
+ * Get the listener configuration for the agent's voice provider
88
+ * @returns Promise containing a check if the agent has listening capabilities
89
+ */
90
+ getListener(): Promise<{ enabled: boolean }> {
91
+ return this.request(`/api/agents/${this.agentId}/voice/listener`);
92
+ }
37
93
  }
38
94
 
39
95
  export class Agent extends BaseResource {
96
+ public readonly voice: AgentVoice;
97
+
40
98
  constructor(
41
99
  options: ClientOptions,
42
100
  private agentId: string,
43
101
  ) {
44
102
  super(options);
103
+ this.voice = new AgentVoice(options, this.agentId);
45
104
  }
46
105
 
47
106
  /**
@@ -57,36 +116,607 @@ export class Agent extends BaseResource {
57
116
  * @param params - Generation parameters including prompt
58
117
  * @returns Promise containing the generated response
59
118
  */
60
- generate<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
119
+ async generate<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
120
+ params: GenerateParams<T> & { output?: never; experimental_output?: never },
121
+ ): Promise<GenerateReturn<T>>;
122
+ async generate<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
123
+ params: GenerateParams<T> & { output: T; experimental_output?: never },
124
+ ): Promise<GenerateReturn<T>>;
125
+ async generate<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
126
+ params: GenerateParams<T> & { output?: never; experimental_output: T },
127
+ ): Promise<GenerateReturn<T>>;
128
+ async generate<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
61
129
  params: GenerateParams<T>,
62
130
  ): Promise<GenerateReturn<T>> {
63
131
  const processedParams = {
64
132
  ...params,
65
- output: params.output instanceof ZodSchema ? zodToJsonSchema(params.output) : params.output,
133
+ output: params.output ? zodToJsonSchema(params.output) : undefined,
134
+ experimental_output: params.experimental_output ? zodToJsonSchema(params.experimental_output) : undefined,
135
+ runtimeContext: parseClientRuntimeContext(params.runtimeContext),
136
+ clientTools: processClientTools(params.clientTools),
66
137
  };
67
138
 
68
- return this.request(`/api/agents/${this.agentId}/generate`, {
139
+ const { runId, resourceId, threadId, runtimeContext } = processedParams as GenerateParams;
140
+
141
+ const response: GenerateReturn<T> = await this.request(`/api/agents/${this.agentId}/generate`, {
69
142
  method: 'POST',
70
143
  body: processedParams,
71
144
  });
145
+
146
+ if (response.finishReason === 'tool-calls') {
147
+ for (const toolCall of (
148
+ response as unknown as {
149
+ toolCalls: { toolName: string; args: any; toolCallId: string }[];
150
+ messages: CoreMessage[];
151
+ }
152
+ ).toolCalls) {
153
+ const clientTool = params.clientTools?.[toolCall.toolName] as Tool;
154
+
155
+ if (clientTool && clientTool.execute) {
156
+ const result = await clientTool.execute(
157
+ { context: toolCall?.args, runId, resourceId, threadId, runtimeContext: runtimeContext as RuntimeContext },
158
+ {
159
+ messages: (response as unknown as { messages: CoreMessage[] }).messages,
160
+ toolCallId: toolCall?.toolCallId,
161
+ },
162
+ );
163
+
164
+ const updatedMessages = [
165
+ {
166
+ role: 'user',
167
+ content: params.messages,
168
+ },
169
+ ...(response.response as unknown as { messages: CoreMessage[] }).messages,
170
+ {
171
+ role: 'tool',
172
+ content: [
173
+ {
174
+ type: 'tool-result',
175
+ toolCallId: toolCall.toolCallId,
176
+ toolName: toolCall.toolName,
177
+ result,
178
+ },
179
+ ],
180
+ },
181
+ ];
182
+ // @ts-ignore
183
+ return this.generate({
184
+ ...params,
185
+ messages: updatedMessages,
186
+ });
187
+ }
188
+ }
189
+ }
190
+
191
+ return response;
192
+ }
193
+
194
+ private async processChatResponse({
195
+ stream,
196
+ update,
197
+ onToolCall,
198
+ onFinish,
199
+ getCurrentDate = () => new Date(),
200
+ lastMessage,
201
+ }: {
202
+ stream: ReadableStream<Uint8Array>;
203
+ update: (options: { message: UIMessage; data: JSONValue[] | undefined; replaceLastMessage: boolean }) => void;
204
+ onToolCall?: UseChatOptions['onToolCall'];
205
+ onFinish?: (options: { message: UIMessage | undefined; finishReason: string; usage: string }) => void;
206
+ generateId?: () => string;
207
+ getCurrentDate?: () => Date;
208
+ lastMessage: UIMessage | undefined;
209
+ }) {
210
+ const replaceLastMessage = lastMessage?.role === 'assistant';
211
+ let step = replaceLastMessage
212
+ ? 1 +
213
+ // find max step in existing tool invocations:
214
+ (lastMessage.toolInvocations?.reduce((max, toolInvocation) => {
215
+ return Math.max(max, toolInvocation.step ?? 0);
216
+ }, 0) ?? 0)
217
+ : 0;
218
+
219
+ const message: UIMessage = replaceLastMessage
220
+ ? structuredClone(lastMessage)
221
+ : {
222
+ id: crypto.randomUUID(),
223
+ createdAt: getCurrentDate(),
224
+ role: 'assistant',
225
+ content: '',
226
+ parts: [],
227
+ };
228
+
229
+ let currentTextPart: TextUIPart | undefined = undefined;
230
+ let currentReasoningPart: ReasoningUIPart | undefined = undefined;
231
+ let currentReasoningTextDetail: { type: 'text'; text: string; signature?: string } | undefined = undefined;
232
+
233
+ function updateToolInvocationPart(toolCallId: string, invocation: ToolInvocation) {
234
+ const part = message.parts.find(
235
+ part => part.type === 'tool-invocation' && part.toolInvocation.toolCallId === toolCallId,
236
+ ) as ToolInvocationUIPart | undefined;
237
+
238
+ if (part != null) {
239
+ part.toolInvocation = invocation;
240
+ } else {
241
+ message.parts.push({
242
+ type: 'tool-invocation',
243
+ toolInvocation: invocation,
244
+ });
245
+ }
246
+ }
247
+
248
+ const data: JSONValue[] = [];
249
+
250
+ // keep list of current message annotations for message
251
+ let messageAnnotations: JSONValue[] | undefined = replaceLastMessage ? lastMessage?.annotations : undefined;
252
+
253
+ // keep track of partial tool calls
254
+ const partialToolCalls: Record<string, { text: string; step: number; index: number; toolName: string }> = {};
255
+
256
+ let usage: any = {
257
+ completionTokens: NaN,
258
+ promptTokens: NaN,
259
+ totalTokens: NaN,
260
+ };
261
+ let finishReason: string = 'unknown';
262
+
263
+ function execUpdate() {
264
+ // make a copy of the data array to ensure UI is updated (SWR)
265
+ const copiedData = [...data];
266
+
267
+ // keeps the currentMessage up to date with the latest annotations,
268
+ // even if annotations preceded the message creation
269
+ if (messageAnnotations?.length) {
270
+ message.annotations = messageAnnotations;
271
+ }
272
+
273
+ const copiedMessage = {
274
+ // deep copy the message to ensure that deep changes (msg attachments) are updated
275
+ // with SolidJS. SolidJS uses referential integration of sub-objects to detect changes.
276
+ ...structuredClone(message),
277
+ // add a revision id to ensure that the message is updated with SWR. SWR uses a
278
+ // hashing approach by default to detect changes, but it only works for shallow
279
+ // changes. This is why we need to add a revision id to ensure that the message
280
+ // is updated with SWR (without it, the changes get stuck in SWR and are not
281
+ // forwarded to rendering):
282
+ revisionId: crypto.randomUUID(),
283
+ } as UIMessage;
284
+
285
+ update({
286
+ message: copiedMessage,
287
+ data: copiedData,
288
+ replaceLastMessage,
289
+ });
290
+ }
291
+
292
+ await processDataStream({
293
+ stream,
294
+ onTextPart(value) {
295
+ if (currentTextPart == null) {
296
+ currentTextPart = {
297
+ type: 'text',
298
+ text: value,
299
+ };
300
+ message.parts.push(currentTextPart);
301
+ } else {
302
+ currentTextPart.text += value;
303
+ }
304
+
305
+ message.content += value;
306
+ execUpdate();
307
+ },
308
+ onReasoningPart(value) {
309
+ if (currentReasoningTextDetail == null) {
310
+ currentReasoningTextDetail = { type: 'text', text: value };
311
+ if (currentReasoningPart != null) {
312
+ currentReasoningPart.details.push(currentReasoningTextDetail);
313
+ }
314
+ } else {
315
+ currentReasoningTextDetail.text += value;
316
+ }
317
+
318
+ if (currentReasoningPart == null) {
319
+ currentReasoningPart = {
320
+ type: 'reasoning',
321
+ reasoning: value,
322
+ details: [currentReasoningTextDetail],
323
+ };
324
+ message.parts.push(currentReasoningPart);
325
+ } else {
326
+ currentReasoningPart.reasoning += value;
327
+ }
328
+
329
+ message.reasoning = (message.reasoning ?? '') + value;
330
+
331
+ execUpdate();
332
+ },
333
+ onReasoningSignaturePart(value) {
334
+ if (currentReasoningTextDetail != null) {
335
+ currentReasoningTextDetail.signature = value.signature;
336
+ }
337
+ },
338
+ onRedactedReasoningPart(value) {
339
+ if (currentReasoningPart == null) {
340
+ currentReasoningPart = {
341
+ type: 'reasoning',
342
+ reasoning: '',
343
+ details: [],
344
+ };
345
+ message.parts.push(currentReasoningPart);
346
+ }
347
+
348
+ currentReasoningPart.details.push({
349
+ type: 'redacted',
350
+ data: value.data,
351
+ });
352
+
353
+ currentReasoningTextDetail = undefined;
354
+
355
+ execUpdate();
356
+ },
357
+ onFilePart(value) {
358
+ message.parts.push({
359
+ type: 'file',
360
+ mimeType: value.mimeType,
361
+ data: value.data,
362
+ });
363
+
364
+ execUpdate();
365
+ },
366
+ onSourcePart(value) {
367
+ message.parts.push({
368
+ type: 'source',
369
+ source: value,
370
+ });
371
+
372
+ execUpdate();
373
+ },
374
+ onToolCallStreamingStartPart(value) {
375
+ if (message.toolInvocations == null) {
376
+ message.toolInvocations = [];
377
+ }
378
+
379
+ // add the partial tool call to the map
380
+ partialToolCalls[value.toolCallId] = {
381
+ text: '',
382
+ step,
383
+ toolName: value.toolName,
384
+ index: message.toolInvocations.length,
385
+ };
386
+
387
+ const invocation = {
388
+ state: 'partial-call',
389
+ step,
390
+ toolCallId: value.toolCallId,
391
+ toolName: value.toolName,
392
+ args: undefined,
393
+ } as const;
394
+
395
+ message.toolInvocations.push(invocation);
396
+
397
+ updateToolInvocationPart(value.toolCallId, invocation);
398
+
399
+ execUpdate();
400
+ },
401
+ onToolCallDeltaPart(value) {
402
+ const partialToolCall = partialToolCalls[value.toolCallId];
403
+
404
+ partialToolCall!.text += value.argsTextDelta;
405
+
406
+ const { value: partialArgs } = parsePartialJson(partialToolCall!.text);
407
+
408
+ const invocation = {
409
+ state: 'partial-call',
410
+ step: partialToolCall!.step,
411
+ toolCallId: value.toolCallId,
412
+ toolName: partialToolCall!.toolName,
413
+ args: partialArgs,
414
+ } as const;
415
+
416
+ message.toolInvocations![partialToolCall!.index] = invocation;
417
+
418
+ updateToolInvocationPart(value.toolCallId, invocation);
419
+
420
+ execUpdate();
421
+ },
422
+ async onToolCallPart(value) {
423
+ const invocation = {
424
+ state: 'call',
425
+ step,
426
+ ...value,
427
+ } as const;
428
+
429
+ if (partialToolCalls[value.toolCallId] != null) {
430
+ // change the partial tool call to a full tool call
431
+ message.toolInvocations![partialToolCalls[value.toolCallId]!.index] = invocation;
432
+ } else {
433
+ if (message.toolInvocations == null) {
434
+ message.toolInvocations = [];
435
+ }
436
+
437
+ message.toolInvocations.push(invocation);
438
+ }
439
+
440
+ updateToolInvocationPart(value.toolCallId, invocation);
441
+
442
+ execUpdate();
443
+
444
+ // invoke the onToolCall callback if it exists. This is blocking.
445
+ // In the future we should make this non-blocking, which
446
+ // requires additional state management for error handling etc.
447
+ if (onToolCall) {
448
+ const result = await onToolCall({ toolCall: value });
449
+ if (result != null) {
450
+ const invocation = {
451
+ state: 'result',
452
+ step,
453
+ ...value,
454
+ result,
455
+ } as const;
456
+
457
+ // store the result in the tool invocation
458
+ message.toolInvocations![message.toolInvocations!.length - 1] = invocation;
459
+
460
+ updateToolInvocationPart(value.toolCallId, invocation);
461
+
462
+ execUpdate();
463
+ }
464
+ }
465
+ },
466
+ onToolResultPart(value) {
467
+ const toolInvocations = message.toolInvocations;
468
+
469
+ if (toolInvocations == null) {
470
+ throw new Error('tool_result must be preceded by a tool_call');
471
+ }
472
+
473
+ // find if there is any tool invocation with the same toolCallId
474
+ // and replace it with the result
475
+ const toolInvocationIndex = toolInvocations.findIndex(invocation => invocation.toolCallId === value.toolCallId);
476
+
477
+ if (toolInvocationIndex === -1) {
478
+ throw new Error('tool_result must be preceded by a tool_call with the same toolCallId');
479
+ }
480
+
481
+ const invocation = {
482
+ ...toolInvocations[toolInvocationIndex],
483
+ state: 'result' as const,
484
+ ...value,
485
+ } as const;
486
+
487
+ toolInvocations[toolInvocationIndex] = invocation as ToolInvocation;
488
+
489
+ updateToolInvocationPart(value.toolCallId, invocation as ToolInvocation);
490
+
491
+ execUpdate();
492
+ },
493
+ onDataPart(value) {
494
+ data.push(...value);
495
+ execUpdate();
496
+ },
497
+ onMessageAnnotationsPart(value) {
498
+ if (messageAnnotations == null) {
499
+ messageAnnotations = [...value];
500
+ } else {
501
+ messageAnnotations.push(...value);
502
+ }
503
+
504
+ execUpdate();
505
+ },
506
+ onFinishStepPart(value) {
507
+ step += 1;
508
+
509
+ // reset the current text and reasoning parts
510
+ currentTextPart = value.isContinued ? currentTextPart : undefined;
511
+ currentReasoningPart = undefined;
512
+ currentReasoningTextDetail = undefined;
513
+ },
514
+ onStartStepPart(value) {
515
+ // keep message id stable when we are updating an existing message:
516
+ if (!replaceLastMessage) {
517
+ message.id = value.messageId;
518
+ }
519
+
520
+ // add a step boundary part to the message
521
+ message.parts.push({ type: 'step-start' });
522
+ execUpdate();
523
+ },
524
+ onFinishMessagePart(value) {
525
+ finishReason = value.finishReason;
526
+ if (value.usage != null) {
527
+ // usage = calculateLanguageModelUsage(value.usage);
528
+ usage = value.usage;
529
+ }
530
+ },
531
+ onErrorPart(error) {
532
+ throw new Error(error);
533
+ },
534
+ });
535
+
536
+ onFinish?.({ message, finishReason, usage });
72
537
  }
73
538
 
74
539
  /**
75
540
  * Streams a response from the agent
76
541
  * @param params - Stream parameters including prompt
77
- * @returns Promise containing the streamed response
542
+ * @returns Promise containing the enhanced Response object with processDataStream method
78
543
  */
79
- stream<T extends JSONSchema7 | ZodSchema | undefined = undefined>(params: StreamParams<T>): Promise<Response> {
544
+ async stream<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
545
+ params: StreamParams<T>,
546
+ ): Promise<
547
+ Response & {
548
+ processDataStream: (options?: Omit<Parameters<typeof processDataStream>[0], 'stream'>) => Promise<void>;
549
+ }
550
+ > {
80
551
  const processedParams = {
81
552
  ...params,
82
- output: params.output instanceof ZodSchema ? zodToJsonSchema(params.output) : params.output,
553
+ output: params.output ? zodToJsonSchema(params.output) : undefined,
554
+ experimental_output: params.experimental_output ? zodToJsonSchema(params.experimental_output) : undefined,
555
+ runtimeContext: parseClientRuntimeContext(params.runtimeContext),
556
+ clientTools: processClientTools(params.clientTools),
557
+ };
558
+
559
+ // Create a readable stream that will handle the response processing
560
+ const { readable, writable } = new TransformStream<Uint8Array, Uint8Array>();
561
+
562
+ // Start processing the response in the background
563
+ const response = await this.processStreamResponse(processedParams, writable);
564
+
565
+ // Create a new response with the readable stream
566
+ const streamResponse = new Response(readable, {
567
+ status: response.status,
568
+ statusText: response.statusText,
569
+ headers: response.headers,
570
+ }) as Response & {
571
+ processDataStream: (options?: Omit<Parameters<typeof processDataStream>[0], 'stream'>) => Promise<void>;
572
+ };
573
+
574
+ // Add the processDataStream method to the response
575
+ streamResponse.processDataStream = async (options = {}) => {
576
+ await processDataStream({
577
+ stream: streamResponse.body as ReadableStream<Uint8Array>,
578
+ ...options,
579
+ });
83
580
  };
84
581
 
85
- return this.request(`/api/agents/${this.agentId}/stream`, {
582
+ return streamResponse;
583
+ }
584
+
585
+ /**
586
+ * Processes the stream response and handles tool calls
587
+ */
588
+ private async processStreamResponse(processedParams: any, writable: WritableStream<Uint8Array>) {
589
+ const response: Response & {
590
+ processDataStream: (options?: Omit<Parameters<typeof processDataStream>[0], 'stream'>) => Promise<void>;
591
+ } = await this.request(`/api/agents/${this.agentId}/stream`, {
86
592
  method: 'POST',
87
593
  body: processedParams,
88
594
  stream: true,
89
595
  });
596
+
597
+ if (!response.body) {
598
+ throw new Error('No response body');
599
+ }
600
+
601
+ try {
602
+ let toolCalls: ToolInvocation[] = [];
603
+ let finishReasonToolCalls = false;
604
+ let messages: UIMessage[] = [];
605
+ let hasProcessedToolCalls = false;
606
+
607
+ // Use tee() to split the stream into two branches
608
+ const [streamForWritable, streamForProcessing] = response.body.tee();
609
+
610
+ // Pipe one branch to the writable stream
611
+ streamForWritable
612
+ .pipeTo(writable, {
613
+ preventClose: true,
614
+ })
615
+ .catch(error => {
616
+ console.error('Error piping to writable stream:', error);
617
+ });
618
+
619
+ // Process the other branch for chat response handling
620
+ this.processChatResponse({
621
+ stream: streamForProcessing,
622
+ update: ({ message }) => {
623
+ messages.push(message);
624
+ },
625
+ onFinish: async ({ finishReason, message }) => {
626
+ if (finishReason === 'tool-calls') {
627
+ const toolCall = [...(message?.parts ?? [])]
628
+ .reverse()
629
+ .find(part => part.type === 'tool-invocation')?.toolInvocation;
630
+ if (toolCall) {
631
+ toolCalls.push(toolCall);
632
+ }
633
+
634
+ // Handle tool calls if needed
635
+ for (const toolCall of toolCalls) {
636
+ const clientTool = processedParams.clientTools?.[toolCall.toolName] as Tool;
637
+ if (clientTool && clientTool.execute) {
638
+ const result = await clientTool.execute(
639
+ {
640
+ context: toolCall?.args,
641
+ runId: processedParams.runId,
642
+ resourceId: processedParams.resourceId,
643
+ threadId: processedParams.threadId,
644
+ runtimeContext: processedParams.runtimeContext as RuntimeContext,
645
+ },
646
+ {
647
+ messages: (response as unknown as { messages: CoreMessage[] }).messages,
648
+ toolCallId: toolCall?.toolCallId,
649
+ },
650
+ );
651
+
652
+ const lastMessage: UIMessage = JSON.parse(JSON.stringify(messages[messages.length - 1]));
653
+
654
+ const toolInvocationPart = lastMessage?.parts?.find(
655
+ part => part.type === 'tool-invocation' && part.toolInvocation?.toolCallId === toolCall.toolCallId,
656
+ ) as ToolInvocationUIPart | undefined;
657
+
658
+ if (toolInvocationPart) {
659
+ toolInvocationPart.toolInvocation = {
660
+ ...toolInvocationPart.toolInvocation,
661
+ state: 'result',
662
+ result,
663
+ };
664
+ }
665
+
666
+ const toolInvocation = lastMessage?.toolInvocations?.find(
667
+ toolInvocation => toolInvocation.toolCallId === toolCall.toolCallId,
668
+ ) as ToolInvocation | undefined;
669
+
670
+ if (toolInvocation) {
671
+ toolInvocation.state = 'result';
672
+ // @ts-ignore
673
+ toolInvocation.result = result;
674
+ }
675
+
676
+ // write the tool result part to the stream
677
+ const writer = writable.getWriter();
678
+
679
+ try {
680
+ await writer.write(
681
+ new TextEncoder().encode(
682
+ 'a:' +
683
+ JSON.stringify({
684
+ toolCallId: toolCall.toolCallId,
685
+ result,
686
+ }) +
687
+ '\n',
688
+ ),
689
+ );
690
+ } finally {
691
+ writer.releaseLock();
692
+ }
693
+
694
+ // Convert messages to the correct format for the recursive call
695
+ const originalMessages = processedParams.messages;
696
+ const messageArray = Array.isArray(originalMessages) ? originalMessages : [originalMessages];
697
+
698
+ // Recursively call stream with updated messages
699
+ this.processStreamResponse(
700
+ {
701
+ ...processedParams,
702
+ messages: [...messageArray, ...messages, lastMessage],
703
+ },
704
+ writable,
705
+ );
706
+ }
707
+ }
708
+ } else {
709
+ setTimeout(() => {
710
+ writable.close();
711
+ }, 0);
712
+ }
713
+ },
714
+ lastMessage: undefined,
715
+ });
716
+ } catch (error) {
717
+ console.error('Error processing stream response:', error);
718
+ }
719
+ return response;
90
720
  }
91
721
 
92
722
  /**
@@ -98,6 +728,23 @@ export class Agent extends BaseResource {
98
728
  return this.request(`/api/agents/${this.agentId}/tools/${toolId}`);
99
729
  }
100
730
 
731
+ /**
732
+ * Executes a tool for the agent
733
+ * @param toolId - ID of the tool to execute
734
+ * @param params - Parameters required for tool execution
735
+ * @returns Promise containing the tool execution results
736
+ */
737
+ executeTool(toolId: string, params: { data: any; runtimeContext?: RuntimeContext }): Promise<any> {
738
+ const body = {
739
+ data: params.data,
740
+ runtimeContext: params.runtimeContext ? Object.fromEntries(params.runtimeContext.entries()) : undefined,
741
+ };
742
+ return this.request(`/api/agents/${this.agentId}/tools/${toolId}/execute`, {
743
+ method: 'POST',
744
+ body,
745
+ });
746
+ }
747
+
101
748
  /**
102
749
  * Retrieves evaluation results for the agent
103
750
  * @returns Promise containing agent evaluations