@mastra/client-js 0.0.0-vector-query-sources-20250516172905 → 0.0.0-vector-query-tool-provider-options-20250828222356

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 (91) hide show
  1. package/.turbo/turbo-build.log +18 -0
  2. package/CHANGELOG.md +1318 -2
  3. package/LICENSE.md +11 -42
  4. package/README.md +2 -1
  5. package/dist/adapters/agui.d.ts +23 -0
  6. package/dist/adapters/agui.d.ts.map +1 -0
  7. package/dist/client.d.ts +274 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/example.d.ts +2 -0
  10. package/dist/example.d.ts.map +1 -0
  11. package/dist/index.cjs +1801 -137
  12. package/dist/index.cjs.map +1 -0
  13. package/dist/index.d.ts +4 -883
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +1803 -139
  16. package/dist/index.js.map +1 -0
  17. package/dist/resources/a2a.d.ts +41 -0
  18. package/dist/resources/a2a.d.ts.map +1 -0
  19. package/dist/resources/agent.d.ts +130 -0
  20. package/dist/resources/agent.d.ts.map +1 -0
  21. package/dist/resources/base.d.ts +13 -0
  22. package/dist/resources/base.d.ts.map +1 -0
  23. package/dist/resources/index.d.ts +12 -0
  24. package/dist/resources/index.d.ts.map +1 -0
  25. package/dist/resources/legacy-workflow.d.ts +87 -0
  26. package/dist/resources/legacy-workflow.d.ts.map +1 -0
  27. package/dist/resources/mcp-tool.d.ts +27 -0
  28. package/dist/resources/mcp-tool.d.ts.map +1 -0
  29. package/dist/resources/memory-thread.d.ts +53 -0
  30. package/dist/resources/memory-thread.d.ts.map +1 -0
  31. package/dist/resources/network-memory-thread.d.ts +47 -0
  32. package/dist/resources/network-memory-thread.d.ts.map +1 -0
  33. package/dist/resources/network.d.ts +30 -0
  34. package/dist/resources/network.d.ts.map +1 -0
  35. package/dist/resources/observability.d.ts +19 -0
  36. package/dist/resources/observability.d.ts.map +1 -0
  37. package/dist/resources/tool.d.ts +23 -0
  38. package/dist/resources/tool.d.ts.map +1 -0
  39. package/dist/resources/vNextNetwork.d.ts +42 -0
  40. package/dist/resources/vNextNetwork.d.ts.map +1 -0
  41. package/dist/resources/vector.d.ts +48 -0
  42. package/dist/resources/vector.d.ts.map +1 -0
  43. package/dist/resources/workflow.d.ts +154 -0
  44. package/dist/resources/workflow.d.ts.map +1 -0
  45. package/dist/types.d.ts +449 -0
  46. package/dist/types.d.ts.map +1 -0
  47. package/dist/utils/index.d.ts +3 -0
  48. package/dist/utils/index.d.ts.map +1 -0
  49. package/dist/utils/process-client-tools.d.ts +3 -0
  50. package/dist/utils/process-client-tools.d.ts.map +1 -0
  51. package/dist/utils/process-mastra-stream.d.ts +7 -0
  52. package/dist/utils/process-mastra-stream.d.ts.map +1 -0
  53. package/dist/utils/zod-to-json-schema.d.ts +3 -0
  54. package/dist/utils/zod-to-json-schema.d.ts.map +1 -0
  55. package/eslint.config.js +6 -1
  56. package/integration-tests/agui-adapter.test.ts +122 -0
  57. package/integration-tests/package.json +18 -0
  58. package/integration-tests/src/mastra/index.ts +35 -0
  59. package/integration-tests/vitest.config.ts +9 -0
  60. package/package.json +32 -19
  61. package/src/adapters/agui.test.ts +116 -3
  62. package/src/adapters/agui.ts +30 -12
  63. package/src/client.ts +333 -24
  64. package/src/example.ts +46 -15
  65. package/src/index.test.ts +429 -6
  66. package/src/index.ts +1 -0
  67. package/src/resources/a2a.ts +35 -25
  68. package/src/resources/agent.ts +1284 -20
  69. package/src/resources/base.ts +8 -1
  70. package/src/resources/index.ts +3 -2
  71. package/src/resources/{vnext-workflow.ts → legacy-workflow.ts} +124 -143
  72. package/src/resources/memory-thread.test.ts +285 -0
  73. package/src/resources/memory-thread.ts +37 -1
  74. package/src/resources/network-memory-thread.test.ts +269 -0
  75. package/src/resources/network-memory-thread.ts +81 -0
  76. package/src/resources/network.ts +7 -7
  77. package/src/resources/observability.ts +53 -0
  78. package/src/resources/tool.ts +4 -3
  79. package/src/resources/vNextNetwork.ts +194 -0
  80. package/src/resources/workflow.ts +255 -96
  81. package/src/types.ts +262 -36
  82. package/src/utils/index.ts +11 -0
  83. package/src/utils/process-client-tools.ts +32 -0
  84. package/src/utils/process-mastra-stream.test.ts +353 -0
  85. package/src/utils/process-mastra-stream.ts +49 -0
  86. package/src/utils/zod-to-json-schema.ts +23 -3
  87. package/src/v2-messages.test.ts +180 -0
  88. package/tsconfig.build.json +9 -0
  89. package/tsconfig.json +2 -2
  90. package/tsup.config.ts +17 -0
  91. package/dist/index.d.cts +0 -883
@@ -1,8 +1,21 @@
1
- import { processDataStream } from '@ai-sdk/ui-utils';
2
- import type { GenerateReturn } from '@mastra/core';
1
+ import { parsePartialJson, processDataStream } from '@ai-sdk/ui-utils';
2
+ import type {
3
+ JSONValue,
4
+ ReasoningUIPart,
5
+ TextUIPart,
6
+ ToolInvocation,
7
+ ToolInvocationUIPart,
8
+ UIMessage,
9
+ UseChatOptions,
10
+ } from '@ai-sdk/ui-utils';
11
+ import { v4 as uuid } from '@lukeed/uuid';
12
+ import type { MessageListInput } from '@mastra/core/agent/message-list';
13
+ import type { GenerateReturn, CoreMessage } from '@mastra/core/llm';
14
+ import type { RuntimeContext } from '@mastra/core/runtime-context';
15
+ import type { OutputSchema, MastraModelOutput } from '@mastra/core/stream';
16
+ import type { Tool } from '@mastra/core/tools';
3
17
  import type { JSONSchema7 } from 'json-schema';
4
- import { ZodSchema } from 'zod';
5
- import { zodToJsonSchema } from '../utils/zod-to-json-schema';
18
+ import type { ZodType } from 'zod';
6
19
 
7
20
  import type {
8
21
  GenerateParams,
@@ -11,10 +24,92 @@ import type {
11
24
  GetToolResponse,
12
25
  ClientOptions,
13
26
  StreamParams,
27
+ UpdateModelParams,
28
+ StreamVNextParams,
14
29
  } from '../types';
15
30
 
31
+ import { parseClientRuntimeContext } from '../utils';
32
+ import { processClientTools } from '../utils/process-client-tools';
33
+ import { processMastraStream } from '../utils/process-mastra-stream';
34
+ import { zodToJsonSchema } from '../utils/zod-to-json-schema';
16
35
  import { BaseResource } from './base';
17
- import type { RuntimeContext } from '@mastra/core/di';
36
+
37
+ async function executeToolCallAndRespond({
38
+ response,
39
+ params,
40
+ runId,
41
+ resourceId,
42
+ threadId,
43
+ runtimeContext,
44
+ respondFn,
45
+ }: {
46
+ params: StreamVNextParams<any>;
47
+ response: Awaited<ReturnType<MastraModelOutput['getFullOutput']>>;
48
+ runId?: string;
49
+ resourceId?: string;
50
+ threadId?: string;
51
+ runtimeContext?: RuntimeContext<any>;
52
+ respondFn: Agent['generateVNext'];
53
+ }) {
54
+ if (response.finishReason === 'tool-calls') {
55
+ const toolCalls = (
56
+ response as unknown as {
57
+ toolCalls: { toolName: string; args: any; toolCallId: string }[];
58
+ messages: CoreMessage[];
59
+ }
60
+ ).toolCalls;
61
+
62
+ if (!toolCalls || !Array.isArray(toolCalls)) {
63
+ return response;
64
+ }
65
+
66
+ for (const toolCall of toolCalls) {
67
+ const clientTool = params.clientTools?.[toolCall.toolName] as Tool;
68
+
69
+ if (clientTool && clientTool.execute) {
70
+ const result = await clientTool.execute(
71
+ {
72
+ context: toolCall?.args,
73
+ runId,
74
+ resourceId,
75
+ threadId,
76
+ runtimeContext: runtimeContext as RuntimeContext,
77
+ tracingContext: { currentSpan: undefined },
78
+ },
79
+ {
80
+ messages: (response as unknown as { messages: CoreMessage[] }).messages,
81
+ toolCallId: toolCall?.toolCallId,
82
+ },
83
+ );
84
+
85
+ const updatedMessages = [
86
+ {
87
+ role: 'user',
88
+ content: params.messages,
89
+ },
90
+ ...(response.response as unknown as { messages: CoreMessage[] }).messages,
91
+ {
92
+ role: 'tool',
93
+ content: [
94
+ {
95
+ type: 'tool-result',
96
+ toolCallId: toolCall.toolCallId,
97
+ toolName: toolCall.toolName,
98
+ result,
99
+ },
100
+ ],
101
+ },
102
+ ] as MessageListInput;
103
+
104
+ // @ts-ignore
105
+ return respondFn({
106
+ ...params,
107
+ messages: updatedMessages,
108
+ });
109
+ }
110
+ }
111
+ }
112
+ }
18
113
 
19
114
  export class AgentVoice extends BaseResource {
20
115
  constructor(
@@ -69,6 +164,14 @@ export class AgentVoice extends BaseResource {
69
164
  getSpeakers(): Promise<Array<{ voiceId: string; [key: string]: any }>> {
70
165
  return this.request(`/api/agents/${this.agentId}/voice/speakers`);
71
166
  }
167
+
168
+ /**
169
+ * Get the listener configuration for the agent's voice provider
170
+ * @returns Promise containing a check if the agent has listening capabilities
171
+ */
172
+ getListener(): Promise<{ enabled: boolean }> {
173
+ return this.request(`/api/agents/${this.agentId}/voice/listener`);
174
+ }
72
175
  }
73
176
 
74
177
  export class Agent extends BaseResource {
@@ -95,20 +198,476 @@ export class Agent extends BaseResource {
95
198
  * @param params - Generation parameters including prompt
96
199
  * @returns Promise containing the generated response
97
200
  */
98
- generate<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
99
- params: GenerateParams<T>,
100
- ): Promise<GenerateReturn<T>> {
201
+ async generate(
202
+ params: GenerateParams<undefined> & { output?: never; experimental_output?: never },
203
+ ): Promise<GenerateReturn<any, undefined, undefined>>;
204
+ async generate<Output extends JSONSchema7 | ZodType>(
205
+ params: GenerateParams<Output> & { output: Output; experimental_output?: never },
206
+ ): Promise<GenerateReturn<any, Output, undefined>>;
207
+ async generate<StructuredOutput extends JSONSchema7 | ZodType>(
208
+ params: GenerateParams<StructuredOutput> & { output?: never; experimental_output: StructuredOutput },
209
+ ): Promise<GenerateReturn<any, undefined, StructuredOutput>>;
210
+ async generate<
211
+ Output extends JSONSchema7 | ZodType | undefined = undefined,
212
+ StructuredOutput extends JSONSchema7 | ZodType | undefined = undefined,
213
+ >(params: GenerateParams<Output>): Promise<GenerateReturn<any, Output, StructuredOutput>> {
101
214
  const processedParams = {
102
215
  ...params,
103
216
  output: params.output ? zodToJsonSchema(params.output) : undefined,
104
217
  experimental_output: params.experimental_output ? zodToJsonSchema(params.experimental_output) : undefined,
105
- runtimeContext: params.runtimeContext ? Object.fromEntries(params.runtimeContext.entries()) : undefined,
218
+ runtimeContext: parseClientRuntimeContext(params.runtimeContext),
219
+ clientTools: processClientTools(params.clientTools),
106
220
  };
107
221
 
108
- return this.request(`/api/agents/${this.agentId}/generate`, {
109
- method: 'POST',
110
- body: processedParams,
222
+ const { runId, resourceId, threadId, runtimeContext } = processedParams as GenerateParams;
223
+
224
+ const response: GenerateReturn<any, Output, StructuredOutput> = await this.request(
225
+ `/api/agents/${this.agentId}/generate`,
226
+ {
227
+ method: 'POST',
228
+ body: processedParams,
229
+ },
230
+ );
231
+
232
+ if (response.finishReason === 'tool-calls') {
233
+ const toolCalls = (
234
+ response as unknown as {
235
+ toolCalls: { toolName: string; args: any; toolCallId: string }[];
236
+ messages: CoreMessage[];
237
+ }
238
+ ).toolCalls;
239
+
240
+ if (!toolCalls || !Array.isArray(toolCalls)) {
241
+ return response;
242
+ }
243
+
244
+ for (const toolCall of toolCalls) {
245
+ const clientTool = params.clientTools?.[toolCall.toolName] as Tool;
246
+
247
+ if (clientTool && clientTool.execute) {
248
+ const result = await clientTool.execute(
249
+ {
250
+ context: toolCall?.args,
251
+ runId,
252
+ resourceId,
253
+ threadId,
254
+ runtimeContext: runtimeContext as RuntimeContext,
255
+ tracingContext: { currentSpan: undefined },
256
+ },
257
+ {
258
+ messages: (response as unknown as { messages: CoreMessage[] }).messages,
259
+ toolCallId: toolCall?.toolCallId,
260
+ },
261
+ );
262
+
263
+ const updatedMessages = [
264
+ {
265
+ role: 'user',
266
+ content: params.messages,
267
+ },
268
+ ...(response.response as unknown as { messages: CoreMessage[] }).messages,
269
+ {
270
+ role: 'tool',
271
+ content: [
272
+ {
273
+ type: 'tool-result',
274
+ toolCallId: toolCall.toolCallId,
275
+ toolName: toolCall.toolName,
276
+ result,
277
+ },
278
+ ],
279
+ },
280
+ ];
281
+ // @ts-ignore
282
+ return this.generate({
283
+ ...params,
284
+ messages: updatedMessages,
285
+ });
286
+ }
287
+ }
288
+ }
289
+
290
+ return response;
291
+ }
292
+
293
+ async generateVNext<T extends OutputSchema | undefined = undefined>(
294
+ params: StreamVNextParams<T>,
295
+ ): Promise<ReturnType<MastraModelOutput['getFullOutput']>> {
296
+ const processedParams = {
297
+ ...params,
298
+ output: params.output ? zodToJsonSchema(params.output) : undefined,
299
+ runtimeContext: parseClientRuntimeContext(params.runtimeContext),
300
+ clientTools: processClientTools(params.clientTools),
301
+ };
302
+
303
+ const { runId, resourceId, threadId, runtimeContext } = processedParams as StreamVNextParams;
304
+
305
+ const response = await this.request<ReturnType<MastraModelOutput['getFullOutput']>>(
306
+ `/api/agents/${this.agentId}/generate/vnext`,
307
+ {
308
+ method: 'POST',
309
+ body: processedParams,
310
+ },
311
+ );
312
+
313
+ if (response.finishReason === 'tool-calls') {
314
+ return executeToolCallAndRespond({
315
+ response,
316
+ params,
317
+ runId,
318
+ resourceId,
319
+ threadId,
320
+ runtimeContext: runtimeContext as RuntimeContext<any>,
321
+ respondFn: this.generateVNext.bind(this),
322
+ }) as unknown as Awaited<ReturnType<MastraModelOutput['getFullOutput']>>;
323
+ }
324
+
325
+ return response;
326
+ }
327
+
328
+ private async processChatResponse({
329
+ stream,
330
+ update,
331
+ onToolCall,
332
+ onFinish,
333
+ getCurrentDate = () => new Date(),
334
+ lastMessage,
335
+ }: {
336
+ stream: ReadableStream<Uint8Array>;
337
+ update: (options: { message: UIMessage; data: JSONValue[] | undefined; replaceLastMessage: boolean }) => void;
338
+ onToolCall?: UseChatOptions['onToolCall'];
339
+ onFinish?: (options: { message: UIMessage | undefined; finishReason: string; usage: string }) => void;
340
+ generateId?: () => string;
341
+ getCurrentDate?: () => Date;
342
+ lastMessage: UIMessage | undefined;
343
+ }) {
344
+ const replaceLastMessage = lastMessage?.role === 'assistant';
345
+ let step = replaceLastMessage
346
+ ? 1 +
347
+ // find max step in existing tool invocations:
348
+ (lastMessage.toolInvocations?.reduce((max, toolInvocation) => {
349
+ return Math.max(max, toolInvocation.step ?? 0);
350
+ }, 0) ?? 0)
351
+ : 0;
352
+
353
+ const message: UIMessage = replaceLastMessage
354
+ ? structuredClone(lastMessage)
355
+ : {
356
+ id: uuid(),
357
+ createdAt: getCurrentDate(),
358
+ role: 'assistant',
359
+ content: '',
360
+ parts: [],
361
+ };
362
+
363
+ let currentTextPart: TextUIPart | undefined = undefined;
364
+ let currentReasoningPart: ReasoningUIPart | undefined = undefined;
365
+ let currentReasoningTextDetail: { type: 'text'; text: string; signature?: string } | undefined = undefined;
366
+
367
+ function updateToolInvocationPart(toolCallId: string, invocation: ToolInvocation) {
368
+ const part = message.parts.find(
369
+ part => part.type === 'tool-invocation' && part.toolInvocation.toolCallId === toolCallId,
370
+ ) as ToolInvocationUIPart | undefined;
371
+
372
+ if (part != null) {
373
+ part.toolInvocation = invocation;
374
+ } else {
375
+ message.parts.push({
376
+ type: 'tool-invocation',
377
+ toolInvocation: invocation,
378
+ });
379
+ }
380
+ }
381
+
382
+ const data: JSONValue[] = [];
383
+
384
+ // keep list of current message annotations for message
385
+ let messageAnnotations: JSONValue[] | undefined = replaceLastMessage ? lastMessage?.annotations : undefined;
386
+
387
+ // keep track of partial tool calls
388
+ const partialToolCalls: Record<string, { text: string; step: number; index: number; toolName: string }> = {};
389
+
390
+ let usage: any = {
391
+ completionTokens: NaN,
392
+ promptTokens: NaN,
393
+ totalTokens: NaN,
394
+ };
395
+ let finishReason: string = 'unknown';
396
+
397
+ function execUpdate() {
398
+ // make a copy of the data array to ensure UI is updated (SWR)
399
+ const copiedData = [...data];
400
+
401
+ // keeps the currentMessage up to date with the latest annotations,
402
+ // even if annotations preceded the message creation
403
+ if (messageAnnotations?.length) {
404
+ message.annotations = messageAnnotations;
405
+ }
406
+
407
+ const copiedMessage = {
408
+ // deep copy the message to ensure that deep changes (msg attachments) are updated
409
+ // with SolidJS. SolidJS uses referential integration of sub-objects to detect changes.
410
+ ...structuredClone(message),
411
+ // add a revision id to ensure that the message is updated with SWR. SWR uses a
412
+ // hashing approach by default to detect changes, but it only works for shallow
413
+ // changes. This is why we need to add a revision id to ensure that the message
414
+ // is updated with SWR (without it, the changes get stuck in SWR and are not
415
+ // forwarded to rendering):
416
+ revisionId: uuid(),
417
+ } as UIMessage;
418
+
419
+ update({
420
+ message: copiedMessage,
421
+ data: copiedData,
422
+ replaceLastMessage,
423
+ });
424
+ }
425
+
426
+ await processDataStream({
427
+ stream,
428
+ onTextPart(value) {
429
+ if (currentTextPart == null) {
430
+ currentTextPart = {
431
+ type: 'text',
432
+ text: value,
433
+ };
434
+ message.parts.push(currentTextPart);
435
+ } else {
436
+ currentTextPart.text += value;
437
+ }
438
+
439
+ message.content += value;
440
+ execUpdate();
441
+ },
442
+ onReasoningPart(value) {
443
+ if (currentReasoningTextDetail == null) {
444
+ currentReasoningTextDetail = { type: 'text', text: value };
445
+ if (currentReasoningPart != null) {
446
+ currentReasoningPart.details.push(currentReasoningTextDetail);
447
+ }
448
+ } else {
449
+ currentReasoningTextDetail.text += value;
450
+ }
451
+
452
+ if (currentReasoningPart == null) {
453
+ currentReasoningPart = {
454
+ type: 'reasoning',
455
+ reasoning: value,
456
+ details: [currentReasoningTextDetail],
457
+ };
458
+ message.parts.push(currentReasoningPart);
459
+ } else {
460
+ currentReasoningPart.reasoning += value;
461
+ }
462
+
463
+ message.reasoning = (message.reasoning ?? '') + value;
464
+
465
+ execUpdate();
466
+ },
467
+ onReasoningSignaturePart(value) {
468
+ if (currentReasoningTextDetail != null) {
469
+ currentReasoningTextDetail.signature = value.signature;
470
+ }
471
+ },
472
+ onRedactedReasoningPart(value) {
473
+ if (currentReasoningPart == null) {
474
+ currentReasoningPart = {
475
+ type: 'reasoning',
476
+ reasoning: '',
477
+ details: [],
478
+ };
479
+ message.parts.push(currentReasoningPart);
480
+ }
481
+
482
+ currentReasoningPart.details.push({
483
+ type: 'redacted',
484
+ data: value.data,
485
+ });
486
+
487
+ currentReasoningTextDetail = undefined;
488
+
489
+ execUpdate();
490
+ },
491
+ onFilePart(value) {
492
+ message.parts.push({
493
+ type: 'file',
494
+ mimeType: value.mimeType,
495
+ data: value.data,
496
+ });
497
+
498
+ execUpdate();
499
+ },
500
+ onSourcePart(value) {
501
+ message.parts.push({
502
+ type: 'source',
503
+ source: value,
504
+ });
505
+
506
+ execUpdate();
507
+ },
508
+ onToolCallStreamingStartPart(value) {
509
+ if (message.toolInvocations == null) {
510
+ message.toolInvocations = [];
511
+ }
512
+
513
+ // add the partial tool call to the map
514
+ partialToolCalls[value.toolCallId] = {
515
+ text: '',
516
+ step,
517
+ toolName: value.toolName,
518
+ index: message.toolInvocations.length,
519
+ };
520
+
521
+ const invocation = {
522
+ state: 'partial-call',
523
+ step,
524
+ toolCallId: value.toolCallId,
525
+ toolName: value.toolName,
526
+ args: undefined,
527
+ } as const;
528
+
529
+ message.toolInvocations.push(invocation);
530
+
531
+ updateToolInvocationPart(value.toolCallId, invocation);
532
+
533
+ execUpdate();
534
+ },
535
+ onToolCallDeltaPart(value) {
536
+ const partialToolCall = partialToolCalls[value.toolCallId];
537
+
538
+ partialToolCall!.text += value.argsTextDelta;
539
+
540
+ const { value: partialArgs } = parsePartialJson(partialToolCall!.text);
541
+
542
+ const invocation = {
543
+ state: 'partial-call',
544
+ step: partialToolCall!.step,
545
+ toolCallId: value.toolCallId,
546
+ toolName: partialToolCall!.toolName,
547
+ args: partialArgs,
548
+ } as const;
549
+
550
+ message.toolInvocations![partialToolCall!.index] = invocation;
551
+
552
+ updateToolInvocationPart(value.toolCallId, invocation);
553
+
554
+ execUpdate();
555
+ },
556
+ async onToolCallPart(value) {
557
+ const invocation = {
558
+ state: 'call',
559
+ step,
560
+ ...value,
561
+ } as const;
562
+
563
+ if (partialToolCalls[value.toolCallId] != null) {
564
+ // change the partial tool call to a full tool call
565
+ message.toolInvocations![partialToolCalls[value.toolCallId]!.index] = invocation;
566
+ } else {
567
+ if (message.toolInvocations == null) {
568
+ message.toolInvocations = [];
569
+ }
570
+
571
+ message.toolInvocations.push(invocation);
572
+ }
573
+
574
+ updateToolInvocationPart(value.toolCallId, invocation);
575
+
576
+ execUpdate();
577
+
578
+ // invoke the onToolCall callback if it exists. This is blocking.
579
+ // In the future we should make this non-blocking, which
580
+ // requires additional state management for error handling etc.
581
+ if (onToolCall) {
582
+ const result = await onToolCall({ toolCall: value });
583
+ if (result != null) {
584
+ const invocation = {
585
+ state: 'result',
586
+ step,
587
+ ...value,
588
+ result,
589
+ } as const;
590
+
591
+ // store the result in the tool invocation
592
+ message.toolInvocations![message.toolInvocations!.length - 1] = invocation;
593
+
594
+ updateToolInvocationPart(value.toolCallId, invocation);
595
+
596
+ execUpdate();
597
+ }
598
+ }
599
+ },
600
+ onToolResultPart(value) {
601
+ const toolInvocations = message.toolInvocations;
602
+
603
+ if (toolInvocations == null) {
604
+ throw new Error('tool_result must be preceded by a tool_call');
605
+ }
606
+
607
+ // find if there is any tool invocation with the same toolCallId
608
+ // and replace it with the result
609
+ const toolInvocationIndex = toolInvocations.findIndex(invocation => invocation.toolCallId === value.toolCallId);
610
+
611
+ if (toolInvocationIndex === -1) {
612
+ throw new Error('tool_result must be preceded by a tool_call with the same toolCallId');
613
+ }
614
+
615
+ const invocation = {
616
+ ...toolInvocations[toolInvocationIndex],
617
+ state: 'result' as const,
618
+ ...value,
619
+ } as const;
620
+
621
+ toolInvocations[toolInvocationIndex] = invocation as ToolInvocation;
622
+
623
+ updateToolInvocationPart(value.toolCallId, invocation as ToolInvocation);
624
+
625
+ execUpdate();
626
+ },
627
+ onDataPart(value) {
628
+ data.push(...value);
629
+ execUpdate();
630
+ },
631
+ onMessageAnnotationsPart(value) {
632
+ if (messageAnnotations == null) {
633
+ messageAnnotations = [...value];
634
+ } else {
635
+ messageAnnotations.push(...value);
636
+ }
637
+
638
+ execUpdate();
639
+ },
640
+ onFinishStepPart(value) {
641
+ step += 1;
642
+
643
+ // reset the current text and reasoning parts
644
+ currentTextPart = value.isContinued ? currentTextPart : undefined;
645
+ currentReasoningPart = undefined;
646
+ currentReasoningTextDetail = undefined;
647
+ },
648
+ onStartStepPart(value) {
649
+ // keep message id stable when we are updating an existing message:
650
+ if (!replaceLastMessage) {
651
+ message.id = value.messageId;
652
+ }
653
+
654
+ // add a step boundary part to the message
655
+ message.parts.push({ type: 'step-start' });
656
+ execUpdate();
657
+ },
658
+ onFinishMessagePart(value) {
659
+ finishReason = value.finishReason;
660
+ if (value.usage != null) {
661
+ // usage = calculateLanguageModelUsage(value.usage);
662
+ usage = value.usage;
663
+ }
664
+ },
665
+ onErrorPart(error) {
666
+ throw new Error(error);
667
+ },
111
668
  });
669
+
670
+ onFinish?.({ message, finishReason, usage });
112
671
  }
113
672
 
114
673
  /**
@@ -116,7 +675,7 @@ export class Agent extends BaseResource {
116
675
  * @param params - Stream parameters including prompt
117
676
  * @returns Promise containing the enhanced Response object with processDataStream method
118
677
  */
119
- async stream<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
678
+ async stream<T extends JSONSchema7 | ZodType | undefined = undefined>(
120
679
  params: StreamParams<T>,
121
680
  ): Promise<
122
681
  Response & {
@@ -127,12 +686,382 @@ export class Agent extends BaseResource {
127
686
  ...params,
128
687
  output: params.output ? zodToJsonSchema(params.output) : undefined,
129
688
  experimental_output: params.experimental_output ? zodToJsonSchema(params.experimental_output) : undefined,
130
- runtimeContext: params.runtimeContext ? Object.fromEntries(params.runtimeContext.entries()) : undefined,
689
+ runtimeContext: parseClientRuntimeContext(params.runtimeContext),
690
+ clientTools: processClientTools(params.clientTools),
131
691
  };
132
692
 
133
- const response: Response & {
693
+ // Create a readable stream that will handle the response processing
694
+ const { readable, writable } = new TransformStream<Uint8Array, Uint8Array>();
695
+
696
+ // Start processing the response in the background
697
+ const response = await this.processStreamResponse(processedParams, writable);
698
+
699
+ // Create a new response with the readable stream
700
+ const streamResponse = new Response(readable, {
701
+ status: response.status,
702
+ statusText: response.statusText,
703
+ headers: response.headers,
704
+ }) as Response & {
134
705
  processDataStream: (options?: Omit<Parameters<typeof processDataStream>[0], 'stream'>) => Promise<void>;
135
- } = await this.request(`/api/agents/${this.agentId}/stream`, {
706
+ };
707
+
708
+ // Add the processDataStream method to the response
709
+ streamResponse.processDataStream = async (options = {}) => {
710
+ await processDataStream({
711
+ stream: streamResponse.body as ReadableStream<Uint8Array>,
712
+ ...options,
713
+ });
714
+ };
715
+
716
+ return streamResponse;
717
+ }
718
+
719
+ private async processChatResponse_vNext({
720
+ stream,
721
+ update,
722
+ onToolCall,
723
+ onFinish,
724
+ getCurrentDate = () => new Date(),
725
+ lastMessage,
726
+ }: {
727
+ stream: ReadableStream<Uint8Array>;
728
+ update: (options: { message: UIMessage; data: JSONValue[] | undefined; replaceLastMessage: boolean }) => void;
729
+ onToolCall?: UseChatOptions['onToolCall'];
730
+ onFinish?: (options: { message: UIMessage | undefined; finishReason: string; usage: string }) => void;
731
+ generateId?: () => string;
732
+ getCurrentDate?: () => Date;
733
+ lastMessage: UIMessage | undefined;
734
+ }) {
735
+ const replaceLastMessage = lastMessage?.role === 'assistant';
736
+ let step = replaceLastMessage
737
+ ? 1 +
738
+ // find max step in existing tool invocations:
739
+ (lastMessage.toolInvocations?.reduce((max, toolInvocation) => {
740
+ return Math.max(max, toolInvocation.step ?? 0);
741
+ }, 0) ?? 0)
742
+ : 0;
743
+
744
+ const message: UIMessage = replaceLastMessage
745
+ ? structuredClone(lastMessage)
746
+ : {
747
+ id: uuid(),
748
+ createdAt: getCurrentDate(),
749
+ role: 'assistant',
750
+ content: '',
751
+ parts: [],
752
+ };
753
+
754
+ let currentTextPart: TextUIPart | undefined = undefined;
755
+ let currentReasoningPart: ReasoningUIPart | undefined = undefined;
756
+ let currentReasoningTextDetail: { type: 'text'; text: string; signature?: string } | undefined = undefined;
757
+
758
+ function updateToolInvocationPart(toolCallId: string, invocation: ToolInvocation) {
759
+ const part = message.parts.find(
760
+ part => part.type === 'tool-invocation' && part.toolInvocation.toolCallId === toolCallId,
761
+ ) as ToolInvocationUIPart | undefined;
762
+
763
+ if (part != null) {
764
+ part.toolInvocation = invocation;
765
+ } else {
766
+ message.parts.push({
767
+ type: 'tool-invocation',
768
+ toolInvocation: invocation,
769
+ });
770
+ }
771
+ }
772
+
773
+ const data: JSONValue[] = [];
774
+
775
+ // keep list of current message annotations for message
776
+ let messageAnnotations: JSONValue[] | undefined = replaceLastMessage ? lastMessage?.annotations : undefined;
777
+
778
+ // keep track of partial tool calls
779
+ const partialToolCalls: Record<string, { text: string; step: number; index: number; toolName: string }> = {};
780
+
781
+ let usage: any = {
782
+ completionTokens: NaN,
783
+ promptTokens: NaN,
784
+ totalTokens: NaN,
785
+ };
786
+ let finishReason: string = 'unknown';
787
+
788
+ function execUpdate() {
789
+ // make a copy of the data array to ensure UI is updated (SWR)
790
+ const copiedData = [...data];
791
+
792
+ // keeps the currentMessage up to date with the latest annotations,
793
+ // even if annotations preceded the message creation
794
+ if (messageAnnotations?.length) {
795
+ message.annotations = messageAnnotations;
796
+ }
797
+
798
+ const copiedMessage = {
799
+ // deep copy the message to ensure that deep changes (msg attachments) are updated
800
+ // with SolidJS. SolidJS uses referential integration of sub-objects to detect changes.
801
+ ...structuredClone(message),
802
+ // add a revision id to ensure that the message is updated with SWR. SWR uses a
803
+ // hashing approach by default to detect changes, but it only works for shallow
804
+ // changes. This is why we need to add a revision id to ensure that the message
805
+ // is updated with SWR (without it, the changes get stuck in SWR and are not
806
+ // forwarded to rendering):
807
+ revisionId: uuid(),
808
+ } as UIMessage;
809
+
810
+ update({
811
+ message: copiedMessage,
812
+ data: copiedData,
813
+ replaceLastMessage,
814
+ });
815
+ }
816
+
817
+ await processMastraStream({
818
+ stream,
819
+ // TODO: casting as any here because the stream types were all typed as any before in core.
820
+ // but this is completely wrong and this fn is probably broken. Remove ":any" and you'll see a bunch of type errors
821
+ onChunk: async (chunk: any) => {
822
+ switch (chunk.type) {
823
+ case 'step-start': {
824
+ // keep message id stable when we are updating an existing message:
825
+ if (!replaceLastMessage) {
826
+ message.id = chunk.payload.messageId;
827
+ }
828
+
829
+ // add a step boundary part to the message
830
+ message.parts.push({ type: 'step-start' });
831
+ execUpdate();
832
+ break;
833
+ }
834
+
835
+ case 'text-delta': {
836
+ if (currentTextPart == null) {
837
+ currentTextPart = {
838
+ type: 'text',
839
+ text: chunk.payload.text,
840
+ };
841
+ message.parts.push(currentTextPart);
842
+ } else {
843
+ currentTextPart.text += chunk.payload.text;
844
+ }
845
+
846
+ message.content += chunk.payload.text;
847
+ execUpdate();
848
+ break;
849
+ }
850
+
851
+ case 'reasoning-delta': {
852
+ if (currentReasoningTextDetail == null) {
853
+ currentReasoningTextDetail = { type: 'text', text: chunk.payload.text };
854
+ if (currentReasoningPart != null) {
855
+ currentReasoningPart.details.push(currentReasoningTextDetail);
856
+ }
857
+ } else {
858
+ currentReasoningTextDetail.text += chunk.payload.text;
859
+ }
860
+
861
+ if (currentReasoningPart == null) {
862
+ currentReasoningPart = {
863
+ type: 'reasoning',
864
+ reasoning: chunk.payload.text,
865
+ details: [currentReasoningTextDetail],
866
+ };
867
+ message.parts.push(currentReasoningPart);
868
+ } else {
869
+ currentReasoningPart.reasoning += chunk.payload.text;
870
+ }
871
+
872
+ message.reasoning = (message.reasoning ?? '') + chunk.payload.text;
873
+
874
+ execUpdate();
875
+ break;
876
+ }
877
+ case 'file': {
878
+ message.parts.push({
879
+ type: 'file',
880
+ mimeType: chunk.payload.mimeType,
881
+ data: chunk.payload.data,
882
+ });
883
+
884
+ execUpdate();
885
+ break;
886
+ }
887
+
888
+ case 'source': {
889
+ message.parts.push({
890
+ type: 'source',
891
+ source: chunk.payload.source,
892
+ });
893
+ execUpdate();
894
+ break;
895
+ }
896
+
897
+ case 'tool-call': {
898
+ const invocation = {
899
+ state: 'call',
900
+ step,
901
+ ...chunk.payload,
902
+ } as const;
903
+
904
+ if (partialToolCalls[chunk.payload.toolCallId] != null) {
905
+ // change the partial tool call to a full tool call
906
+ message.toolInvocations![partialToolCalls[chunk.payload.toolCallId]!.index] =
907
+ invocation as ToolInvocation;
908
+ } else {
909
+ if (message.toolInvocations == null) {
910
+ message.toolInvocations = [];
911
+ }
912
+
913
+ message.toolInvocations.push(invocation as ToolInvocation);
914
+ }
915
+
916
+ updateToolInvocationPart(chunk.payload.toolCallId, invocation as ToolInvocation);
917
+
918
+ execUpdate();
919
+
920
+ // invoke the onToolCall callback if it exists. This is blocking.
921
+ // In the future we should make this non-blocking, which
922
+ // requires additional state management for error handling etc.
923
+ if (onToolCall) {
924
+ const result = await onToolCall({ toolCall: chunk.payload as any });
925
+ if (result != null) {
926
+ const invocation = {
927
+ state: 'result',
928
+ step,
929
+ ...chunk.payload,
930
+ result,
931
+ } as const;
932
+
933
+ // store the result in the tool invocation
934
+ message.toolInvocations![message.toolInvocations!.length - 1] = invocation as ToolInvocation;
935
+
936
+ updateToolInvocationPart(chunk.payload.toolCallId, invocation as ToolInvocation);
937
+
938
+ execUpdate();
939
+ }
940
+ }
941
+ }
942
+
943
+ case 'tool-call-input-streaming-start': {
944
+ if (message.toolInvocations == null) {
945
+ message.toolInvocations = [];
946
+ }
947
+
948
+ // add the partial tool call to the map
949
+ partialToolCalls[chunk.payload.toolCallId] = {
950
+ text: '',
951
+ step,
952
+ toolName: chunk.payload.toolName,
953
+ index: message.toolInvocations.length,
954
+ };
955
+
956
+ const invocation = {
957
+ state: 'partial-call',
958
+ step,
959
+ toolCallId: chunk.payload.toolCallId,
960
+ toolName: chunk.payload.toolName,
961
+ args: undefined,
962
+ } as const;
963
+
964
+ message.toolInvocations.push(invocation as ToolInvocation);
965
+
966
+ updateToolInvocationPart(chunk.payload.toolCallId, invocation);
967
+
968
+ execUpdate();
969
+ break;
970
+ }
971
+
972
+ case 'tool-call-delta': {
973
+ const partialToolCall = partialToolCalls[chunk.payload.toolCallId];
974
+
975
+ partialToolCall!.text += chunk.payload.argsTextDelta;
976
+
977
+ const { value: partialArgs } = parsePartialJson(partialToolCall!.text);
978
+
979
+ const invocation = {
980
+ state: 'partial-call',
981
+ step: partialToolCall!.step,
982
+ toolCallId: chunk.payload.toolCallId,
983
+ toolName: partialToolCall!.toolName,
984
+ args: partialArgs,
985
+ } as const;
986
+
987
+ message.toolInvocations![partialToolCall!.index] = invocation as ToolInvocation;
988
+
989
+ updateToolInvocationPart(chunk.payload.toolCallId, invocation);
990
+
991
+ execUpdate();
992
+ break;
993
+ }
994
+
995
+ case 'tool-result': {
996
+ const toolInvocations = message.toolInvocations;
997
+
998
+ if (toolInvocations == null) {
999
+ throw new Error('tool_result must be preceded by a tool_call');
1000
+ }
1001
+
1002
+ // find if there is any tool invocation with the same toolCallId
1003
+ // and replace it with the result
1004
+ const toolInvocationIndex = toolInvocations.findIndex(
1005
+ invocation => invocation.toolCallId === chunk.payload.toolCallId,
1006
+ );
1007
+
1008
+ if (toolInvocationIndex === -1) {
1009
+ throw new Error('tool_result must be preceded by a tool_call with the same toolCallId');
1010
+ }
1011
+
1012
+ const invocation = {
1013
+ ...toolInvocations[toolInvocationIndex],
1014
+ state: 'result' as const,
1015
+ ...chunk.payload,
1016
+ } as const;
1017
+
1018
+ toolInvocations[toolInvocationIndex] = invocation as ToolInvocation;
1019
+
1020
+ updateToolInvocationPart(chunk.payload.toolCallId, invocation as ToolInvocation);
1021
+
1022
+ execUpdate();
1023
+ break;
1024
+ }
1025
+
1026
+ case 'error': {
1027
+ throw new Error(chunk.payload.error);
1028
+ }
1029
+
1030
+ case 'data': {
1031
+ data.push(...chunk.payload.data);
1032
+ execUpdate();
1033
+ break;
1034
+ }
1035
+
1036
+ case 'step-finish': {
1037
+ step += 1;
1038
+
1039
+ // reset the current text and reasoning parts
1040
+ currentTextPart = chunk.payload.isContinued ? currentTextPart : undefined;
1041
+ currentReasoningPart = undefined;
1042
+ currentReasoningTextDetail = undefined;
1043
+
1044
+ execUpdate();
1045
+ break;
1046
+ }
1047
+
1048
+ case 'finish': {
1049
+ finishReason = chunk.payload.finishReason;
1050
+ if (chunk.payload.usage != null) {
1051
+ // usage = calculateLanguageModelUsage(value.usage);
1052
+ usage = chunk.payload.usage;
1053
+ }
1054
+ break;
1055
+ }
1056
+ }
1057
+ },
1058
+ });
1059
+
1060
+ onFinish?.({ message, finishReason, usage });
1061
+ }
1062
+
1063
+ async processStreamResponse_vNext(processedParams: any, writable: any) {
1064
+ const response: Response = await this.request(`/api/agents/${this.agentId}/stream/vnext`, {
136
1065
  method: 'POST',
137
1066
  body: processedParams,
138
1067
  stream: true,
@@ -142,13 +1071,336 @@ export class Agent extends BaseResource {
142
1071
  throw new Error('No response body');
143
1072
  }
144
1073
 
145
- response.processDataStream = async (options = {}) => {
146
- await processDataStream({
147
- stream: response.body as ReadableStream<Uint8Array>,
148
- ...options,
1074
+ try {
1075
+ let toolCalls: ToolInvocation[] = [];
1076
+ let messages: UIMessage[] = [];
1077
+
1078
+ // Use tee() to split the stream into two branches
1079
+ const [streamForWritable, streamForProcessing] = response.body.tee();
1080
+
1081
+ // Pipe one branch to the writable stream
1082
+ streamForWritable
1083
+ .pipeTo(writable, {
1084
+ preventClose: true,
1085
+ })
1086
+ .catch(error => {
1087
+ console.error('Error piping to writable stream:', error);
1088
+ });
1089
+
1090
+ // Process the other branch for chat response handling
1091
+ this.processChatResponse_vNext({
1092
+ stream: streamForProcessing,
1093
+ update: ({ message }) => {
1094
+ const existingIndex = messages.findIndex(m => m.id === message.id);
1095
+
1096
+ if (existingIndex !== -1) {
1097
+ messages[existingIndex] = message;
1098
+ } else {
1099
+ messages.push(message);
1100
+ }
1101
+ },
1102
+ onFinish: async ({ finishReason, message }) => {
1103
+ if (finishReason === 'tool-calls') {
1104
+ const toolCall = [...(message?.parts ?? [])]
1105
+ .reverse()
1106
+ .find(part => part.type === 'tool-invocation')?.toolInvocation;
1107
+ if (toolCall) {
1108
+ toolCalls.push(toolCall);
1109
+ }
1110
+
1111
+ // Handle tool calls if needed
1112
+ for (const toolCall of toolCalls) {
1113
+ const clientTool = processedParams.clientTools?.[toolCall.toolName] as Tool;
1114
+ if (clientTool && clientTool.execute) {
1115
+ const result = await clientTool.execute(
1116
+ {
1117
+ context: toolCall?.args,
1118
+ runId: processedParams.runId,
1119
+ resourceId: processedParams.resourceId,
1120
+ threadId: processedParams.threadId,
1121
+ runtimeContext: processedParams.runtimeContext as RuntimeContext,
1122
+ // TODO: Pass proper tracing context when client-js supports tracing
1123
+ tracingContext: { currentSpan: undefined },
1124
+ },
1125
+ {
1126
+ messages: (response as unknown as { messages: CoreMessage[] }).messages,
1127
+ toolCallId: toolCall?.toolCallId,
1128
+ },
1129
+ );
1130
+
1131
+ const lastMessage: UIMessage = JSON.parse(JSON.stringify(messages[messages.length - 1]));
1132
+
1133
+ const toolInvocationPart = lastMessage?.parts?.find(
1134
+ part => part.type === 'tool-invocation' && part.toolInvocation?.toolCallId === toolCall.toolCallId,
1135
+ ) as ToolInvocationUIPart | undefined;
1136
+
1137
+ if (toolInvocationPart) {
1138
+ toolInvocationPart.toolInvocation = {
1139
+ ...toolInvocationPart.toolInvocation,
1140
+ state: 'result',
1141
+ result,
1142
+ };
1143
+ }
1144
+
1145
+ const toolInvocation = lastMessage?.toolInvocations?.find(
1146
+ toolInvocation => toolInvocation.toolCallId === toolCall.toolCallId,
1147
+ ) as ToolInvocation | undefined;
1148
+
1149
+ if (toolInvocation) {
1150
+ toolInvocation.state = 'result';
1151
+ // @ts-ignore
1152
+ toolInvocation.result = result;
1153
+ }
1154
+
1155
+ // write the tool result part to the stream
1156
+ const writer = writable.getWriter();
1157
+
1158
+ try {
1159
+ await writer.write(
1160
+ new TextEncoder().encode(
1161
+ 'a:' +
1162
+ JSON.stringify({
1163
+ toolCallId: toolCall.toolCallId,
1164
+ result,
1165
+ }) +
1166
+ '\n',
1167
+ ),
1168
+ );
1169
+ } finally {
1170
+ writer.releaseLock();
1171
+ }
1172
+
1173
+ // Convert messages to the correct format for the recursive call
1174
+ const originalMessages = processedParams.messages;
1175
+ const messageArray = Array.isArray(originalMessages) ? originalMessages : [originalMessages];
1176
+
1177
+ // Recursively call stream with updated messages
1178
+ this.processStreamResponse_vNext(
1179
+ {
1180
+ ...processedParams,
1181
+ messages: [...messageArray, ...messages.filter(m => m.id !== lastMessage.id), lastMessage],
1182
+ },
1183
+ writable,
1184
+ ).catch(error => {
1185
+ console.error('Error processing stream response:', error);
1186
+ });
1187
+ }
1188
+ }
1189
+ } else {
1190
+ setTimeout(() => {
1191
+ writable.close();
1192
+ }, 0);
1193
+ }
1194
+ },
1195
+ lastMessage: undefined,
1196
+ }).catch(error => {
1197
+ console.error('Error processing stream response:', error);
1198
+ });
1199
+ } catch (error) {
1200
+ console.error('Error processing stream response:', error);
1201
+ }
1202
+
1203
+ return response;
1204
+ }
1205
+
1206
+ async streamVNext<T extends OutputSchema | undefined = undefined>(
1207
+ params: StreamVNextParams<T>,
1208
+ ): Promise<
1209
+ Response & {
1210
+ processDataStream: ({
1211
+ onChunk,
1212
+ }: {
1213
+ onChunk: Parameters<typeof processMastraStream>[0]['onChunk'];
1214
+ }) => Promise<void>;
1215
+ }
1216
+ > {
1217
+ const processedParams = {
1218
+ ...params,
1219
+ output: params.output ? zodToJsonSchema(params.output) : undefined,
1220
+ runtimeContext: parseClientRuntimeContext(params.runtimeContext),
1221
+ clientTools: processClientTools(params.clientTools),
1222
+ };
1223
+
1224
+ // Create a readable stream that will handle the response processing
1225
+ const { readable, writable } = new TransformStream<Uint8Array, Uint8Array>();
1226
+
1227
+ // Start processing the response in the background
1228
+ const response = await this.processStreamResponse_vNext(processedParams, writable);
1229
+
1230
+ // Create a new response with the readable stream
1231
+ const streamResponse = new Response(readable, {
1232
+ status: response.status,
1233
+ statusText: response.statusText,
1234
+ headers: response.headers,
1235
+ }) as Response & {
1236
+ processDataStream: ({
1237
+ onChunk,
1238
+ }: {
1239
+ onChunk: Parameters<typeof processMastraStream>[0]['onChunk'];
1240
+ }) => Promise<void>;
1241
+ };
1242
+
1243
+ // Add the processDataStream method to the response
1244
+ streamResponse.processDataStream = async ({
1245
+ onChunk,
1246
+ }: {
1247
+ onChunk: Parameters<typeof processMastraStream>[0]['onChunk'];
1248
+ }) => {
1249
+ await processMastraStream({
1250
+ stream: streamResponse.body as ReadableStream<Uint8Array>,
1251
+ onChunk,
149
1252
  });
150
1253
  };
151
1254
 
1255
+ return streamResponse;
1256
+ }
1257
+
1258
+ /**
1259
+ * Processes the stream response and handles tool calls
1260
+ */
1261
+ private async processStreamResponse(processedParams: any, writable: WritableStream<Uint8Array>) {
1262
+ const response: Response & {
1263
+ processDataStream: (options?: Omit<Parameters<typeof processDataStream>[0], 'stream'>) => Promise<void>;
1264
+ } = await this.request(`/api/agents/${this.agentId}/stream`, {
1265
+ method: 'POST',
1266
+ body: processedParams,
1267
+ stream: true,
1268
+ });
1269
+
1270
+ if (!response.body) {
1271
+ throw new Error('No response body');
1272
+ }
1273
+
1274
+ try {
1275
+ let toolCalls: ToolInvocation[] = [];
1276
+ let messages: UIMessage[] = [];
1277
+
1278
+ // Use tee() to split the stream into two branches
1279
+ const [streamForWritable, streamForProcessing] = response.body.tee();
1280
+
1281
+ // Pipe one branch to the writable stream
1282
+ streamForWritable
1283
+ .pipeTo(writable, {
1284
+ preventClose: true,
1285
+ })
1286
+ .catch(error => {
1287
+ console.error('Error piping to writable stream:', error);
1288
+ });
1289
+
1290
+ // Process the other branch for chat response handling
1291
+ this.processChatResponse({
1292
+ stream: streamForProcessing,
1293
+ update: ({ message }) => {
1294
+ const existingIndex = messages.findIndex(m => m.id === message.id);
1295
+
1296
+ if (existingIndex !== -1) {
1297
+ messages[existingIndex] = message;
1298
+ } else {
1299
+ messages.push(message);
1300
+ }
1301
+ },
1302
+ onFinish: async ({ finishReason, message }) => {
1303
+ if (finishReason === 'tool-calls') {
1304
+ const toolCall = [...(message?.parts ?? [])]
1305
+ .reverse()
1306
+ .find(part => part.type === 'tool-invocation')?.toolInvocation;
1307
+ if (toolCall) {
1308
+ toolCalls.push(toolCall);
1309
+ }
1310
+
1311
+ // Handle tool calls if needed
1312
+ for (const toolCall of toolCalls) {
1313
+ const clientTool = processedParams.clientTools?.[toolCall.toolName] as Tool;
1314
+ if (clientTool && clientTool.execute) {
1315
+ const result = await clientTool.execute(
1316
+ {
1317
+ context: toolCall?.args,
1318
+ runId: processedParams.runId,
1319
+ resourceId: processedParams.resourceId,
1320
+ threadId: processedParams.threadId,
1321
+ runtimeContext: processedParams.runtimeContext as RuntimeContext,
1322
+ // TODO: Pass proper tracing context when client-js supports tracing
1323
+ tracingContext: { currentSpan: undefined },
1324
+ },
1325
+ {
1326
+ messages: (response as unknown as { messages: CoreMessage[] }).messages,
1327
+ toolCallId: toolCall?.toolCallId,
1328
+ },
1329
+ );
1330
+
1331
+ const lastMessage: UIMessage = JSON.parse(JSON.stringify(messages[messages.length - 1]));
1332
+
1333
+ const toolInvocationPart = lastMessage?.parts?.find(
1334
+ part => part.type === 'tool-invocation' && part.toolInvocation?.toolCallId === toolCall.toolCallId,
1335
+ ) as ToolInvocationUIPart | undefined;
1336
+
1337
+ if (toolInvocationPart) {
1338
+ toolInvocationPart.toolInvocation = {
1339
+ ...toolInvocationPart.toolInvocation,
1340
+ state: 'result',
1341
+ result,
1342
+ };
1343
+ }
1344
+
1345
+ const toolInvocation = lastMessage?.toolInvocations?.find(
1346
+ toolInvocation => toolInvocation.toolCallId === toolCall.toolCallId,
1347
+ ) as ToolInvocation | undefined;
1348
+
1349
+ if (toolInvocation) {
1350
+ toolInvocation.state = 'result';
1351
+ // @ts-ignore
1352
+ toolInvocation.result = result;
1353
+ }
1354
+
1355
+ // write the tool result part to the stream
1356
+ const writer = writable.getWriter();
1357
+
1358
+ try {
1359
+ await writer.write(
1360
+ new TextEncoder().encode(
1361
+ 'a:' +
1362
+ JSON.stringify({
1363
+ toolCallId: toolCall.toolCallId,
1364
+ result,
1365
+ }) +
1366
+ '\n',
1367
+ ),
1368
+ );
1369
+ } finally {
1370
+ writer.releaseLock();
1371
+ }
1372
+
1373
+ // Convert messages to the correct format for the recursive call
1374
+ const originalMessages = processedParams.messages;
1375
+ const messageArray = Array.isArray(originalMessages) ? originalMessages : [originalMessages];
1376
+
1377
+ // Recursively call stream with updated messages
1378
+ this.processStreamResponse(
1379
+ {
1380
+ ...processedParams,
1381
+ messages: [...messageArray, ...messages.filter(m => m.id !== lastMessage.id), lastMessage],
1382
+ },
1383
+ writable,
1384
+ ).catch(error => {
1385
+ console.error('Error processing stream response:', error);
1386
+ });
1387
+ }
1388
+ }
1389
+ } else {
1390
+ setTimeout(() => {
1391
+ // We can't close the stream in this function, we have to wait until it's done
1392
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
1393
+ writable.close();
1394
+ }, 0);
1395
+ }
1396
+ },
1397
+ lastMessage: undefined,
1398
+ }).catch(error => {
1399
+ console.error('Error processing stream response:', error);
1400
+ });
1401
+ } catch (error) {
1402
+ console.error('Error processing stream response:', error);
1403
+ }
152
1404
  return response;
153
1405
  }
154
1406
 
@@ -193,4 +1445,16 @@ export class Agent extends BaseResource {
193
1445
  liveEvals(): Promise<GetEvalsByAgentIdResponse> {
194
1446
  return this.request(`/api/agents/${this.agentId}/evals/live`);
195
1447
  }
1448
+
1449
+ /**
1450
+ * Updates the model for the agent
1451
+ * @param params - Parameters for updating the model
1452
+ * @returns Promise containing the updated model
1453
+ */
1454
+ updateModel(params: UpdateModelParams): Promise<{ message: string }> {
1455
+ return this.request(`/api/agents/${this.agentId}/model`, {
1456
+ method: 'POST',
1457
+ body: params,
1458
+ });
1459
+ }
196
1460
  }