@mastra/client-js 0.0.0-tsconfig-compile-20250703214351 → 0.0.0-update-scorers-api-20250801170445

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 (76) hide show
  1. package/CHANGELOG.md +401 -2
  2. package/LICENSE.md +11 -42
  3. package/README.md +1 -0
  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 +265 -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 +269 -67
  11. package/dist/index.cjs.map +1 -0
  12. package/dist/index.d.ts +4 -1164
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +268 -66
  15. package/dist/index.js.map +1 -0
  16. package/dist/resources/a2a.d.ts +44 -0
  17. package/dist/resources/a2a.d.ts.map +1 -0
  18. package/dist/resources/agent.d.ts +112 -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 +12 -8
  55. package/src/adapters/agui.test.ts +145 -3
  56. package/src/client.ts +145 -2
  57. package/src/example.ts +45 -17
  58. package/src/index.test.ts +402 -6
  59. package/src/index.ts +1 -0
  60. package/src/resources/agent.ts +46 -24
  61. package/src/resources/base.ts +6 -1
  62. package/src/resources/memory-thread.test.ts +285 -0
  63. package/src/resources/memory-thread.ts +36 -0
  64. package/src/resources/network-memory-thread.test.ts +269 -0
  65. package/src/resources/network-memory-thread.ts +18 -0
  66. package/src/resources/network.ts +4 -3
  67. package/src/resources/vNextNetwork.ts +22 -5
  68. package/src/resources/workflow.ts +17 -3
  69. package/src/types.ts +90 -10
  70. package/src/utils/process-client-tools.ts +1 -1
  71. package/src/v2-messages.test.ts +180 -0
  72. package/tsconfig.build.json +9 -0
  73. package/tsconfig.json +1 -1
  74. package/tsup.config.ts +22 -0
  75. package/.turbo/turbo-build.log +0 -19
  76. package/dist/index.d.cts +0 -1164
package/src/index.test.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { describe, expect, beforeEach, it, vi } from 'vitest';
2
2
  import { MastraClient } from './client';
3
- import type { McpServerListResponse, ServerDetailInfo } from './types';
3
+ import type { McpServerListResponse } from './types';
4
+ import type { ServerDetailInfo } from '@mastra/core/mcp';
5
+ import { ScoringEntityType, ScoringSource } from '@mastra/core/scores';
4
6
 
5
7
  // Mock fetch globally
6
8
  global.fetch = vi.fn();
@@ -27,7 +29,13 @@ describe('MastraClient Resources', () => {
27
29
  } else {
28
30
  responseBody = new ReadableStream({
29
31
  start(controller) {
30
- controller.enqueue(new TextEncoder().encode(JSON.stringify(data)));
32
+ if (typeof data === 'string') {
33
+ controller.enqueue(new TextEncoder().encode(data));
34
+ } else if (typeof data === 'object' && data !== null) {
35
+ controller.enqueue(new TextEncoder().encode(JSON.stringify(data)));
36
+ } else {
37
+ controller.enqueue(new TextEncoder().encode(String(data)));
38
+ }
31
39
  controller.close();
32
40
  },
33
41
  });
@@ -279,7 +287,7 @@ describe('MastraClient Resources', () => {
279
287
  });
280
288
 
281
289
  it('should stream responses', async () => {
282
- const mockChunk = { content: 'test response' };
290
+ const mockChunk = `0:"test response"\n`;
283
291
  mockFetchResponse(mockChunk, { isStream: true });
284
292
 
285
293
  const response = await agent.stream({
@@ -298,10 +306,118 @@ describe('MastraClient Resources', () => {
298
306
  if (reader) {
299
307
  const { value, done } = await reader.read();
300
308
  expect(done).toBe(false);
301
- expect(new TextDecoder().decode(value)).toBe(JSON.stringify(mockChunk));
309
+ expect(new TextDecoder().decode(value)).toBe(mockChunk);
302
310
  }
303
311
  });
304
312
 
313
+ it('should stream responses with tool calls', async () => {
314
+ const firstMockChunk = `0:"test "
315
+ 0:"response"
316
+ 9:{"toolCallId":"tool1","toolName":"testTool","args":{"arg1":"value1"}}
317
+ e:{"finishReason":"tool-calls","usage":{"promptTokens":1,"completionTokens":1},"isContinued":false}
318
+ d:{"finishReason":"tool-calls","usage":{"promptTokens":2,"completionTokens":2}}
319
+ `;
320
+
321
+ const secondMockChunk = `0:"final response"
322
+ e:{"finishReason":"stop","usage":{"promptTokens":2,"completionTokens":2},"isContinued":false}
323
+ d:{"finishReason":"stop","usage":{"promptTokens":2,"completionTokens":2}}
324
+ `;
325
+
326
+ const firstResponseBody = new ReadableStream({
327
+ start(controller) {
328
+ controller.enqueue(new TextEncoder().encode(firstMockChunk));
329
+ controller.close();
330
+ },
331
+ });
332
+
333
+ const secondResponseBody = new ReadableStream({
334
+ start(controller) {
335
+ controller.enqueue(new TextEncoder().encode(secondMockChunk));
336
+ controller.close();
337
+ },
338
+ });
339
+
340
+ (global.fetch as any)
341
+ .mockResolvedValueOnce(
342
+ new Response(firstResponseBody, {
343
+ status: 200,
344
+ headers: new Headers({ 'Content-Type': 'text/event-stream' }),
345
+ }),
346
+ )
347
+ .mockResolvedValueOnce(
348
+ new Response(secondResponseBody, {
349
+ status: 200,
350
+ headers: new Headers({ 'Content-Type': 'text/event-stream' }),
351
+ }),
352
+ );
353
+
354
+ const response = await agent.stream({
355
+ messages: [
356
+ {
357
+ role: 'user',
358
+ content: 'test',
359
+ },
360
+ ],
361
+ clientTools: {
362
+ testTool: {
363
+ id: 'testTool',
364
+ description: 'Test Tool',
365
+ inputSchema: {
366
+ type: 'object',
367
+ properties: {
368
+ arg1: { type: 'string' },
369
+ },
370
+ },
371
+ execute: async () => {
372
+ return 'test result';
373
+ },
374
+ },
375
+ },
376
+ });
377
+
378
+ expect(response.body).toBeInstanceOf(ReadableStream);
379
+ const reader = response?.body?.getReader();
380
+ expect(reader).toBeDefined();
381
+
382
+ let output = '';
383
+ if (reader) {
384
+ while (true) {
385
+ const { value, done } = await reader.read();
386
+ if (done) break;
387
+ output += new TextDecoder().decode(value);
388
+ }
389
+ }
390
+
391
+ expect(global.fetch).toHaveBeenCalledTimes(2);
392
+
393
+ const [secondUrl, secondConfig] = (global.fetch as any).mock.calls[1];
394
+ expect(secondUrl).toBe(`${clientOptions.baseUrl}/api/agents/test-agent/stream`);
395
+
396
+ const secondRequestBody = JSON.parse(secondConfig.body);
397
+ expect(secondRequestBody.messages).toHaveLength(2);
398
+ expect(secondRequestBody.messages[0].content).toBe('test');
399
+ expect(secondRequestBody.messages[1].content).toBe('test response');
400
+ expect(secondRequestBody.messages[1].parts).toEqual([
401
+ {
402
+ type: 'text',
403
+ text: 'test response',
404
+ },
405
+ {
406
+ type: 'tool-invocation',
407
+ toolInvocation: {
408
+ state: 'result',
409
+ step: 0,
410
+ toolCallId: 'tool1',
411
+ toolName: 'testTool',
412
+ args: {
413
+ arg1: 'value1',
414
+ },
415
+ result: 'test result',
416
+ },
417
+ },
418
+ ]);
419
+ });
420
+
305
421
  it('should get agent tool', async () => {
306
422
  const mockResponse = {
307
423
  id: 'tool1',
@@ -582,6 +698,48 @@ describe('MastraClient Resources', () => {
582
698
  }),
583
699
  );
584
700
  });
701
+
702
+ it('should get paginated thread messages', async () => {
703
+ const mockResponse = {
704
+ messages: [
705
+ {
706
+ id: '1',
707
+ content: 'test message',
708
+ threadId,
709
+ role: 'user',
710
+ type: 'text',
711
+ resourceId: 'test-resource',
712
+ createdAt: new Date(),
713
+ },
714
+ ],
715
+ total: 5,
716
+ page: 1,
717
+ perPage: 2,
718
+ hasMore: true,
719
+ };
720
+ mockFetchResponse(mockResponse);
721
+
722
+ const selectBy = {
723
+ pagination: {
724
+ page: 1,
725
+ perPage: 2,
726
+ },
727
+ };
728
+
729
+ const result = await memoryThread.getMessagesPaginated({
730
+ resourceId: 'test-resource',
731
+ format: 'v2',
732
+ selectBy,
733
+ });
734
+
735
+ expect(result).toEqual(mockResponse);
736
+ expect(global.fetch).toHaveBeenCalledWith(
737
+ `${clientOptions.baseUrl}/api/memory/threads/${threadId}/messages/paginated?resourceId=test-resource&format=v2&selectBy=${encodeURIComponent(JSON.stringify(selectBy))}`,
738
+ expect.objectContaining({
739
+ headers: expect.objectContaining(clientOptions.headers),
740
+ }),
741
+ );
742
+ });
585
743
  });
586
744
 
587
745
  describe('Tool Resource', () => {
@@ -662,14 +820,14 @@ describe('MastraClient Resources', () => {
662
820
  };
663
821
  mockFetchResponse(mockResponse);
664
822
 
665
- const result = await workflow.startAsync({ triggerData: { test: 'test' } });
823
+ const result = await workflow.startAsync({ inputData: { test: 'test' } });
666
824
  expect(result).toEqual(mockResponse);
667
825
  expect(global.fetch).toHaveBeenCalledWith(
668
826
  `${clientOptions.baseUrl}/api/workflows/test-workflow/start-async?`,
669
827
  expect.objectContaining({
670
828
  method: 'POST',
671
829
  headers: expect.objectContaining(clientOptions.headers),
672
- body: JSON.stringify({ test: 'test' }),
830
+ body: JSON.stringify({ inputData: { test: 'test' } }),
673
831
  }),
674
832
  );
675
833
  });
@@ -827,4 +985,242 @@ describe('MastraClient Resources', () => {
827
985
  });
828
986
  });
829
987
  });
988
+
989
+ describe('Scores Methods', () => {
990
+ describe('getScorers()', () => {
991
+ it('should fetch all available scorers', async () => {
992
+ const mockResponse = {
993
+ scorers: [
994
+ { id: 'scorer-1', name: 'Test Scorer 1', description: 'A test scorer' },
995
+ { id: 'scorer-2', name: 'Test Scorer 2', description: 'Another test scorer' },
996
+ ],
997
+ };
998
+ mockFetchResponse(mockResponse);
999
+
1000
+ const result = await client.getScorers();
1001
+ expect(result).toEqual(mockResponse);
1002
+ expect(global.fetch).toHaveBeenCalledWith(
1003
+ `${clientOptions.baseUrl}/api/scores/scorers`,
1004
+ expect.objectContaining({
1005
+ headers: expect.objectContaining(clientOptions.headers),
1006
+ }),
1007
+ );
1008
+ });
1009
+ });
1010
+
1011
+ describe('getScoresByRunId()', () => {
1012
+ it('should fetch scores by run ID without pagination', async () => {
1013
+ const mockResponse = {
1014
+ pagination: {
1015
+ total: 10,
1016
+ page: 0,
1017
+ perPage: 10,
1018
+ hasMore: false,
1019
+ },
1020
+ scores: [
1021
+ {
1022
+ id: 'score-1',
1023
+ runId: 'run-123',
1024
+ scorer: { name: 'test-scorer' },
1025
+ result: { score: 0.8 },
1026
+ input: { messages: [] },
1027
+ output: { response: 'test' },
1028
+ source: 'LIVE',
1029
+ createdAt: new Date(),
1030
+ updatedAt: new Date(),
1031
+ },
1032
+ ],
1033
+ };
1034
+
1035
+ mockFetchResponse({
1036
+ ...mockResponse,
1037
+ scores: mockResponse.scores.map(score => ({
1038
+ ...score,
1039
+ createdAt: score.createdAt.toISOString(),
1040
+ updatedAt: score.updatedAt.toISOString(),
1041
+ })),
1042
+ });
1043
+
1044
+ const result = await client.getScoresByRunId({ runId: 'run-123' });
1045
+
1046
+ expect(result).toEqual({
1047
+ ...mockResponse,
1048
+ scores: mockResponse.scores.map(score => ({
1049
+ ...score,
1050
+ createdAt: score.createdAt.toISOString(),
1051
+ updatedAt: score.updatedAt.toISOString(),
1052
+ })),
1053
+ });
1054
+
1055
+ expect(global.fetch).toHaveBeenCalledWith(
1056
+ `${clientOptions.baseUrl}/api/scores/run/run-123`,
1057
+ expect.objectContaining({
1058
+ headers: expect.objectContaining(clientOptions.headers),
1059
+ }),
1060
+ );
1061
+ });
1062
+
1063
+ it('should fetch scores by run ID with pagination', async () => {
1064
+ const mockResponse = {
1065
+ pagination: {
1066
+ total: 20,
1067
+ page: 1,
1068
+ perPage: 5,
1069
+ hasMore: true,
1070
+ },
1071
+ scores: [],
1072
+ };
1073
+ mockFetchResponse(mockResponse);
1074
+
1075
+ const result = await client.getScoresByRunId({
1076
+ runId: 'run-123',
1077
+ page: 1,
1078
+ perPage: 5,
1079
+ });
1080
+ expect(result).toEqual(mockResponse);
1081
+ expect(global.fetch).toHaveBeenCalledWith(
1082
+ `${clientOptions.baseUrl}/api/scores/run/run-123?page=1&perPage=5`,
1083
+ expect.objectContaining({
1084
+ headers: expect.objectContaining(clientOptions.headers),
1085
+ }),
1086
+ );
1087
+ });
1088
+ });
1089
+
1090
+ describe('getScoresByEntityId()', () => {
1091
+ it('should fetch scores by entity ID and type without pagination', async () => {
1092
+ const mockResponse = {
1093
+ pagination: {
1094
+ total: 5,
1095
+ page: 0,
1096
+ perPage: 10,
1097
+ hasMore: false,
1098
+ },
1099
+ scores: [
1100
+ {
1101
+ id: 'score-1',
1102
+ runId: 'run-123',
1103
+ entityId: 'agent-456',
1104
+ entityType: 'AGENT',
1105
+ scorer: { name: 'test-scorer' },
1106
+ result: { score: 0.9 },
1107
+ input: { messages: [] },
1108
+ output: { response: 'test' },
1109
+ source: 'LIVE',
1110
+ createdAt: new Date(),
1111
+ updatedAt: new Date(),
1112
+ },
1113
+ ],
1114
+ };
1115
+
1116
+ const mockResponseWithDates = mockResponse.scores.map(score => ({
1117
+ ...score,
1118
+ createdAt: score.createdAt.toISOString(),
1119
+ updatedAt: score.updatedAt.toISOString(),
1120
+ }));
1121
+
1122
+ mockFetchResponse({
1123
+ ...mockResponse,
1124
+ scores: mockResponseWithDates,
1125
+ });
1126
+
1127
+ const result = await client.getScoresByEntityId({
1128
+ entityId: 'agent-456',
1129
+ entityType: 'AGENT',
1130
+ });
1131
+
1132
+ expect(result).toEqual({
1133
+ ...mockResponse,
1134
+ scores: mockResponseWithDates,
1135
+ });
1136
+
1137
+ expect(global.fetch).toHaveBeenCalledWith(
1138
+ `${clientOptions.baseUrl}/api/scores/entity/AGENT/agent-456`,
1139
+ expect.objectContaining({
1140
+ headers: expect.objectContaining(clientOptions.headers),
1141
+ }),
1142
+ );
1143
+ });
1144
+
1145
+ it('should fetch scores by entity ID and type with pagination', async () => {
1146
+ const mockResponse = {
1147
+ pagination: {
1148
+ total: 15,
1149
+ page: 2,
1150
+ perPage: 5,
1151
+ hasMore: true,
1152
+ },
1153
+ scores: [],
1154
+ };
1155
+ mockFetchResponse(mockResponse);
1156
+
1157
+ const result = await client.getScoresByEntityId({
1158
+ entityId: 'workflow-789',
1159
+ entityType: 'WORKFLOW',
1160
+ page: 2,
1161
+ perPage: 5,
1162
+ });
1163
+ expect(result).toEqual(mockResponse);
1164
+ expect(global.fetch).toHaveBeenCalledWith(
1165
+ `${clientOptions.baseUrl}/api/scores/entity/WORKFLOW/workflow-789?page=2&perPage=5`,
1166
+ expect.objectContaining({
1167
+ body: undefined,
1168
+ headers: expect.objectContaining(clientOptions.headers),
1169
+ signal: undefined,
1170
+ }),
1171
+ );
1172
+ });
1173
+ });
1174
+
1175
+ describe('saveScore()', () => {
1176
+ it('should save a score', async () => {
1177
+ const scoreData = {
1178
+ id: 'score-1',
1179
+ scorerId: 'test-scorer',
1180
+ runId: 'run-123',
1181
+ scorer: { name: 'test-scorer' },
1182
+ score: 0.85,
1183
+ input: [],
1184
+ output: { response: 'test response' },
1185
+ source: 'LIVE' as ScoringSource,
1186
+ entityId: 'agent-456',
1187
+ entityType: 'AGENT' as ScoringEntityType,
1188
+ entity: { id: 'agent-456', name: 'test-agent' },
1189
+ createdAt: new Date(),
1190
+ updatedAt: new Date(),
1191
+ runtimeContext: {
1192
+ model: {
1193
+ name: 'test-model',
1194
+ version: '1.0.0',
1195
+ },
1196
+ },
1197
+ };
1198
+ const mockResponse = {
1199
+ score: {
1200
+ ...scoreData,
1201
+ createdAt: scoreData.createdAt.toISOString(),
1202
+ updatedAt: scoreData.updatedAt.toISOString(),
1203
+ },
1204
+ };
1205
+ mockFetchResponse(mockResponse);
1206
+
1207
+ const result = await client.saveScore({ score: scoreData });
1208
+ expect(result).toEqual({
1209
+ score: {
1210
+ ...scoreData,
1211
+ createdAt: scoreData.createdAt.toISOString(),
1212
+ updatedAt: scoreData.updatedAt.toISOString(),
1213
+ },
1214
+ });
1215
+ expect(global.fetch).toHaveBeenCalledWith(
1216
+ `${clientOptions.baseUrl}/api/scores`,
1217
+ expect.objectContaining({
1218
+ method: 'POST',
1219
+ headers: expect.objectContaining(clientOptions.headers),
1220
+ body: JSON.stringify({ score: scoreData }),
1221
+ }),
1222
+ );
1223
+ });
1224
+ });
1225
+ });
830
1226
  });
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './client';
2
2
  export * from './types';
3
+ export type { UIMessageWithMetadata } from '@mastra/core/agent';
@@ -9,11 +9,13 @@ import {
9
9
  type UIMessage,
10
10
  type UseChatOptions,
11
11
  } from '@ai-sdk/ui-utils';
12
- import { Tool, type CoreMessage, type GenerateReturn } from '@mastra/core';
12
+ import { Tool, type CoreMessage } from '@mastra/core';
13
+ import { type GenerateReturn } from '@mastra/core/llm';
13
14
  import type { JSONSchema7 } from 'json-schema';
14
15
  import { ZodSchema } from 'zod';
15
16
  import { zodToJsonSchema } from '../utils/zod-to-json-schema';
16
17
  import { processClientTools } from '../utils/process-client-tools';
18
+ import { v4 as uuid } from '@lukeed/uuid';
17
19
 
18
20
  import type {
19
21
  GenerateParams,
@@ -116,18 +118,19 @@ export class Agent extends BaseResource {
116
118
  * @param params - Generation parameters including prompt
117
119
  * @returns Promise containing the generated response
118
120
  */
119
- async generate<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
120
- params: GenerateParams<T> & { output?: never; experimental_output?: never },
121
- ): Promise<GenerateReturn<T>>;
122
- async generate<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
123
- params: GenerateParams<T> & { output: T; experimental_output?: never },
124
- ): Promise<GenerateReturn<T>>;
125
- async generate<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
126
- params: GenerateParams<T> & { output?: never; experimental_output: T },
127
- ): Promise<GenerateReturn<T>>;
128
- async generate<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
129
- params: GenerateParams<T>,
130
- ): 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>> {
131
134
  const processedParams = {
132
135
  ...params,
133
136
  output: params.output ? zodToJsonSchema(params.output) : undefined,
@@ -138,18 +141,27 @@ export class Agent extends BaseResource {
138
141
 
139
142
  const { runId, resourceId, threadId, runtimeContext } = processedParams as GenerateParams;
140
143
 
141
- const response: GenerateReturn<T> = await this.request(`/api/agents/${this.agentId}/generate`, {
142
- method: 'POST',
143
- body: processedParams,
144
- });
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
+ );
145
151
 
146
152
  if (response.finishReason === 'tool-calls') {
147
- for (const toolCall of (
153
+ const toolCalls = (
148
154
  response as unknown as {
149
155
  toolCalls: { toolName: string; args: any; toolCallId: string }[];
150
156
  messages: CoreMessage[];
151
157
  }
152
- ).toolCalls) {
158
+ ).toolCalls;
159
+
160
+ if (!toolCalls || !Array.isArray(toolCalls)) {
161
+ return response;
162
+ }
163
+
164
+ for (const toolCall of toolCalls) {
153
165
  const clientTool = params.clientTools?.[toolCall.toolName] as Tool;
154
166
 
155
167
  if (clientTool && clientTool.execute) {
@@ -219,7 +231,7 @@ export class Agent extends BaseResource {
219
231
  const message: UIMessage = replaceLastMessage
220
232
  ? structuredClone(lastMessage)
221
233
  : {
222
- id: crypto.randomUUID(),
234
+ id: uuid(),
223
235
  createdAt: getCurrentDate(),
224
236
  role: 'assistant',
225
237
  content: '',
@@ -279,7 +291,7 @@ export class Agent extends BaseResource {
279
291
  // changes. This is why we need to add a revision id to ensure that the message
280
292
  // is updated with SWR (without it, the changes get stuck in SWR and are not
281
293
  // forwarded to rendering):
282
- revisionId: crypto.randomUUID(),
294
+ revisionId: uuid(),
283
295
  } as UIMessage;
284
296
 
285
297
  update({
@@ -620,7 +632,13 @@ export class Agent extends BaseResource {
620
632
  this.processChatResponse({
621
633
  stream: streamForProcessing,
622
634
  update: ({ message }) => {
623
- messages.push(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
+ }
624
642
  },
625
643
  onFinish: async ({ finishReason, message }) => {
626
644
  if (finishReason === 'tool-calls') {
@@ -699,10 +717,12 @@ export class Agent extends BaseResource {
699
717
  this.processStreamResponse(
700
718
  {
701
719
  ...processedParams,
702
- messages: [...messageArray, ...messages, lastMessage],
720
+ messages: [...messageArray, ...messages.filter(m => m.id !== lastMessage.id), lastMessage],
703
721
  },
704
722
  writable,
705
- );
723
+ ).catch(error => {
724
+ console.error('Error processing stream response:', error);
725
+ });
706
726
  }
707
727
  }
708
728
  } else {
@@ -712,6 +732,8 @@ export class Agent extends BaseResource {
712
732
  }
713
733
  },
714
734
  lastMessage: undefined,
735
+ }).catch(error => {
736
+ console.error('Error processing stream response:', error);
715
737
  });
716
738
  } catch (error) {
717
739
  console.error('Error processing stream response:', error);
@@ -24,12 +24,17 @@ export class BaseResource {
24
24
  const response = await fetch(`${baseUrl.replace(/\/$/, '')}${path}`, {
25
25
  ...options,
26
26
  headers: {
27
- ...(options.method === 'POST' || options.method === 'PUT' ? { 'content-type': 'application/json' } : {}),
27
+ ...(options.body &&
28
+ !(options.body instanceof FormData) &&
29
+ (options.method === 'POST' || options.method === 'PUT')
30
+ ? { 'content-type': 'application/json' }
31
+ : {}),
28
32
  ...headers,
29
33
  ...options.headers,
30
34
  // TODO: Bring this back once we figure out what we/users need to do to make this work with cross-origin requests
31
35
  // 'x-mastra-client-type': 'js',
32
36
  },
37
+ signal: this.options.abortSignal,
33
38
  body:
34
39
  options.body instanceof FormData ? options.body : options.body ? JSON.stringify(options.body) : undefined,
35
40
  });