@mastra/client-js 0.10.10 → 0.10.11-alpha.0

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,6 +1,7 @@
1
1
  import {
2
2
  parsePartialJson,
3
3
  processDataStream,
4
+ processTextStream,
4
5
  type JSONValue,
5
6
  type ReasoningUIPart,
6
7
  type TextUIPart,
@@ -144,12 +145,18 @@ export class Agent extends BaseResource {
144
145
  });
145
146
 
146
147
  if (response.finishReason === 'tool-calls') {
147
- for (const toolCall of (
148
+ const toolCalls = (
148
149
  response as unknown as {
149
150
  toolCalls: { toolName: string; args: any; toolCallId: string }[];
150
151
  messages: CoreMessage[];
151
152
  }
152
- ).toolCalls) {
153
+ ).toolCalls;
154
+
155
+ if (!toolCalls || !Array.isArray(toolCalls)) {
156
+ return response;
157
+ }
158
+
159
+ for (const toolCall of toolCalls) {
153
160
  const clientTool = params.clientTools?.[toolCall.toolName] as Tool;
154
161
 
155
162
  if (clientTool && clientTool.execute) {
@@ -198,6 +205,7 @@ export class Agent extends BaseResource {
198
205
  onFinish,
199
206
  getCurrentDate = () => new Date(),
200
207
  lastMessage,
208
+ streamProtocol,
201
209
  }: {
202
210
  stream: ReadableStream<Uint8Array>;
203
211
  update: (options: { message: UIMessage; data: JSONValue[] | undefined; replaceLastMessage: boolean }) => void;
@@ -206,6 +214,7 @@ export class Agent extends BaseResource {
206
214
  generateId?: () => string;
207
215
  getCurrentDate?: () => Date;
208
216
  lastMessage: UIMessage | undefined;
217
+ streamProtocol: 'text' | 'data';
209
218
  }) {
210
219
  const replaceLastMessage = lastMessage?.role === 'assistant';
211
220
  let step = replaceLastMessage
@@ -289,297 +298,265 @@ export class Agent extends BaseResource {
289
298
  });
290
299
  }
291
300
 
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
- }
301
+ if (streamProtocol === 'text') {
302
+ await processTextStream({
303
+ stream,
304
+ onTextPart(value) {
305
+ message.content += value;
306
+ execUpdate();
307
+ },
308
+ });
304
309
 
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);
310
+ onFinish?.({ message, finishReason, usage });
311
+ } else {
312
+ await processDataStream({
313
+ stream,
314
+ onTextPart(value) {
315
+ if (currentTextPart == null) {
316
+ currentTextPart = {
317
+ type: 'text',
318
+ text: value,
319
+ };
320
+ message.parts.push(currentTextPart);
321
+ } else {
322
+ currentTextPart.text += value;
313
323
  }
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
324
 
355
- execUpdate();
356
- },
357
- onFilePart(value) {
358
- message.parts.push({
359
- type: 'file',
360
- mimeType: value.mimeType,
361
- data: value.data,
362
- });
325
+ message.content += value;
326
+ execUpdate();
327
+ },
328
+ onReasoningPart(value) {
329
+ if (currentReasoningTextDetail == null) {
330
+ currentReasoningTextDetail = { type: 'text', text: value };
331
+ if (currentReasoningPart != null) {
332
+ currentReasoningPart.details.push(currentReasoningTextDetail);
333
+ }
334
+ } else {
335
+ currentReasoningTextDetail.text += value;
336
+ }
363
337
 
364
- execUpdate();
365
- },
366
- onSourcePart(value) {
367
- message.parts.push({
368
- type: 'source',
369
- source: value,
370
- });
338
+ if (currentReasoningPart == null) {
339
+ currentReasoningPart = {
340
+ type: 'reasoning',
341
+ reasoning: value,
342
+ details: [currentReasoningTextDetail],
343
+ };
344
+ message.parts.push(currentReasoningPart);
345
+ } else {
346
+ currentReasoningPart.reasoning += value;
347
+ }
371
348
 
372
- execUpdate();
373
- },
374
- onToolCallStreamingStartPart(value) {
375
- if (message.toolInvocations == null) {
376
- message.toolInvocations = [];
377
- }
349
+ message.reasoning = (message.reasoning ?? '') + value;
378
350
 
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
- };
351
+ execUpdate();
352
+ },
353
+ onReasoningSignaturePart(value) {
354
+ if (currentReasoningTextDetail != null) {
355
+ currentReasoningTextDetail.signature = value.signature;
356
+ }
357
+ },
358
+ onRedactedReasoningPart(value) {
359
+ if (currentReasoningPart == null) {
360
+ currentReasoningPart = {
361
+ type: 'reasoning',
362
+ reasoning: '',
363
+ details: [],
364
+ };
365
+ message.parts.push(currentReasoningPart);
366
+ }
386
367
 
387
- const invocation = {
388
- state: 'partial-call',
389
- step,
390
- toolCallId: value.toolCallId,
391
- toolName: value.toolName,
392
- args: undefined,
393
- } as const;
368
+ currentReasoningPart.details.push({
369
+ type: 'redacted',
370
+ data: value.data,
371
+ });
394
372
 
395
- message.toolInvocations.push(invocation);
373
+ currentReasoningTextDetail = undefined;
396
374
 
397
- updateToolInvocationPart(value.toolCallId, invocation);
375
+ execUpdate();
376
+ },
377
+ onFilePart(value) {
378
+ message.parts.push({
379
+ type: 'file',
380
+ mimeType: value.mimeType,
381
+ data: value.data,
382
+ });
398
383
 
399
- execUpdate();
400
- },
401
- onToolCallDeltaPart(value) {
402
- const partialToolCall = partialToolCalls[value.toolCallId];
384
+ execUpdate();
385
+ },
386
+ onSourcePart(value) {
387
+ message.parts.push({
388
+ type: 'source',
389
+ source: value,
390
+ });
403
391
 
404
- partialToolCall!.text += value.argsTextDelta;
392
+ execUpdate();
393
+ },
394
+ onToolCallStreamingStartPart(value) {
395
+ if (message.toolInvocations == null) {
396
+ message.toolInvocations = [];
397
+ }
405
398
 
406
- const { value: partialArgs } = parsePartialJson(partialToolCall!.text);
399
+ // add the partial tool call to the map
400
+ partialToolCalls[value.toolCallId] = {
401
+ text: '',
402
+ step,
403
+ toolName: value.toolName,
404
+ index: message.toolInvocations.length,
405
+ };
407
406
 
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;
407
+ const invocation = {
408
+ state: 'partial-call',
409
+ step,
410
+ toolCallId: value.toolCallId,
411
+ toolName: value.toolName,
412
+ args: undefined,
413
+ } as const;
415
414
 
416
- message.toolInvocations![partialToolCall!.index] = invocation;
415
+ message.toolInvocations.push(invocation);
417
416
 
418
- updateToolInvocationPart(value.toolCallId, invocation);
417
+ updateToolInvocationPart(value.toolCallId, invocation);
419
418
 
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
- }
419
+ execUpdate();
420
+ },
421
+ onToolCallDeltaPart(value) {
422
+ const partialToolCall = partialToolCalls[value.toolCallId];
436
423
 
437
- message.toolInvocations.push(invocation);
438
- }
424
+ partialToolCall!.text += value.argsTextDelta;
439
425
 
440
- updateToolInvocationPart(value.toolCallId, invocation);
426
+ const { value: partialArgs } = parsePartialJson(partialToolCall!.text);
441
427
 
442
- execUpdate();
428
+ const invocation = {
429
+ state: 'partial-call',
430
+ step: partialToolCall!.step,
431
+ toolCallId: value.toolCallId,
432
+ toolName: partialToolCall!.toolName,
433
+ args: partialArgs,
434
+ } as const;
443
435
 
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;
436
+ message.toolInvocations![partialToolCall!.index] = invocation;
456
437
 
457
- // store the result in the tool invocation
458
- message.toolInvocations![message.toolInvocations!.length - 1] = invocation;
438
+ updateToolInvocationPart(value.toolCallId, invocation);
459
439
 
460
- updateToolInvocationPart(value.toolCallId, invocation);
440
+ execUpdate();
441
+ },
442
+ async onToolCallPart(value) {
443
+ const invocation = {
444
+ state: 'call',
445
+ step,
446
+ ...value,
447
+ } as const;
448
+
449
+ if (partialToolCalls[value.toolCallId] != null) {
450
+ // change the partial tool call to a full tool call
451
+ message.toolInvocations![partialToolCalls[value.toolCallId]!.index] = invocation;
452
+ } else {
453
+ if (message.toolInvocations == null) {
454
+ message.toolInvocations = [];
455
+ }
461
456
 
462
- execUpdate();
457
+ message.toolInvocations.push(invocation);
463
458
  }
464
- }
465
- },
466
- onToolResultPart(value) {
467
- const toolInvocations = message.toolInvocations;
468
459
 
469
- if (toolInvocations == null) {
470
- throw new Error('tool_result must be preceded by a tool_call');
471
- }
460
+ updateToolInvocationPart(value.toolCallId, invocation);
472
461
 
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);
462
+ execUpdate();
476
463
 
477
- if (toolInvocationIndex === -1) {
478
- throw new Error('tool_result must be preceded by a tool_call with the same toolCallId');
479
- }
464
+ // invoke the onToolCall callback if it exists. This is blocking.
465
+ // In the future we should make this non-blocking, which
466
+ // requires additional state management for error handling etc.
467
+ if (onToolCall) {
468
+ const result = await onToolCall({ toolCall: value });
469
+ if (result != null) {
470
+ const invocation = {
471
+ state: 'result',
472
+ step,
473
+ ...value,
474
+ result,
475
+ } as const;
480
476
 
481
- const invocation = {
482
- ...toolInvocations[toolInvocationIndex],
483
- state: 'result' as const,
484
- ...value,
485
- } as const;
477
+ // store the result in the tool invocation
478
+ message.toolInvocations![message.toolInvocations!.length - 1] = invocation;
486
479
 
487
- toolInvocations[toolInvocationIndex] = invocation as ToolInvocation;
480
+ updateToolInvocationPart(value.toolCallId, invocation);
488
481
 
489
- updateToolInvocationPart(value.toolCallId, invocation as ToolInvocation);
482
+ execUpdate();
483
+ }
484
+ }
485
+ },
486
+ onToolResultPart(value) {
487
+ const toolInvocations = message.toolInvocations;
490
488
 
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
- }
489
+ if (toolInvocations == null) {
490
+ throw new Error('tool_result must be preceded by a tool_call');
491
+ }
503
492
 
504
- execUpdate();
505
- },
506
- onFinishStepPart(value) {
507
- step += 1;
493
+ // find if there is any tool invocation with the same toolCallId
494
+ // and replace it with the result
495
+ const toolInvocationIndex = toolInvocations.findIndex(
496
+ invocation => invocation.toolCallId === value.toolCallId,
497
+ );
508
498
 
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
- }
499
+ if (toolInvocationIndex === -1) {
500
+ throw new Error('tool_result must be preceded by a tool_call with the same toolCallId');
501
+ }
519
502
 
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
- });
503
+ const invocation = {
504
+ ...toolInvocations[toolInvocationIndex],
505
+ state: 'result' as const,
506
+ ...value,
507
+ } as const;
535
508
 
536
- onFinish?.({ message, finishReason, usage });
537
- }
509
+ toolInvocations[toolInvocationIndex] = invocation as ToolInvocation;
538
510
 
539
- /**
540
- * Streams a response from the agent
541
- * @param params - Stream parameters including prompt
542
- * @returns Promise containing the enhanced Response object with processDataStream method
543
- */
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
- > {
551
- const processedParams = {
552
- ...params,
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
- };
511
+ updateToolInvocationPart(value.toolCallId, invocation as ToolInvocation);
558
512
 
559
- // Create a readable stream that will handle the response processing
560
- const { readable, writable } = new TransformStream<Uint8Array, Uint8Array>();
513
+ execUpdate();
514
+ },
515
+ onDataPart(value) {
516
+ data.push(...value);
517
+ execUpdate();
518
+ },
519
+ onMessageAnnotationsPart(value) {
520
+ if (messageAnnotations == null) {
521
+ messageAnnotations = [...value];
522
+ } else {
523
+ messageAnnotations.push(...value);
524
+ }
561
525
 
562
- // Start processing the response in the background
563
- const response = await this.processStreamResponse(processedParams, writable);
526
+ execUpdate();
527
+ },
528
+ onFinishStepPart(value) {
529
+ step += 1;
564
530
 
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
- };
531
+ // reset the current text and reasoning parts
532
+ currentTextPart = value.isContinued ? currentTextPart : undefined;
533
+ currentReasoningPart = undefined;
534
+ currentReasoningTextDetail = undefined;
535
+ },
536
+ onStartStepPart(value) {
537
+ // keep message id stable when we are updating an existing message:
538
+ if (!replaceLastMessage) {
539
+ message.id = value.messageId;
540
+ }
573
541
 
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,
542
+ // add a step boundary part to the message
543
+ message.parts.push({ type: 'step-start' });
544
+ execUpdate();
545
+ },
546
+ onFinishMessagePart(value) {
547
+ finishReason = value.finishReason;
548
+ if (value.usage != null) {
549
+ // usage = calculateLanguageModelUsage(value.usage);
550
+ usage = value.usage;
551
+ }
552
+ },
553
+ onErrorPart(error) {
554
+ throw new Error(error);
555
+ },
579
556
  });
580
- };
581
557
 
582
- return streamResponse;
558
+ onFinish?.({ message, finishReason, usage });
559
+ }
583
560
  }
584
561
 
585
562
  /**
@@ -599,6 +576,7 @@ export class Agent extends BaseResource {
599
576
  }
600
577
 
601
578
  try {
579
+ const streamProtocol = processedParams.output ? 'text' : 'data';
602
580
  let toolCalls: ToolInvocation[] = [];
603
581
  let finishReasonToolCalls = false;
604
582
  let messages: UIMessage[] = [];
@@ -707,11 +685,14 @@ export class Agent extends BaseResource {
707
685
  }
708
686
  } else {
709
687
  setTimeout(() => {
710
- writable.close();
688
+ if (!writable.locked) {
689
+ writable.close();
690
+ }
711
691
  }, 0);
712
692
  }
713
693
  },
714
694
  lastMessage: undefined,
695
+ streamProtocol,
715
696
  });
716
697
  } catch (error) {
717
698
  console.error('Error processing stream response:', error);
@@ -719,6 +700,61 @@ export class Agent extends BaseResource {
719
700
  return response;
720
701
  }
721
702
 
703
+ /**
704
+ * Streams a response from the agent
705
+ * @param params - Stream parameters including prompt
706
+ * @returns Promise containing the enhanced Response object with processDataStream and processTextStream methods
707
+ */
708
+ async stream<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
709
+ params: StreamParams<T>,
710
+ ): Promise<
711
+ Response & {
712
+ processDataStream: (options?: Omit<Parameters<typeof processDataStream>[0], 'stream'>) => Promise<void>;
713
+ processTextStream: (options?: Omit<Parameters<typeof processTextStream>[0], 'stream'>) => Promise<void>;
714
+ }
715
+ > {
716
+ const processedParams = {
717
+ ...params,
718
+ output: params.output ? zodToJsonSchema(params.output) : undefined,
719
+ experimental_output: params.experimental_output ? zodToJsonSchema(params.experimental_output) : undefined,
720
+ runtimeContext: parseClientRuntimeContext(params.runtimeContext),
721
+ clientTools: processClientTools(params.clientTools),
722
+ };
723
+
724
+ // Create a readable stream that will handle the response processing
725
+ const { readable, writable } = new TransformStream<Uint8Array, Uint8Array>();
726
+ // Start processing the response in the background
727
+ const response = await this.processStreamResponse(processedParams, writable);
728
+
729
+ // Create a new response with the readable stream
730
+ const streamResponse = new Response(readable, {
731
+ status: response.status,
732
+ statusText: response.statusText,
733
+ headers: response.headers,
734
+ }) as Response & {
735
+ processDataStream: (options?: Omit<Parameters<typeof processDataStream>[0], 'stream'>) => Promise<void>;
736
+ processTextStream: (options?: Omit<Parameters<typeof processTextStream>[0], 'stream'>) => Promise<void>;
737
+ };
738
+
739
+ // Add the processDataStream method to the response
740
+ streamResponse.processDataStream = async (options = {}) => {
741
+ await processDataStream({
742
+ stream: streamResponse.body as ReadableStream<Uint8Array>,
743
+ ...options,
744
+ });
745
+ };
746
+
747
+ //Add the processTextStream method to the response
748
+ streamResponse.processTextStream = async options => {
749
+ await processTextStream({
750
+ stream: streamResponse.body as ReadableStream<Uint8Array>,
751
+ onTextPart: options?.onTextPart ?? (() => {}),
752
+ });
753
+ };
754
+
755
+ return streamResponse;
756
+ }
757
+
722
758
  /**
723
759
  * Gets details about a specific tool available to the agent
724
760
  * @param toolId - ID of the tool to retrieve