@mastra/client-js 0.10.22-alpha.2 → 0.10.22-alpha.4

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/dist/index.js CHANGED
@@ -309,6 +309,94 @@ function parseClientRuntimeContext(runtimeContext) {
309
309
  }
310
310
  return void 0;
311
311
  }
312
+
313
+ // src/utils/process-mastra-stream.ts
314
+ async function processMastraStream({
315
+ stream,
316
+ onChunk
317
+ }) {
318
+ const reader = stream.getReader();
319
+ const decoder = new TextDecoder();
320
+ let buffer = "";
321
+ try {
322
+ while (true) {
323
+ const { done, value } = await reader.read();
324
+ if (done) break;
325
+ buffer += decoder.decode(value, { stream: true });
326
+ const lines = buffer.split("\n\n");
327
+ buffer = lines.pop() || "";
328
+ for (const line of lines) {
329
+ if (line.startsWith("data: ")) {
330
+ const data = line.slice(6);
331
+ if (data === "[DONE]") {
332
+ console.log("\u{1F3C1} Stream finished");
333
+ return;
334
+ }
335
+ try {
336
+ const json = JSON.parse(data);
337
+ await onChunk(json);
338
+ } catch (error) {
339
+ console.error("\u274C JSON parse error:", error, "Data:", data);
340
+ }
341
+ }
342
+ }
343
+ }
344
+ } finally {
345
+ reader.releaseLock();
346
+ }
347
+ }
348
+
349
+ // src/resources/agent.ts
350
+ async function executeToolCallAndRespond({
351
+ response,
352
+ params,
353
+ runId,
354
+ resourceId,
355
+ threadId,
356
+ runtimeContext,
357
+ respondFn
358
+ }) {
359
+ if (response.finishReason === "tool-calls") {
360
+ const toolCalls = response.toolCalls;
361
+ if (!toolCalls || !Array.isArray(toolCalls)) {
362
+ return response;
363
+ }
364
+ for (const toolCall of toolCalls) {
365
+ const clientTool = params.clientTools?.[toolCall.toolName];
366
+ if (clientTool && clientTool.execute) {
367
+ const result = await clientTool.execute(
368
+ { context: toolCall?.args, runId, resourceId, threadId, runtimeContext },
369
+ {
370
+ messages: response.messages,
371
+ toolCallId: toolCall?.toolCallId
372
+ }
373
+ );
374
+ const updatedMessages = [
375
+ {
376
+ role: "user",
377
+ content: params.messages
378
+ },
379
+ ...response.response.messages,
380
+ {
381
+ role: "tool",
382
+ content: [
383
+ {
384
+ type: "tool-result",
385
+ toolCallId: toolCall.toolCallId,
386
+ toolName: toolCall.toolName,
387
+ result
388
+ }
389
+ ]
390
+ }
391
+ ];
392
+ return respondFn({
393
+ ...params,
394
+ messages: updatedMessages
395
+ });
396
+ }
397
+ }
398
+ }
399
+ }
312
400
  var AgentVoice = class extends BaseResource {
313
401
  constructor(options, agentId) {
314
402
  super(options);
@@ -435,6 +523,34 @@ var Agent = class extends BaseResource {
435
523
  }
436
524
  return response;
437
525
  }
526
+ async generateVNext(params) {
527
+ const processedParams = {
528
+ ...params,
529
+ output: params.output ? zodToJsonSchema(params.output) : void 0,
530
+ runtimeContext: parseClientRuntimeContext(params.runtimeContext),
531
+ clientTools: processClientTools(params.clientTools)
532
+ };
533
+ const { runId, resourceId, threadId, runtimeContext } = processedParams;
534
+ const response = await this.request(
535
+ `/api/agents/${this.agentId}/generate/vnext`,
536
+ {
537
+ method: "POST",
538
+ body: processedParams
539
+ }
540
+ );
541
+ if (response.finishReason === "tool-calls") {
542
+ return executeToolCallAndRespond({
543
+ response,
544
+ params,
545
+ runId,
546
+ resourceId,
547
+ threadId,
548
+ runtimeContext,
549
+ respondFn: this.generateVNext.bind(this)
550
+ });
551
+ }
552
+ return response;
553
+ }
438
554
  async processChatResponse({
439
555
  stream,
440
556
  update,
@@ -725,6 +841,392 @@ var Agent = class extends BaseResource {
725
841
  };
726
842
  return streamResponse;
727
843
  }
844
+ async processChatResponse_vNext({
845
+ stream,
846
+ update,
847
+ onToolCall,
848
+ onFinish,
849
+ getCurrentDate = () => /* @__PURE__ */ new Date(),
850
+ lastMessage
851
+ }) {
852
+ const replaceLastMessage = lastMessage?.role === "assistant";
853
+ let step = replaceLastMessage ? 1 + // find max step in existing tool invocations:
854
+ (lastMessage.toolInvocations?.reduce((max, toolInvocation) => {
855
+ return Math.max(max, toolInvocation.step ?? 0);
856
+ }, 0) ?? 0) : 0;
857
+ const message = replaceLastMessage ? structuredClone(lastMessage) : {
858
+ id: v4(),
859
+ createdAt: getCurrentDate(),
860
+ role: "assistant",
861
+ content: "",
862
+ parts: []
863
+ };
864
+ let currentTextPart = void 0;
865
+ let currentReasoningPart = void 0;
866
+ let currentReasoningTextDetail = void 0;
867
+ function updateToolInvocationPart(toolCallId, invocation) {
868
+ const part = message.parts.find(
869
+ (part2) => part2.type === "tool-invocation" && part2.toolInvocation.toolCallId === toolCallId
870
+ );
871
+ if (part != null) {
872
+ part.toolInvocation = invocation;
873
+ } else {
874
+ message.parts.push({
875
+ type: "tool-invocation",
876
+ toolInvocation: invocation
877
+ });
878
+ }
879
+ }
880
+ const data = [];
881
+ let messageAnnotations = replaceLastMessage ? lastMessage?.annotations : void 0;
882
+ const partialToolCalls = {};
883
+ let usage = {
884
+ completionTokens: NaN,
885
+ promptTokens: NaN,
886
+ totalTokens: NaN
887
+ };
888
+ let finishReason = "unknown";
889
+ function execUpdate() {
890
+ const copiedData = [...data];
891
+ if (messageAnnotations?.length) {
892
+ message.annotations = messageAnnotations;
893
+ }
894
+ const copiedMessage = {
895
+ // deep copy the message to ensure that deep changes (msg attachments) are updated
896
+ // with SolidJS. SolidJS uses referential integration of sub-objects to detect changes.
897
+ ...structuredClone(message),
898
+ // add a revision id to ensure that the message is updated with SWR. SWR uses a
899
+ // hashing approach by default to detect changes, but it only works for shallow
900
+ // changes. This is why we need to add a revision id to ensure that the message
901
+ // is updated with SWR (without it, the changes get stuck in SWR and are not
902
+ // forwarded to rendering):
903
+ revisionId: v4()
904
+ };
905
+ update({
906
+ message: copiedMessage,
907
+ data: copiedData,
908
+ replaceLastMessage
909
+ });
910
+ }
911
+ await processMastraStream({
912
+ stream,
913
+ // TODO: casting as any here because the stream types were all typed as any before in core.
914
+ // but this is completely wrong and this fn is probably broken. Remove ":any" and you'll see a bunch of type errors
915
+ onChunk: async (chunk) => {
916
+ switch (chunk.type) {
917
+ case "step-start": {
918
+ if (!replaceLastMessage) {
919
+ message.id = chunk.payload.messageId;
920
+ }
921
+ message.parts.push({ type: "step-start" });
922
+ execUpdate();
923
+ break;
924
+ }
925
+ case "text-delta": {
926
+ if (currentTextPart == null) {
927
+ currentTextPart = {
928
+ type: "text",
929
+ text: chunk.payload.text
930
+ };
931
+ message.parts.push(currentTextPart);
932
+ } else {
933
+ currentTextPart.text += chunk.payload.text;
934
+ }
935
+ message.content += chunk.payload.text;
936
+ execUpdate();
937
+ break;
938
+ }
939
+ case "reasoning-delta": {
940
+ if (currentReasoningTextDetail == null) {
941
+ currentReasoningTextDetail = { type: "text", text: chunk.payload.text };
942
+ if (currentReasoningPart != null) {
943
+ currentReasoningPart.details.push(currentReasoningTextDetail);
944
+ }
945
+ } else {
946
+ currentReasoningTextDetail.text += chunk.payload.text;
947
+ }
948
+ if (currentReasoningPart == null) {
949
+ currentReasoningPart = {
950
+ type: "reasoning",
951
+ reasoning: chunk.payload.text,
952
+ details: [currentReasoningTextDetail]
953
+ };
954
+ message.parts.push(currentReasoningPart);
955
+ } else {
956
+ currentReasoningPart.reasoning += chunk.payload.text;
957
+ }
958
+ message.reasoning = (message.reasoning ?? "") + chunk.payload.text;
959
+ execUpdate();
960
+ break;
961
+ }
962
+ case "file": {
963
+ message.parts.push({
964
+ type: "file",
965
+ mimeType: chunk.payload.mimeType,
966
+ data: chunk.payload.data
967
+ });
968
+ execUpdate();
969
+ break;
970
+ }
971
+ case "source": {
972
+ message.parts.push({
973
+ type: "source",
974
+ source: chunk.payload.source
975
+ });
976
+ execUpdate();
977
+ break;
978
+ }
979
+ case "tool-call": {
980
+ const invocation = {
981
+ state: "call",
982
+ step,
983
+ ...chunk.payload
984
+ };
985
+ if (partialToolCalls[chunk.payload.toolCallId] != null) {
986
+ message.toolInvocations[partialToolCalls[chunk.payload.toolCallId].index] = invocation;
987
+ } else {
988
+ if (message.toolInvocations == null) {
989
+ message.toolInvocations = [];
990
+ }
991
+ message.toolInvocations.push(invocation);
992
+ }
993
+ updateToolInvocationPart(chunk.payload.toolCallId, invocation);
994
+ execUpdate();
995
+ if (onToolCall) {
996
+ const result = await onToolCall({ toolCall: chunk.payload });
997
+ if (result != null) {
998
+ const invocation2 = {
999
+ state: "result",
1000
+ step,
1001
+ ...chunk.payload,
1002
+ result
1003
+ };
1004
+ message.toolInvocations[message.toolInvocations.length - 1] = invocation2;
1005
+ updateToolInvocationPart(chunk.payload.toolCallId, invocation2);
1006
+ execUpdate();
1007
+ }
1008
+ }
1009
+ }
1010
+ case "tool-call-input-streaming-start": {
1011
+ if (message.toolInvocations == null) {
1012
+ message.toolInvocations = [];
1013
+ }
1014
+ partialToolCalls[chunk.payload.toolCallId] = {
1015
+ text: "",
1016
+ step,
1017
+ toolName: chunk.payload.toolName,
1018
+ index: message.toolInvocations.length
1019
+ };
1020
+ const invocation = {
1021
+ state: "partial-call",
1022
+ step,
1023
+ toolCallId: chunk.payload.toolCallId,
1024
+ toolName: chunk.payload.toolName,
1025
+ args: void 0
1026
+ };
1027
+ message.toolInvocations.push(invocation);
1028
+ updateToolInvocationPart(chunk.payload.toolCallId, invocation);
1029
+ execUpdate();
1030
+ break;
1031
+ }
1032
+ case "tool-call-delta": {
1033
+ const partialToolCall = partialToolCalls[chunk.payload.toolCallId];
1034
+ partialToolCall.text += chunk.payload.argsTextDelta;
1035
+ const { value: partialArgs } = parsePartialJson(partialToolCall.text);
1036
+ const invocation = {
1037
+ state: "partial-call",
1038
+ step: partialToolCall.step,
1039
+ toolCallId: chunk.payload.toolCallId,
1040
+ toolName: partialToolCall.toolName,
1041
+ args: partialArgs
1042
+ };
1043
+ message.toolInvocations[partialToolCall.index] = invocation;
1044
+ updateToolInvocationPart(chunk.payload.toolCallId, invocation);
1045
+ execUpdate();
1046
+ break;
1047
+ }
1048
+ case "tool-result": {
1049
+ const toolInvocations = message.toolInvocations;
1050
+ if (toolInvocations == null) {
1051
+ throw new Error("tool_result must be preceded by a tool_call");
1052
+ }
1053
+ const toolInvocationIndex = toolInvocations.findIndex(
1054
+ (invocation2) => invocation2.toolCallId === chunk.payload.toolCallId
1055
+ );
1056
+ if (toolInvocationIndex === -1) {
1057
+ throw new Error("tool_result must be preceded by a tool_call with the same toolCallId");
1058
+ }
1059
+ const invocation = {
1060
+ ...toolInvocations[toolInvocationIndex],
1061
+ state: "result",
1062
+ ...chunk.payload
1063
+ };
1064
+ toolInvocations[toolInvocationIndex] = invocation;
1065
+ updateToolInvocationPart(chunk.payload.toolCallId, invocation);
1066
+ execUpdate();
1067
+ break;
1068
+ }
1069
+ case "error": {
1070
+ throw new Error(chunk.payload.error);
1071
+ }
1072
+ case "data": {
1073
+ data.push(...chunk.payload.data);
1074
+ execUpdate();
1075
+ break;
1076
+ }
1077
+ case "step-finish": {
1078
+ step += 1;
1079
+ currentTextPart = chunk.payload.isContinued ? currentTextPart : void 0;
1080
+ currentReasoningPart = void 0;
1081
+ currentReasoningTextDetail = void 0;
1082
+ execUpdate();
1083
+ break;
1084
+ }
1085
+ case "finish": {
1086
+ finishReason = chunk.payload.finishReason;
1087
+ if (chunk.payload.usage != null) {
1088
+ usage = chunk.payload.usage;
1089
+ }
1090
+ break;
1091
+ }
1092
+ }
1093
+ }
1094
+ });
1095
+ onFinish?.({ message, finishReason, usage });
1096
+ }
1097
+ async processStreamResponse_vNext(processedParams, writable) {
1098
+ const response = await this.request(`/api/agents/${this.agentId}/stream/vnext`, {
1099
+ method: "POST",
1100
+ body: processedParams,
1101
+ stream: true
1102
+ });
1103
+ if (!response.body) {
1104
+ throw new Error("No response body");
1105
+ }
1106
+ try {
1107
+ let toolCalls = [];
1108
+ let messages = [];
1109
+ const [streamForWritable, streamForProcessing] = response.body.tee();
1110
+ streamForWritable.pipeTo(writable, {
1111
+ preventClose: true
1112
+ }).catch((error) => {
1113
+ console.error("Error piping to writable stream:", error);
1114
+ });
1115
+ this.processChatResponse_vNext({
1116
+ stream: streamForProcessing,
1117
+ update: ({ message }) => {
1118
+ const existingIndex = messages.findIndex((m) => m.id === message.id);
1119
+ if (existingIndex !== -1) {
1120
+ messages[existingIndex] = message;
1121
+ } else {
1122
+ messages.push(message);
1123
+ }
1124
+ },
1125
+ onFinish: async ({ finishReason, message }) => {
1126
+ if (finishReason === "tool-calls") {
1127
+ const toolCall = [...message?.parts ?? []].reverse().find((part) => part.type === "tool-invocation")?.toolInvocation;
1128
+ if (toolCall) {
1129
+ toolCalls.push(toolCall);
1130
+ }
1131
+ for (const toolCall2 of toolCalls) {
1132
+ const clientTool = processedParams.clientTools?.[toolCall2.toolName];
1133
+ if (clientTool && clientTool.execute) {
1134
+ const result = await clientTool.execute(
1135
+ {
1136
+ context: toolCall2?.args,
1137
+ runId: processedParams.runId,
1138
+ resourceId: processedParams.resourceId,
1139
+ threadId: processedParams.threadId,
1140
+ runtimeContext: processedParams.runtimeContext
1141
+ },
1142
+ {
1143
+ messages: response.messages,
1144
+ toolCallId: toolCall2?.toolCallId
1145
+ }
1146
+ );
1147
+ const lastMessage = JSON.parse(JSON.stringify(messages[messages.length - 1]));
1148
+ const toolInvocationPart = lastMessage?.parts?.find(
1149
+ (part) => part.type === "tool-invocation" && part.toolInvocation?.toolCallId === toolCall2.toolCallId
1150
+ );
1151
+ if (toolInvocationPart) {
1152
+ toolInvocationPart.toolInvocation = {
1153
+ ...toolInvocationPart.toolInvocation,
1154
+ state: "result",
1155
+ result
1156
+ };
1157
+ }
1158
+ const toolInvocation = lastMessage?.toolInvocations?.find(
1159
+ (toolInvocation2) => toolInvocation2.toolCallId === toolCall2.toolCallId
1160
+ );
1161
+ if (toolInvocation) {
1162
+ toolInvocation.state = "result";
1163
+ toolInvocation.result = result;
1164
+ }
1165
+ const writer = writable.getWriter();
1166
+ try {
1167
+ await writer.write(
1168
+ new TextEncoder().encode(
1169
+ "a:" + JSON.stringify({
1170
+ toolCallId: toolCall2.toolCallId,
1171
+ result
1172
+ }) + "\n"
1173
+ )
1174
+ );
1175
+ } finally {
1176
+ writer.releaseLock();
1177
+ }
1178
+ const originalMessages = processedParams.messages;
1179
+ const messageArray = Array.isArray(originalMessages) ? originalMessages : [originalMessages];
1180
+ this.processStreamResponse_vNext(
1181
+ {
1182
+ ...processedParams,
1183
+ messages: [...messageArray, ...messages.filter((m) => m.id !== lastMessage.id), lastMessage]
1184
+ },
1185
+ writable
1186
+ ).catch((error) => {
1187
+ console.error("Error processing stream response:", error);
1188
+ });
1189
+ }
1190
+ }
1191
+ } else {
1192
+ setTimeout(() => {
1193
+ writable.close();
1194
+ }, 0);
1195
+ }
1196
+ },
1197
+ lastMessage: void 0
1198
+ }).catch((error) => {
1199
+ console.error("Error processing stream response:", error);
1200
+ });
1201
+ } catch (error) {
1202
+ console.error("Error processing stream response:", error);
1203
+ }
1204
+ return response;
1205
+ }
1206
+ async streamVNext(params) {
1207
+ const processedParams = {
1208
+ ...params,
1209
+ output: params.output ? zodToJsonSchema(params.output) : void 0,
1210
+ runtimeContext: parseClientRuntimeContext(params.runtimeContext),
1211
+ clientTools: processClientTools(params.clientTools)
1212
+ };
1213
+ const { readable, writable } = new TransformStream();
1214
+ const response = await this.processStreamResponse_vNext(processedParams, writable);
1215
+ const streamResponse = new Response(readable, {
1216
+ status: response.status,
1217
+ statusText: response.statusText,
1218
+ headers: response.headers
1219
+ });
1220
+ streamResponse.processDataStream = async ({
1221
+ onChunk
1222
+ }) => {
1223
+ await processMastraStream({
1224
+ stream: streamResponse.body,
1225
+ onChunk
1226
+ });
1227
+ };
1228
+ return streamResponse;
1229
+ }
728
1230
  /**
729
1231
  * Processes the stream response and handles tool calls
730
1232
  */