@mastra/client-js 0.10.22-alpha.3 → 0.10.22-alpha.5

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.
@@ -9,7 +9,7 @@ import {
9
9
  type UIMessage,
10
10
  type UseChatOptions,
11
11
  } from '@ai-sdk/ui-utils';
12
- import { Tool, type CoreMessage, type MastraLanguageModel } from '@mastra/core';
12
+ import { Tool, type CoreMessage } from '@mastra/core';
13
13
  import { type GenerateReturn } from '@mastra/core/llm';
14
14
  import type { JSONSchema7 } from 'json-schema';
15
15
  import { ZodSchema } from 'zod';
@@ -25,12 +25,85 @@ import type {
25
25
  ClientOptions,
26
26
  StreamParams,
27
27
  UpdateModelParams,
28
+ StreamVNextParams,
28
29
  } from '../types';
29
30
 
30
31
  import { BaseResource } from './base';
31
32
  import type { RuntimeContext } from '@mastra/core/runtime-context';
32
33
  import { parseClientRuntimeContext } from '../utils';
33
- import { MessageList } from '@mastra/core/agent';
34
+ import { processMastraStream } from '../utils/process-mastra-stream';
35
+ import type { MastraModelOutput } from '@mastra/core/stream';
36
+ import type { MessageListInput } from '@mastra/core/agent/message-list';
37
+
38
+ async function executeToolCallAndRespond({
39
+ response,
40
+ params,
41
+ runId,
42
+ resourceId,
43
+ threadId,
44
+ runtimeContext,
45
+ respondFn,
46
+ }: {
47
+ params: StreamVNextParams<any>;
48
+ response: Awaited<ReturnType<MastraModelOutput['getFullOutput']>>;
49
+ runId?: string;
50
+ resourceId?: string;
51
+ threadId?: string;
52
+ runtimeContext?: RuntimeContext<any>;
53
+ respondFn: Agent['generateVNext'];
54
+ }) {
55
+ if (response.finishReason === 'tool-calls') {
56
+ const toolCalls = (
57
+ response as unknown as {
58
+ toolCalls: { toolName: string; args: any; toolCallId: string }[];
59
+ messages: CoreMessage[];
60
+ }
61
+ ).toolCalls;
62
+
63
+ if (!toolCalls || !Array.isArray(toolCalls)) {
64
+ return response;
65
+ }
66
+
67
+ for (const toolCall of toolCalls) {
68
+ const clientTool = params.clientTools?.[toolCall.toolName] as Tool;
69
+
70
+ if (clientTool && clientTool.execute) {
71
+ const result = await clientTool.execute(
72
+ { context: toolCall?.args, runId, resourceId, threadId, runtimeContext: runtimeContext as RuntimeContext },
73
+ {
74
+ messages: (response as unknown as { messages: CoreMessage[] }).messages,
75
+ toolCallId: toolCall?.toolCallId,
76
+ },
77
+ );
78
+
79
+ const updatedMessages = [
80
+ {
81
+ role: 'user',
82
+ content: params.messages,
83
+ },
84
+ ...(response.response as unknown as { messages: CoreMessage[] }).messages,
85
+ {
86
+ role: 'tool',
87
+ content: [
88
+ {
89
+ type: 'tool-result',
90
+ toolCallId: toolCall.toolCallId,
91
+ toolName: toolCall.toolName,
92
+ result,
93
+ },
94
+ ],
95
+ },
96
+ ] as MessageListInput;
97
+
98
+ // @ts-ignore
99
+ return respondFn({
100
+ ...params,
101
+ messages: updatedMessages,
102
+ });
103
+ }
104
+ }
105
+ }
106
+ }
34
107
 
35
108
  export class AgentVoice extends BaseResource {
36
109
  constructor(
@@ -204,6 +277,41 @@ export class Agent extends BaseResource {
204
277
  return response;
205
278
  }
206
279
 
280
+ async generateVNext<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
281
+ params: StreamVNextParams<T>,
282
+ ): Promise<ReturnType<MastraModelOutput['getFullOutput']>> {
283
+ const processedParams = {
284
+ ...params,
285
+ output: params.output ? zodToJsonSchema(params.output) : undefined,
286
+ runtimeContext: parseClientRuntimeContext(params.runtimeContext),
287
+ clientTools: processClientTools(params.clientTools),
288
+ };
289
+
290
+ const { runId, resourceId, threadId, runtimeContext } = processedParams as StreamVNextParams;
291
+
292
+ const response = await this.request<ReturnType<MastraModelOutput['getFullOutput']>>(
293
+ `/api/agents/${this.agentId}/generate/vnext`,
294
+ {
295
+ method: 'POST',
296
+ body: processedParams,
297
+ },
298
+ );
299
+
300
+ if (response.finishReason === 'tool-calls') {
301
+ return executeToolCallAndRespond({
302
+ response,
303
+ params,
304
+ runId,
305
+ resourceId,
306
+ threadId,
307
+ runtimeContext: runtimeContext as RuntimeContext<any>,
308
+ respondFn: this.generateVNext.bind(this),
309
+ }) as unknown as Awaited<ReturnType<MastraModelOutput['getFullOutput']>>;
310
+ }
311
+
312
+ return response;
313
+ }
314
+
207
315
  private async processChatResponse({
208
316
  stream,
209
317
  update,
@@ -595,6 +703,543 @@ export class Agent extends BaseResource {
595
703
  return streamResponse;
596
704
  }
597
705
 
706
+ private async processChatResponse_vNext({
707
+ stream,
708
+ update,
709
+ onToolCall,
710
+ onFinish,
711
+ getCurrentDate = () => new Date(),
712
+ lastMessage,
713
+ }: {
714
+ stream: ReadableStream<Uint8Array>;
715
+ update: (options: { message: UIMessage; data: JSONValue[] | undefined; replaceLastMessage: boolean }) => void;
716
+ onToolCall?: UseChatOptions['onToolCall'];
717
+ onFinish?: (options: { message: UIMessage | undefined; finishReason: string; usage: string }) => void;
718
+ generateId?: () => string;
719
+ getCurrentDate?: () => Date;
720
+ lastMessage: UIMessage | undefined;
721
+ }) {
722
+ const replaceLastMessage = lastMessage?.role === 'assistant';
723
+ let step = replaceLastMessage
724
+ ? 1 +
725
+ // find max step in existing tool invocations:
726
+ (lastMessage.toolInvocations?.reduce((max, toolInvocation) => {
727
+ return Math.max(max, toolInvocation.step ?? 0);
728
+ }, 0) ?? 0)
729
+ : 0;
730
+
731
+ const message: UIMessage = replaceLastMessage
732
+ ? structuredClone(lastMessage)
733
+ : {
734
+ id: uuid(),
735
+ createdAt: getCurrentDate(),
736
+ role: 'assistant',
737
+ content: '',
738
+ parts: [],
739
+ };
740
+
741
+ let currentTextPart: TextUIPart | undefined = undefined;
742
+ let currentReasoningPart: ReasoningUIPart | undefined = undefined;
743
+ let currentReasoningTextDetail: { type: 'text'; text: string; signature?: string } | undefined = undefined;
744
+
745
+ function updateToolInvocationPart(toolCallId: string, invocation: ToolInvocation) {
746
+ const part = message.parts.find(
747
+ part => part.type === 'tool-invocation' && part.toolInvocation.toolCallId === toolCallId,
748
+ ) as ToolInvocationUIPart | undefined;
749
+
750
+ if (part != null) {
751
+ part.toolInvocation = invocation;
752
+ } else {
753
+ message.parts.push({
754
+ type: 'tool-invocation',
755
+ toolInvocation: invocation,
756
+ });
757
+ }
758
+ }
759
+
760
+ const data: JSONValue[] = [];
761
+
762
+ // keep list of current message annotations for message
763
+ let messageAnnotations: JSONValue[] | undefined = replaceLastMessage ? lastMessage?.annotations : undefined;
764
+
765
+ // keep track of partial tool calls
766
+ const partialToolCalls: Record<string, { text: string; step: number; index: number; toolName: string }> = {};
767
+
768
+ let usage: any = {
769
+ completionTokens: NaN,
770
+ promptTokens: NaN,
771
+ totalTokens: NaN,
772
+ };
773
+ let finishReason: string = 'unknown';
774
+
775
+ function execUpdate() {
776
+ // make a copy of the data array to ensure UI is updated (SWR)
777
+ const copiedData = [...data];
778
+
779
+ // keeps the currentMessage up to date with the latest annotations,
780
+ // even if annotations preceded the message creation
781
+ if (messageAnnotations?.length) {
782
+ message.annotations = messageAnnotations;
783
+ }
784
+
785
+ const copiedMessage = {
786
+ // deep copy the message to ensure that deep changes (msg attachments) are updated
787
+ // with SolidJS. SolidJS uses referential integration of sub-objects to detect changes.
788
+ ...structuredClone(message),
789
+ // add a revision id to ensure that the message is updated with SWR. SWR uses a
790
+ // hashing approach by default to detect changes, but it only works for shallow
791
+ // changes. This is why we need to add a revision id to ensure that the message
792
+ // is updated with SWR (without it, the changes get stuck in SWR and are not
793
+ // forwarded to rendering):
794
+ revisionId: uuid(),
795
+ } as UIMessage;
796
+
797
+ update({
798
+ message: copiedMessage,
799
+ data: copiedData,
800
+ replaceLastMessage,
801
+ });
802
+ }
803
+
804
+ await processMastraStream({
805
+ stream,
806
+ // TODO: casting as any here because the stream types were all typed as any before in core.
807
+ // but this is completely wrong and this fn is probably broken. Remove ":any" and you'll see a bunch of type errors
808
+ onChunk: async (chunk: any) => {
809
+ switch (chunk.type) {
810
+ case 'step-start': {
811
+ // keep message id stable when we are updating an existing message:
812
+ if (!replaceLastMessage) {
813
+ message.id = chunk.payload.messageId;
814
+ }
815
+
816
+ // add a step boundary part to the message
817
+ message.parts.push({ type: 'step-start' });
818
+ execUpdate();
819
+ break;
820
+ }
821
+
822
+ case 'text-delta': {
823
+ if (currentTextPart == null) {
824
+ currentTextPart = {
825
+ type: 'text',
826
+ text: chunk.payload.text,
827
+ };
828
+ message.parts.push(currentTextPart);
829
+ } else {
830
+ currentTextPart.text += chunk.payload.text;
831
+ }
832
+
833
+ message.content += chunk.payload.text;
834
+ execUpdate();
835
+ break;
836
+ }
837
+
838
+ case 'reasoning-delta': {
839
+ if (currentReasoningTextDetail == null) {
840
+ currentReasoningTextDetail = { type: 'text', text: chunk.payload.text };
841
+ if (currentReasoningPart != null) {
842
+ currentReasoningPart.details.push(currentReasoningTextDetail);
843
+ }
844
+ } else {
845
+ currentReasoningTextDetail.text += chunk.payload.text;
846
+ }
847
+
848
+ if (currentReasoningPart == null) {
849
+ currentReasoningPart = {
850
+ type: 'reasoning',
851
+ reasoning: chunk.payload.text,
852
+ details: [currentReasoningTextDetail],
853
+ };
854
+ message.parts.push(currentReasoningPart);
855
+ } else {
856
+ currentReasoningPart.reasoning += chunk.payload.text;
857
+ }
858
+
859
+ message.reasoning = (message.reasoning ?? '') + chunk.payload.text;
860
+
861
+ execUpdate();
862
+ break;
863
+ }
864
+ case 'file': {
865
+ message.parts.push({
866
+ type: 'file',
867
+ mimeType: chunk.payload.mimeType,
868
+ data: chunk.payload.data,
869
+ });
870
+
871
+ execUpdate();
872
+ break;
873
+ }
874
+
875
+ case 'source': {
876
+ message.parts.push({
877
+ type: 'source',
878
+ source: chunk.payload.source,
879
+ });
880
+ execUpdate();
881
+ break;
882
+ }
883
+
884
+ case 'tool-call': {
885
+ const invocation = {
886
+ state: 'call',
887
+ step,
888
+ ...chunk.payload,
889
+ } as const;
890
+
891
+ if (partialToolCalls[chunk.payload.toolCallId] != null) {
892
+ // change the partial tool call to a full tool call
893
+ message.toolInvocations![partialToolCalls[chunk.payload.toolCallId]!.index] =
894
+ invocation as ToolInvocation;
895
+ } else {
896
+ if (message.toolInvocations == null) {
897
+ message.toolInvocations = [];
898
+ }
899
+
900
+ message.toolInvocations.push(invocation as ToolInvocation);
901
+ }
902
+
903
+ updateToolInvocationPart(chunk.payload.toolCallId, invocation as ToolInvocation);
904
+
905
+ execUpdate();
906
+
907
+ // invoke the onToolCall callback if it exists. This is blocking.
908
+ // In the future we should make this non-blocking, which
909
+ // requires additional state management for error handling etc.
910
+ if (onToolCall) {
911
+ const result = await onToolCall({ toolCall: chunk.payload as any });
912
+ if (result != null) {
913
+ const invocation = {
914
+ state: 'result',
915
+ step,
916
+ ...chunk.payload,
917
+ result,
918
+ } as const;
919
+
920
+ // store the result in the tool invocation
921
+ message.toolInvocations![message.toolInvocations!.length - 1] = invocation as ToolInvocation;
922
+
923
+ updateToolInvocationPart(chunk.payload.toolCallId, invocation as ToolInvocation);
924
+
925
+ execUpdate();
926
+ }
927
+ }
928
+ }
929
+
930
+ case 'tool-call-input-streaming-start': {
931
+ if (message.toolInvocations == null) {
932
+ message.toolInvocations = [];
933
+ }
934
+
935
+ // add the partial tool call to the map
936
+ partialToolCalls[chunk.payload.toolCallId] = {
937
+ text: '',
938
+ step,
939
+ toolName: chunk.payload.toolName,
940
+ index: message.toolInvocations.length,
941
+ };
942
+
943
+ const invocation = {
944
+ state: 'partial-call',
945
+ step,
946
+ toolCallId: chunk.payload.toolCallId,
947
+ toolName: chunk.payload.toolName,
948
+ args: undefined,
949
+ } as const;
950
+
951
+ message.toolInvocations.push(invocation as ToolInvocation);
952
+
953
+ updateToolInvocationPart(chunk.payload.toolCallId, invocation);
954
+
955
+ execUpdate();
956
+ break;
957
+ }
958
+
959
+ case 'tool-call-delta': {
960
+ const partialToolCall = partialToolCalls[chunk.payload.toolCallId];
961
+
962
+ partialToolCall!.text += chunk.payload.argsTextDelta;
963
+
964
+ const { value: partialArgs } = parsePartialJson(partialToolCall!.text);
965
+
966
+ const invocation = {
967
+ state: 'partial-call',
968
+ step: partialToolCall!.step,
969
+ toolCallId: chunk.payload.toolCallId,
970
+ toolName: partialToolCall!.toolName,
971
+ args: partialArgs,
972
+ } as const;
973
+
974
+ message.toolInvocations![partialToolCall!.index] = invocation as ToolInvocation;
975
+
976
+ updateToolInvocationPart(chunk.payload.toolCallId, invocation);
977
+
978
+ execUpdate();
979
+ break;
980
+ }
981
+
982
+ case 'tool-result': {
983
+ const toolInvocations = message.toolInvocations;
984
+
985
+ if (toolInvocations == null) {
986
+ throw new Error('tool_result must be preceded by a tool_call');
987
+ }
988
+
989
+ // find if there is any tool invocation with the same toolCallId
990
+ // and replace it with the result
991
+ const toolInvocationIndex = toolInvocations.findIndex(
992
+ invocation => invocation.toolCallId === chunk.payload.toolCallId,
993
+ );
994
+
995
+ if (toolInvocationIndex === -1) {
996
+ throw new Error('tool_result must be preceded by a tool_call with the same toolCallId');
997
+ }
998
+
999
+ const invocation = {
1000
+ ...toolInvocations[toolInvocationIndex],
1001
+ state: 'result' as const,
1002
+ ...chunk.payload,
1003
+ } as const;
1004
+
1005
+ toolInvocations[toolInvocationIndex] = invocation as ToolInvocation;
1006
+
1007
+ updateToolInvocationPart(chunk.payload.toolCallId, invocation as ToolInvocation);
1008
+
1009
+ execUpdate();
1010
+ break;
1011
+ }
1012
+
1013
+ case 'error': {
1014
+ throw new Error(chunk.payload.error);
1015
+ }
1016
+
1017
+ case 'data': {
1018
+ data.push(...chunk.payload.data);
1019
+ execUpdate();
1020
+ break;
1021
+ }
1022
+
1023
+ case 'step-finish': {
1024
+ step += 1;
1025
+
1026
+ // reset the current text and reasoning parts
1027
+ currentTextPart = chunk.payload.isContinued ? currentTextPart : undefined;
1028
+ currentReasoningPart = undefined;
1029
+ currentReasoningTextDetail = undefined;
1030
+
1031
+ execUpdate();
1032
+ break;
1033
+ }
1034
+
1035
+ case 'finish': {
1036
+ finishReason = chunk.payload.finishReason;
1037
+ if (chunk.payload.usage != null) {
1038
+ // usage = calculateLanguageModelUsage(value.usage);
1039
+ usage = chunk.payload.usage;
1040
+ }
1041
+ break;
1042
+ }
1043
+ }
1044
+ },
1045
+ });
1046
+
1047
+ onFinish?.({ message, finishReason, usage });
1048
+ }
1049
+
1050
+ async processStreamResponse_vNext(processedParams: any, writable: any) {
1051
+ const response: Response = await this.request(`/api/agents/${this.agentId}/stream/vnext`, {
1052
+ method: 'POST',
1053
+ body: processedParams,
1054
+ stream: true,
1055
+ });
1056
+
1057
+ if (!response.body) {
1058
+ throw new Error('No response body');
1059
+ }
1060
+
1061
+ try {
1062
+ let toolCalls: ToolInvocation[] = [];
1063
+ let messages: UIMessage[] = [];
1064
+
1065
+ // Use tee() to split the stream into two branches
1066
+ const [streamForWritable, streamForProcessing] = response.body.tee();
1067
+
1068
+ // Pipe one branch to the writable stream
1069
+ streamForWritable
1070
+ .pipeTo(writable, {
1071
+ preventClose: true,
1072
+ })
1073
+ .catch(error => {
1074
+ console.error('Error piping to writable stream:', error);
1075
+ });
1076
+
1077
+ // Process the other branch for chat response handling
1078
+ this.processChatResponse_vNext({
1079
+ stream: streamForProcessing,
1080
+ update: ({ message }) => {
1081
+ const existingIndex = messages.findIndex(m => m.id === message.id);
1082
+
1083
+ if (existingIndex !== -1) {
1084
+ messages[existingIndex] = message;
1085
+ } else {
1086
+ messages.push(message);
1087
+ }
1088
+ },
1089
+ onFinish: async ({ finishReason, message }) => {
1090
+ if (finishReason === 'tool-calls') {
1091
+ const toolCall = [...(message?.parts ?? [])]
1092
+ .reverse()
1093
+ .find(part => part.type === 'tool-invocation')?.toolInvocation;
1094
+ if (toolCall) {
1095
+ toolCalls.push(toolCall);
1096
+ }
1097
+
1098
+ // Handle tool calls if needed
1099
+ for (const toolCall of toolCalls) {
1100
+ const clientTool = processedParams.clientTools?.[toolCall.toolName] as Tool;
1101
+ if (clientTool && clientTool.execute) {
1102
+ const result = await clientTool.execute(
1103
+ {
1104
+ context: toolCall?.args,
1105
+ runId: processedParams.runId,
1106
+ resourceId: processedParams.resourceId,
1107
+ threadId: processedParams.threadId,
1108
+ runtimeContext: processedParams.runtimeContext as RuntimeContext,
1109
+ },
1110
+ {
1111
+ messages: (response as unknown as { messages: CoreMessage[] }).messages,
1112
+ toolCallId: toolCall?.toolCallId,
1113
+ },
1114
+ );
1115
+
1116
+ const lastMessage: UIMessage = JSON.parse(JSON.stringify(messages[messages.length - 1]));
1117
+
1118
+ const toolInvocationPart = lastMessage?.parts?.find(
1119
+ part => part.type === 'tool-invocation' && part.toolInvocation?.toolCallId === toolCall.toolCallId,
1120
+ ) as ToolInvocationUIPart | undefined;
1121
+
1122
+ if (toolInvocationPart) {
1123
+ toolInvocationPart.toolInvocation = {
1124
+ ...toolInvocationPart.toolInvocation,
1125
+ state: 'result',
1126
+ result,
1127
+ };
1128
+ }
1129
+
1130
+ const toolInvocation = lastMessage?.toolInvocations?.find(
1131
+ toolInvocation => toolInvocation.toolCallId === toolCall.toolCallId,
1132
+ ) as ToolInvocation | undefined;
1133
+
1134
+ if (toolInvocation) {
1135
+ toolInvocation.state = 'result';
1136
+ // @ts-ignore
1137
+ toolInvocation.result = result;
1138
+ }
1139
+
1140
+ // write the tool result part to the stream
1141
+ const writer = writable.getWriter();
1142
+
1143
+ try {
1144
+ await writer.write(
1145
+ new TextEncoder().encode(
1146
+ 'a:' +
1147
+ JSON.stringify({
1148
+ toolCallId: toolCall.toolCallId,
1149
+ result,
1150
+ }) +
1151
+ '\n',
1152
+ ),
1153
+ );
1154
+ } finally {
1155
+ writer.releaseLock();
1156
+ }
1157
+
1158
+ // Convert messages to the correct format for the recursive call
1159
+ const originalMessages = processedParams.messages;
1160
+ const messageArray = Array.isArray(originalMessages) ? originalMessages : [originalMessages];
1161
+
1162
+ // Recursively call stream with updated messages
1163
+ this.processStreamResponse_vNext(
1164
+ {
1165
+ ...processedParams,
1166
+ messages: [...messageArray, ...messages.filter(m => m.id !== lastMessage.id), lastMessage],
1167
+ },
1168
+ writable,
1169
+ ).catch(error => {
1170
+ console.error('Error processing stream response:', error);
1171
+ });
1172
+ }
1173
+ }
1174
+ } else {
1175
+ setTimeout(() => {
1176
+ writable.close();
1177
+ }, 0);
1178
+ }
1179
+ },
1180
+ lastMessage: undefined,
1181
+ }).catch(error => {
1182
+ console.error('Error processing stream response:', error);
1183
+ });
1184
+ } catch (error) {
1185
+ console.error('Error processing stream response:', error);
1186
+ }
1187
+
1188
+ return response;
1189
+ }
1190
+
1191
+ async streamVNext<T extends JSONSchema7 | ZodSchema | undefined = undefined>(
1192
+ params: StreamVNextParams<T>,
1193
+ ): Promise<
1194
+ Response & {
1195
+ processDataStream: ({
1196
+ onChunk,
1197
+ }: {
1198
+ onChunk: Parameters<typeof processMastraStream>[0]['onChunk'];
1199
+ }) => Promise<void>;
1200
+ }
1201
+ > {
1202
+ const processedParams = {
1203
+ ...params,
1204
+ output: params.output ? zodToJsonSchema(params.output) : undefined,
1205
+ runtimeContext: parseClientRuntimeContext(params.runtimeContext),
1206
+ clientTools: processClientTools(params.clientTools),
1207
+ };
1208
+
1209
+ // Create a readable stream that will handle the response processing
1210
+ const { readable, writable } = new TransformStream<Uint8Array, Uint8Array>();
1211
+
1212
+ // Start processing the response in the background
1213
+ const response = await this.processStreamResponse_vNext(processedParams, writable);
1214
+
1215
+ // Create a new response with the readable stream
1216
+ const streamResponse = new Response(readable, {
1217
+ status: response.status,
1218
+ statusText: response.statusText,
1219
+ headers: response.headers,
1220
+ }) as Response & {
1221
+ processDataStream: ({
1222
+ onChunk,
1223
+ }: {
1224
+ onChunk: Parameters<typeof processMastraStream>[0]['onChunk'];
1225
+ }) => Promise<void>;
1226
+ };
1227
+
1228
+ // Add the processDataStream method to the response
1229
+ streamResponse.processDataStream = async ({
1230
+ onChunk,
1231
+ }: {
1232
+ onChunk: Parameters<typeof processMastraStream>[0]['onChunk'];
1233
+ }) => {
1234
+ await processMastraStream({
1235
+ stream: streamResponse.body as ReadableStream<Uint8Array>,
1236
+ onChunk,
1237
+ });
1238
+ };
1239
+
1240
+ return streamResponse;
1241
+ }
1242
+
598
1243
  /**
599
1244
  * Processes the stream response and handles tool calls
600
1245
  */