@mastra/client-js 0.10.6-alpha.1 → 0.10.6-alpha.3
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.
- package/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +24 -0
- package/dist/index.cjs +606 -13
- package/dist/index.d.cts +166 -4
- package/dist/index.d.ts +166 -4
- package/dist/index.js +607 -14
- package/package.json +4 -4
- package/src/client.ts +71 -1
- package/src/example.ts +4 -1
- package/src/resources/agent.ts +533 -12
- package/src/resources/base.ts +1 -0
- package/src/resources/network-memory-thread.ts +63 -0
- package/src/resources/network.ts +2 -3
- package/src/resources/vNextNetwork.ts +147 -0
- package/src/resources/workflow.ts +2 -2
- package/src/types.ts +61 -0
- package/src/utils/process-client-tools.ts +3 -2
package/src/resources/agent.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
parsePartialJson,
|
|
3
|
+
processDataStream,
|
|
4
|
+
type JSONValue,
|
|
5
|
+
type ReasoningUIPart,
|
|
6
|
+
type TextUIPart,
|
|
7
|
+
type ToolInvocation,
|
|
8
|
+
type ToolInvocationUIPart,
|
|
9
|
+
type UIMessage,
|
|
10
|
+
type UseChatOptions,
|
|
11
|
+
} from '@ai-sdk/ui-utils';
|
|
12
|
+
import { Tool, type CoreMessage, type GenerateReturn } from '@mastra/core';
|
|
3
13
|
import type { JSONSchema7 } from 'json-schema';
|
|
4
14
|
import { ZodSchema } from 'zod';
|
|
5
15
|
import { zodToJsonSchema } from '../utils/zod-to-json-schema';
|
|
@@ -17,6 +27,7 @@ import type {
|
|
|
17
27
|
import { BaseResource } from './base';
|
|
18
28
|
import type { RuntimeContext } from '@mastra/core/runtime-context';
|
|
19
29
|
import { parseClientRuntimeContext } from '../utils';
|
|
30
|
+
import { MessageList } from '@mastra/core/agent';
|
|
20
31
|
|
|
21
32
|
export class AgentVoice extends BaseResource {
|
|
22
33
|
constructor(
|
|
@@ -105,16 +116,16 @@ export class Agent extends BaseResource {
|
|
|
105
116
|
* @param params - Generation parameters including prompt
|
|
106
117
|
* @returns Promise containing the generated response
|
|
107
118
|
*/
|
|
108
|
-
generate<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
|
|
119
|
+
async generate<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
|
|
109
120
|
params: GenerateParams<T> & { output?: never; experimental_output?: never },
|
|
110
121
|
): Promise<GenerateReturn<T>>;
|
|
111
|
-
generate<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
|
|
122
|
+
async generate<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
|
|
112
123
|
params: GenerateParams<T> & { output: T; experimental_output?: never },
|
|
113
124
|
): Promise<GenerateReturn<T>>;
|
|
114
|
-
generate<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
|
|
125
|
+
async generate<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
|
|
115
126
|
params: GenerateParams<T> & { output?: never; experimental_output: T },
|
|
116
127
|
): Promise<GenerateReturn<T>>;
|
|
117
|
-
generate<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
|
|
128
|
+
async generate<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
|
|
118
129
|
params: GenerateParams<T>,
|
|
119
130
|
): Promise<GenerateReturn<T>> {
|
|
120
131
|
const processedParams = {
|
|
@@ -125,10 +136,404 @@ export class Agent extends BaseResource {
|
|
|
125
136
|
clientTools: processClientTools(params.clientTools),
|
|
126
137
|
};
|
|
127
138
|
|
|
128
|
-
|
|
139
|
+
const { runId, resourceId, threadId, runtimeContext } = processedParams as GenerateParams;
|
|
140
|
+
|
|
141
|
+
const response: GenerateReturn<T> = await this.request(`/api/agents/${this.agentId}/generate`, {
|
|
129
142
|
method: 'POST',
|
|
130
143
|
body: processedParams,
|
|
131
144
|
});
|
|
145
|
+
|
|
146
|
+
if (response.finishReason === 'tool-calls') {
|
|
147
|
+
for (const toolCall of (
|
|
148
|
+
response as unknown as {
|
|
149
|
+
toolCalls: { toolName: string; args: any; toolCallId: string }[];
|
|
150
|
+
messages: CoreMessage[];
|
|
151
|
+
}
|
|
152
|
+
).toolCalls) {
|
|
153
|
+
const clientTool = params.clientTools?.[toolCall.toolName] as Tool;
|
|
154
|
+
|
|
155
|
+
if (clientTool && clientTool.execute) {
|
|
156
|
+
const result = await clientTool.execute(
|
|
157
|
+
{ context: toolCall?.args, runId, resourceId, threadId, runtimeContext: runtimeContext as RuntimeContext },
|
|
158
|
+
{
|
|
159
|
+
messages: (response as unknown as { messages: CoreMessage[] }).messages,
|
|
160
|
+
toolCallId: toolCall?.toolCallId,
|
|
161
|
+
},
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const updatedMessages = [
|
|
165
|
+
{
|
|
166
|
+
role: 'user',
|
|
167
|
+
content: params.messages,
|
|
168
|
+
},
|
|
169
|
+
...(response.response as unknown as { messages: CoreMessage[] }).messages,
|
|
170
|
+
{
|
|
171
|
+
role: 'tool',
|
|
172
|
+
content: [
|
|
173
|
+
{
|
|
174
|
+
type: 'tool-result',
|
|
175
|
+
toolCallId: toolCall.toolCallId,
|
|
176
|
+
toolName: toolCall.toolName,
|
|
177
|
+
result,
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
},
|
|
181
|
+
];
|
|
182
|
+
// @ts-ignore
|
|
183
|
+
return this.generate({
|
|
184
|
+
...params,
|
|
185
|
+
messages: updatedMessages,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return response;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private async processChatResponse({
|
|
195
|
+
stream,
|
|
196
|
+
update,
|
|
197
|
+
onToolCall,
|
|
198
|
+
onFinish,
|
|
199
|
+
getCurrentDate = () => new Date(),
|
|
200
|
+
lastMessage,
|
|
201
|
+
}: {
|
|
202
|
+
stream: ReadableStream<Uint8Array>;
|
|
203
|
+
update: (options: { message: UIMessage; data: JSONValue[] | undefined; replaceLastMessage: boolean }) => void;
|
|
204
|
+
onToolCall?: UseChatOptions['onToolCall'];
|
|
205
|
+
onFinish?: (options: { message: UIMessage | undefined; finishReason: string; usage: string }) => void;
|
|
206
|
+
generateId?: () => string;
|
|
207
|
+
getCurrentDate?: () => Date;
|
|
208
|
+
lastMessage: UIMessage | undefined;
|
|
209
|
+
}) {
|
|
210
|
+
const replaceLastMessage = lastMessage?.role === 'assistant';
|
|
211
|
+
let step = replaceLastMessage
|
|
212
|
+
? 1 +
|
|
213
|
+
// find max step in existing tool invocations:
|
|
214
|
+
(lastMessage.toolInvocations?.reduce((max, toolInvocation) => {
|
|
215
|
+
return Math.max(max, toolInvocation.step ?? 0);
|
|
216
|
+
}, 0) ?? 0)
|
|
217
|
+
: 0;
|
|
218
|
+
|
|
219
|
+
const message: UIMessage = replaceLastMessage
|
|
220
|
+
? structuredClone(lastMessage)
|
|
221
|
+
: {
|
|
222
|
+
id: crypto.randomUUID(),
|
|
223
|
+
createdAt: getCurrentDate(),
|
|
224
|
+
role: 'assistant',
|
|
225
|
+
content: '',
|
|
226
|
+
parts: [],
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
let currentTextPart: TextUIPart | undefined = undefined;
|
|
230
|
+
let currentReasoningPart: ReasoningUIPart | undefined = undefined;
|
|
231
|
+
let currentReasoningTextDetail: { type: 'text'; text: string; signature?: string } | undefined = undefined;
|
|
232
|
+
|
|
233
|
+
function updateToolInvocationPart(toolCallId: string, invocation: ToolInvocation) {
|
|
234
|
+
const part = message.parts.find(
|
|
235
|
+
part => part.type === 'tool-invocation' && part.toolInvocation.toolCallId === toolCallId,
|
|
236
|
+
) as ToolInvocationUIPart | undefined;
|
|
237
|
+
|
|
238
|
+
if (part != null) {
|
|
239
|
+
part.toolInvocation = invocation;
|
|
240
|
+
} else {
|
|
241
|
+
message.parts.push({
|
|
242
|
+
type: 'tool-invocation',
|
|
243
|
+
toolInvocation: invocation,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const data: JSONValue[] = [];
|
|
249
|
+
|
|
250
|
+
// keep list of current message annotations for message
|
|
251
|
+
let messageAnnotations: JSONValue[] | undefined = replaceLastMessage ? lastMessage?.annotations : undefined;
|
|
252
|
+
|
|
253
|
+
// keep track of partial tool calls
|
|
254
|
+
const partialToolCalls: Record<string, { text: string; step: number; index: number; toolName: string }> = {};
|
|
255
|
+
|
|
256
|
+
let usage: any = {
|
|
257
|
+
completionTokens: NaN,
|
|
258
|
+
promptTokens: NaN,
|
|
259
|
+
totalTokens: NaN,
|
|
260
|
+
};
|
|
261
|
+
let finishReason: string = 'unknown';
|
|
262
|
+
|
|
263
|
+
function execUpdate() {
|
|
264
|
+
// make a copy of the data array to ensure UI is updated (SWR)
|
|
265
|
+
const copiedData = [...data];
|
|
266
|
+
|
|
267
|
+
// keeps the currentMessage up to date with the latest annotations,
|
|
268
|
+
// even if annotations preceded the message creation
|
|
269
|
+
if (messageAnnotations?.length) {
|
|
270
|
+
message.annotations = messageAnnotations;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const copiedMessage = {
|
|
274
|
+
// deep copy the message to ensure that deep changes (msg attachments) are updated
|
|
275
|
+
// with SolidJS. SolidJS uses referential integration of sub-objects to detect changes.
|
|
276
|
+
...structuredClone(message),
|
|
277
|
+
// add a revision id to ensure that the message is updated with SWR. SWR uses a
|
|
278
|
+
// hashing approach by default to detect changes, but it only works for shallow
|
|
279
|
+
// changes. This is why we need to add a revision id to ensure that the message
|
|
280
|
+
// is updated with SWR (without it, the changes get stuck in SWR and are not
|
|
281
|
+
// forwarded to rendering):
|
|
282
|
+
revisionId: crypto.randomUUID(),
|
|
283
|
+
} as UIMessage;
|
|
284
|
+
|
|
285
|
+
update({
|
|
286
|
+
message: copiedMessage,
|
|
287
|
+
data: copiedData,
|
|
288
|
+
replaceLastMessage,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
await processDataStream({
|
|
293
|
+
stream,
|
|
294
|
+
onTextPart(value) {
|
|
295
|
+
if (currentTextPart == null) {
|
|
296
|
+
currentTextPart = {
|
|
297
|
+
type: 'text',
|
|
298
|
+
text: value,
|
|
299
|
+
};
|
|
300
|
+
message.parts.push(currentTextPart);
|
|
301
|
+
} else {
|
|
302
|
+
currentTextPart.text += value;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
message.content += value;
|
|
306
|
+
execUpdate();
|
|
307
|
+
},
|
|
308
|
+
onReasoningPart(value) {
|
|
309
|
+
if (currentReasoningTextDetail == null) {
|
|
310
|
+
currentReasoningTextDetail = { type: 'text', text: value };
|
|
311
|
+
if (currentReasoningPart != null) {
|
|
312
|
+
currentReasoningPart.details.push(currentReasoningTextDetail);
|
|
313
|
+
}
|
|
314
|
+
} else {
|
|
315
|
+
currentReasoningTextDetail.text += value;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (currentReasoningPart == null) {
|
|
319
|
+
currentReasoningPart = {
|
|
320
|
+
type: 'reasoning',
|
|
321
|
+
reasoning: value,
|
|
322
|
+
details: [currentReasoningTextDetail],
|
|
323
|
+
};
|
|
324
|
+
message.parts.push(currentReasoningPart);
|
|
325
|
+
} else {
|
|
326
|
+
currentReasoningPart.reasoning += value;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
message.reasoning = (message.reasoning ?? '') + value;
|
|
330
|
+
|
|
331
|
+
execUpdate();
|
|
332
|
+
},
|
|
333
|
+
onReasoningSignaturePart(value) {
|
|
334
|
+
if (currentReasoningTextDetail != null) {
|
|
335
|
+
currentReasoningTextDetail.signature = value.signature;
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
onRedactedReasoningPart(value) {
|
|
339
|
+
if (currentReasoningPart == null) {
|
|
340
|
+
currentReasoningPart = {
|
|
341
|
+
type: 'reasoning',
|
|
342
|
+
reasoning: '',
|
|
343
|
+
details: [],
|
|
344
|
+
};
|
|
345
|
+
message.parts.push(currentReasoningPart);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
currentReasoningPart.details.push({
|
|
349
|
+
type: 'redacted',
|
|
350
|
+
data: value.data,
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
currentReasoningTextDetail = undefined;
|
|
354
|
+
|
|
355
|
+
execUpdate();
|
|
356
|
+
},
|
|
357
|
+
onFilePart(value) {
|
|
358
|
+
message.parts.push({
|
|
359
|
+
type: 'file',
|
|
360
|
+
mimeType: value.mimeType,
|
|
361
|
+
data: value.data,
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
execUpdate();
|
|
365
|
+
},
|
|
366
|
+
onSourcePart(value) {
|
|
367
|
+
message.parts.push({
|
|
368
|
+
type: 'source',
|
|
369
|
+
source: value,
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
execUpdate();
|
|
373
|
+
},
|
|
374
|
+
onToolCallStreamingStartPart(value) {
|
|
375
|
+
if (message.toolInvocations == null) {
|
|
376
|
+
message.toolInvocations = [];
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// add the partial tool call to the map
|
|
380
|
+
partialToolCalls[value.toolCallId] = {
|
|
381
|
+
text: '',
|
|
382
|
+
step,
|
|
383
|
+
toolName: value.toolName,
|
|
384
|
+
index: message.toolInvocations.length,
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
const invocation = {
|
|
388
|
+
state: 'partial-call',
|
|
389
|
+
step,
|
|
390
|
+
toolCallId: value.toolCallId,
|
|
391
|
+
toolName: value.toolName,
|
|
392
|
+
args: undefined,
|
|
393
|
+
} as const;
|
|
394
|
+
|
|
395
|
+
message.toolInvocations.push(invocation);
|
|
396
|
+
|
|
397
|
+
updateToolInvocationPart(value.toolCallId, invocation);
|
|
398
|
+
|
|
399
|
+
execUpdate();
|
|
400
|
+
},
|
|
401
|
+
onToolCallDeltaPart(value) {
|
|
402
|
+
const partialToolCall = partialToolCalls[value.toolCallId];
|
|
403
|
+
|
|
404
|
+
partialToolCall!.text += value.argsTextDelta;
|
|
405
|
+
|
|
406
|
+
const { value: partialArgs } = parsePartialJson(partialToolCall!.text);
|
|
407
|
+
|
|
408
|
+
const invocation = {
|
|
409
|
+
state: 'partial-call',
|
|
410
|
+
step: partialToolCall!.step,
|
|
411
|
+
toolCallId: value.toolCallId,
|
|
412
|
+
toolName: partialToolCall!.toolName,
|
|
413
|
+
args: partialArgs,
|
|
414
|
+
} as const;
|
|
415
|
+
|
|
416
|
+
message.toolInvocations![partialToolCall!.index] = invocation;
|
|
417
|
+
|
|
418
|
+
updateToolInvocationPart(value.toolCallId, invocation);
|
|
419
|
+
|
|
420
|
+
execUpdate();
|
|
421
|
+
},
|
|
422
|
+
async onToolCallPart(value) {
|
|
423
|
+
const invocation = {
|
|
424
|
+
state: 'call',
|
|
425
|
+
step,
|
|
426
|
+
...value,
|
|
427
|
+
} as const;
|
|
428
|
+
|
|
429
|
+
if (partialToolCalls[value.toolCallId] != null) {
|
|
430
|
+
// change the partial tool call to a full tool call
|
|
431
|
+
message.toolInvocations![partialToolCalls[value.toolCallId]!.index] = invocation;
|
|
432
|
+
} else {
|
|
433
|
+
if (message.toolInvocations == null) {
|
|
434
|
+
message.toolInvocations = [];
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
message.toolInvocations.push(invocation);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
updateToolInvocationPart(value.toolCallId, invocation);
|
|
441
|
+
|
|
442
|
+
execUpdate();
|
|
443
|
+
|
|
444
|
+
// invoke the onToolCall callback if it exists. This is blocking.
|
|
445
|
+
// In the future we should make this non-blocking, which
|
|
446
|
+
// requires additional state management for error handling etc.
|
|
447
|
+
if (onToolCall) {
|
|
448
|
+
const result = await onToolCall({ toolCall: value });
|
|
449
|
+
if (result != null) {
|
|
450
|
+
const invocation = {
|
|
451
|
+
state: 'result',
|
|
452
|
+
step,
|
|
453
|
+
...value,
|
|
454
|
+
result,
|
|
455
|
+
} as const;
|
|
456
|
+
|
|
457
|
+
// store the result in the tool invocation
|
|
458
|
+
message.toolInvocations![message.toolInvocations!.length - 1] = invocation;
|
|
459
|
+
|
|
460
|
+
updateToolInvocationPart(value.toolCallId, invocation);
|
|
461
|
+
|
|
462
|
+
execUpdate();
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
onToolResultPart(value) {
|
|
467
|
+
const toolInvocations = message.toolInvocations;
|
|
468
|
+
|
|
469
|
+
if (toolInvocations == null) {
|
|
470
|
+
throw new Error('tool_result must be preceded by a tool_call');
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// find if there is any tool invocation with the same toolCallId
|
|
474
|
+
// and replace it with the result
|
|
475
|
+
const toolInvocationIndex = toolInvocations.findIndex(invocation => invocation.toolCallId === value.toolCallId);
|
|
476
|
+
|
|
477
|
+
if (toolInvocationIndex === -1) {
|
|
478
|
+
throw new Error('tool_result must be preceded by a tool_call with the same toolCallId');
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const invocation = {
|
|
482
|
+
...toolInvocations[toolInvocationIndex],
|
|
483
|
+
state: 'result' as const,
|
|
484
|
+
...value,
|
|
485
|
+
} as const;
|
|
486
|
+
|
|
487
|
+
toolInvocations[toolInvocationIndex] = invocation as ToolInvocation;
|
|
488
|
+
|
|
489
|
+
updateToolInvocationPart(value.toolCallId, invocation as ToolInvocation);
|
|
490
|
+
|
|
491
|
+
execUpdate();
|
|
492
|
+
},
|
|
493
|
+
onDataPart(value) {
|
|
494
|
+
data.push(...value);
|
|
495
|
+
execUpdate();
|
|
496
|
+
},
|
|
497
|
+
onMessageAnnotationsPart(value) {
|
|
498
|
+
if (messageAnnotations == null) {
|
|
499
|
+
messageAnnotations = [...value];
|
|
500
|
+
} else {
|
|
501
|
+
messageAnnotations.push(...value);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
execUpdate();
|
|
505
|
+
},
|
|
506
|
+
onFinishStepPart(value) {
|
|
507
|
+
step += 1;
|
|
508
|
+
|
|
509
|
+
// reset the current text and reasoning parts
|
|
510
|
+
currentTextPart = value.isContinued ? currentTextPart : undefined;
|
|
511
|
+
currentReasoningPart = undefined;
|
|
512
|
+
currentReasoningTextDetail = undefined;
|
|
513
|
+
},
|
|
514
|
+
onStartStepPart(value) {
|
|
515
|
+
// keep message id stable when we are updating an existing message:
|
|
516
|
+
if (!replaceLastMessage) {
|
|
517
|
+
message.id = value.messageId;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// add a step boundary part to the message
|
|
521
|
+
message.parts.push({ type: 'step-start' });
|
|
522
|
+
execUpdate();
|
|
523
|
+
},
|
|
524
|
+
onFinishMessagePart(value) {
|
|
525
|
+
finishReason = value.finishReason;
|
|
526
|
+
if (value.usage != null) {
|
|
527
|
+
// usage = calculateLanguageModelUsage(value.usage);
|
|
528
|
+
usage = value.usage;
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
onErrorPart(error) {
|
|
532
|
+
throw new Error(error);
|
|
533
|
+
},
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
onFinish?.({ message, finishReason, usage });
|
|
132
537
|
}
|
|
133
538
|
|
|
134
539
|
/**
|
|
@@ -151,6 +556,36 @@ export class Agent extends BaseResource {
|
|
|
151
556
|
clientTools: processClientTools(params.clientTools),
|
|
152
557
|
};
|
|
153
558
|
|
|
559
|
+
// Create a readable stream that will handle the response processing
|
|
560
|
+
const { readable, writable } = new TransformStream<Uint8Array, Uint8Array>();
|
|
561
|
+
|
|
562
|
+
// Start processing the response in the background
|
|
563
|
+
const response = await this.processStreamResponse(processedParams, writable);
|
|
564
|
+
|
|
565
|
+
// Create a new response with the readable stream
|
|
566
|
+
const streamResponse = new Response(readable, {
|
|
567
|
+
status: response.status,
|
|
568
|
+
statusText: response.statusText,
|
|
569
|
+
headers: response.headers,
|
|
570
|
+
}) as Response & {
|
|
571
|
+
processDataStream: (options?: Omit<Parameters<typeof processDataStream>[0], 'stream'>) => Promise<void>;
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
// Add the processDataStream method to the response
|
|
575
|
+
streamResponse.processDataStream = async (options = {}) => {
|
|
576
|
+
await processDataStream({
|
|
577
|
+
stream: readable as ReadableStream<Uint8Array>,
|
|
578
|
+
...options,
|
|
579
|
+
});
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
return streamResponse;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Processes the stream response and handles tool calls
|
|
587
|
+
*/
|
|
588
|
+
private async processStreamResponse(processedParams: any, writable: WritableStream<Uint8Array>) {
|
|
154
589
|
const response: Response & {
|
|
155
590
|
processDataStream: (options?: Omit<Parameters<typeof processDataStream>[0], 'stream'>) => Promise<void>;
|
|
156
591
|
} = await this.request(`/api/agents/${this.agentId}/stream`, {
|
|
@@ -163,13 +598,99 @@ export class Agent extends BaseResource {
|
|
|
163
598
|
throw new Error('No response body');
|
|
164
599
|
}
|
|
165
600
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
601
|
+
try {
|
|
602
|
+
let toolCalls: ToolInvocation[] = [];
|
|
603
|
+
let finishReasonToolCalls = false;
|
|
604
|
+
let messages: UIMessage[] = [];
|
|
605
|
+
let hasProcessedToolCalls = false;
|
|
606
|
+
|
|
607
|
+
// If no tool calls or they've been processed, pipe the original response
|
|
608
|
+
response.clone().body!.pipeTo(writable, {
|
|
609
|
+
preventClose: true,
|
|
170
610
|
});
|
|
171
|
-
};
|
|
172
611
|
|
|
612
|
+
await this.processChatResponse({
|
|
613
|
+
stream: response.clone().body!,
|
|
614
|
+
update: ({ message }) => {
|
|
615
|
+
messages.push(message);
|
|
616
|
+
},
|
|
617
|
+
onFinish: ({ finishReason, message }) => {
|
|
618
|
+
if (finishReason === 'tool-calls') {
|
|
619
|
+
finishReasonToolCalls = true;
|
|
620
|
+
const toolCall = [...(message?.parts ?? [])]
|
|
621
|
+
.reverse()
|
|
622
|
+
.find(part => part.type === 'tool-invocation')?.toolInvocation;
|
|
623
|
+
if (toolCall) {
|
|
624
|
+
toolCalls.push(toolCall);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
},
|
|
628
|
+
lastMessage: undefined,
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
// Handle tool calls if needed
|
|
632
|
+
if (finishReasonToolCalls && !hasProcessedToolCalls) {
|
|
633
|
+
hasProcessedToolCalls = true;
|
|
634
|
+
|
|
635
|
+
for (const toolCall of toolCalls) {
|
|
636
|
+
const clientTool = processedParams.clientTools?.[toolCall.toolName] as Tool;
|
|
637
|
+
if (clientTool && clientTool.execute) {
|
|
638
|
+
const result = await clientTool.execute(
|
|
639
|
+
{
|
|
640
|
+
context: toolCall?.args,
|
|
641
|
+
runId: processedParams.runId,
|
|
642
|
+
resourceId: processedParams.resourceId,
|
|
643
|
+
threadId: processedParams.threadId,
|
|
644
|
+
runtimeContext: processedParams.runtimeContext as RuntimeContext,
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
messages: (response as unknown as { messages: CoreMessage[] }).messages,
|
|
648
|
+
toolCallId: toolCall?.toolCallId,
|
|
649
|
+
},
|
|
650
|
+
);
|
|
651
|
+
|
|
652
|
+
const lastMessage: UIMessage = JSON.parse(JSON.stringify(messages[messages.length - 1]));
|
|
653
|
+
|
|
654
|
+
const toolInvocationPart = lastMessage?.parts?.find(
|
|
655
|
+
part => part.type === 'tool-invocation' && part.toolInvocation?.toolCallId === toolCall.toolCallId,
|
|
656
|
+
) as ToolInvocationUIPart | undefined;
|
|
657
|
+
|
|
658
|
+
if (toolInvocationPart) {
|
|
659
|
+
toolInvocationPart.toolInvocation = {
|
|
660
|
+
...toolInvocationPart.toolInvocation,
|
|
661
|
+
state: 'result',
|
|
662
|
+
result,
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const toolInvocation = lastMessage?.toolInvocations?.find(
|
|
667
|
+
toolInvocation => toolInvocation.toolCallId === toolCall.toolCallId,
|
|
668
|
+
) as ToolInvocation | undefined;
|
|
669
|
+
|
|
670
|
+
if (toolInvocation) {
|
|
671
|
+
toolInvocation.state = 'result';
|
|
672
|
+
// @ts-ignore
|
|
673
|
+
toolInvocation.result = result;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Convert messages to the correct format for the recursive call
|
|
677
|
+
const originalMessages = processedParams.messages;
|
|
678
|
+
const messageArray = Array.isArray(originalMessages) ? originalMessages : [originalMessages];
|
|
679
|
+
|
|
680
|
+
// Recursively call stream with updated messages
|
|
681
|
+
this.processStreamResponse(
|
|
682
|
+
{
|
|
683
|
+
...processedParams,
|
|
684
|
+
messages: [...messageArray, ...messages, lastMessage],
|
|
685
|
+
},
|
|
686
|
+
writable,
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
} catch (error) {
|
|
692
|
+
console.error('Error processing stream response:', error);
|
|
693
|
+
}
|
|
173
694
|
return response;
|
|
174
695
|
}
|
|
175
696
|
|
package/src/resources/base.ts
CHANGED
|
@@ -24,6 +24,7 @@ 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
28
|
...headers,
|
|
28
29
|
...options.headers,
|
|
29
30
|
// TODO: Bring this back once we figure out what we/users need to do to make this work with cross-origin requests
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { StorageThreadType } from '@mastra/core';
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
GetMemoryThreadMessagesResponse,
|
|
5
|
+
ClientOptions,
|
|
6
|
+
UpdateMemoryThreadParams,
|
|
7
|
+
GetMemoryThreadMessagesParams,
|
|
8
|
+
} from '../types';
|
|
9
|
+
|
|
10
|
+
import { BaseResource } from './base';
|
|
11
|
+
|
|
12
|
+
export class NetworkMemoryThread extends BaseResource {
|
|
13
|
+
constructor(
|
|
14
|
+
options: ClientOptions,
|
|
15
|
+
private threadId: string,
|
|
16
|
+
private networkId: string,
|
|
17
|
+
) {
|
|
18
|
+
super(options);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Retrieves the memory thread details
|
|
23
|
+
* @returns Promise containing thread details including title and metadata
|
|
24
|
+
*/
|
|
25
|
+
get(): Promise<StorageThreadType> {
|
|
26
|
+
return this.request(`/api/memory/network/threads/${this.threadId}?networkId=${this.networkId}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Updates the memory thread properties
|
|
31
|
+
* @param params - Update parameters including title and metadata
|
|
32
|
+
* @returns Promise containing updated thread details
|
|
33
|
+
*/
|
|
34
|
+
update(params: UpdateMemoryThreadParams): Promise<StorageThreadType> {
|
|
35
|
+
return this.request(`/api/memory/network/threads/${this.threadId}?networkId=${this.networkId}`, {
|
|
36
|
+
method: 'PATCH',
|
|
37
|
+
body: params,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Deletes the memory thread
|
|
43
|
+
* @returns Promise containing deletion result
|
|
44
|
+
*/
|
|
45
|
+
delete(): Promise<{ result: string }> {
|
|
46
|
+
return this.request(`/api/memory/network/threads/${this.threadId}?networkId=${this.networkId}`, {
|
|
47
|
+
method: 'DELETE',
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Retrieves messages associated with the thread
|
|
53
|
+
* @param params - Optional parameters including limit for number of messages to retrieve
|
|
54
|
+
* @returns Promise containing thread messages and UI messages
|
|
55
|
+
*/
|
|
56
|
+
getMessages(params?: GetMemoryThreadMessagesParams): Promise<GetMemoryThreadMessagesResponse> {
|
|
57
|
+
const query = new URLSearchParams({
|
|
58
|
+
networkId: this.networkId,
|
|
59
|
+
...(params?.limit ? { limit: params.limit.toString() } : {}),
|
|
60
|
+
});
|
|
61
|
+
return this.request(`/api/memory/network/threads/${this.threadId}/messages?${query.toString()}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/resources/network.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { processDataStream } from '@ai-sdk/ui-utils';
|
|
2
2
|
import type { GenerateReturn } from '@mastra/core';
|
|
3
3
|
import type { JSONSchema7 } from 'json-schema';
|
|
4
|
-
import { ZodSchema } from 'zod';
|
|
5
|
-
import { zodToJsonSchema } from '../utils/zod-to-json-schema';
|
|
6
|
-
|
|
4
|
+
import type { ZodSchema } from 'zod';
|
|
7
5
|
import type { GenerateParams, ClientOptions, StreamParams, GetNetworkResponse } from '../types';
|
|
6
|
+
import { zodToJsonSchema } from '../utils/zod-to-json-schema';
|
|
8
7
|
|
|
9
8
|
import { BaseResource } from './base';
|
|
10
9
|
|