@mastra/client-js 0.0.0-mcp-server-deploy-20250507160341 → 0.0.0-memory-system-message-error-20250813233316

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