@perstack/runtime 0.0.60 → 0.0.62

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/src/index.js CHANGED
@@ -5,10 +5,10 @@ import { createDeepSeek } from '@ai-sdk/deepseek';
5
5
  import { createGoogleGenerativeAI } from '@ai-sdk/google';
6
6
  import { createVertex } from '@ai-sdk/google-vertex';
7
7
  import { createOpenAI } from '@ai-sdk/openai';
8
- import { knownModels, stopRunByDelegate, stopRunByInteractiveTool, resolveThought, attemptCompletion, resolvePdfFile, resolveImageFile, resolveToolResult, stopRunByExceededMaxSteps, continueToNextStep, retry, completeRun, callDelegate, callInteractiveTool, callTool, startRun, startGeneration, finishToolCall, runParamsSchema, createRuntimeEvent, checkpointSchema, runSettingSchema } from '@perstack/core';
8
+ import { knownModels, stopRunByDelegate, stopRunByInteractiveTool, resolveToolResults, attemptCompletion, callDelegate, callInteractiveTool, stopRunByExceededMaxSteps, continueToNextStep, retry, completeRun, callTools, startRun, resumeToolCalls, finishAllToolCalls, startGeneration, finishToolCall, runParamsSchema, createRuntimeEvent, checkpointSchema, runSettingSchema } from '@perstack/core';
9
9
  import { createOllama } from 'ollama-ai-provider-v2';
10
10
  import { createId } from '@paralleldrive/cuid2';
11
- import { readFile, readdir, mkdir, writeFile } from 'fs/promises';
11
+ import { readdir, readFile, mkdir, writeFile } from 'fs/promises';
12
12
  import path from 'path';
13
13
  import { setup, assign, createActor } from 'xstate';
14
14
  import { generateText, tool, jsonSchema } from 'ai';
@@ -21,7 +21,7 @@ import { ApiV1Client } from '@perstack/api-client/v1';
21
21
 
22
22
  // package.json
23
23
  var package_default = {
24
- version: "0.0.60"};
24
+ version: "0.0.62"};
25
25
  function getModel(modelId, providerConfig) {
26
26
  switch (providerConfig.providerName) {
27
27
  case "anthropic": {
@@ -192,7 +192,9 @@ function buildDelegationReturnState(currentSetting, resultCheckpoint, parentChec
192
192
  checkpoint: {
193
193
  ...parentCheckpoint,
194
194
  stepNumber: resultCheckpoint.stepNumber,
195
- usage: resultCheckpoint.usage
195
+ usage: resultCheckpoint.usage,
196
+ pendingToolCalls: parentCheckpoint.pendingToolCalls,
197
+ partialToolResults: parentCheckpoint.partialToolResults
196
198
  }
197
199
  };
198
200
  }
@@ -229,7 +231,9 @@ function buildDelegateToState(currentSetting, resultCheckpoint, currentExpert) {
229
231
  toolName,
230
232
  checkpointId: resultCheckpoint.id
231
233
  },
232
- usage: resultCheckpoint.usage
234
+ usage: resultCheckpoint.usage,
235
+ pendingToolCalls: void 0,
236
+ partialToolResults: void 0
233
237
  }
234
238
  };
235
239
  }
@@ -479,25 +483,37 @@ var McpSkillManager = class extends BaseSkillManager {
479
483
  env[envName] = this._env[envName];
480
484
  }
481
485
  const startTime = Date.now();
482
- console.log(`[MCP] Starting skill ${skill.name}...`);
483
486
  const { command, args } = this._getCommandArgs(skill);
487
+ if (this._eventListener) {
488
+ const event = createRuntimeEvent("skillStarting", this._runId, {
489
+ skillName: skill.name,
490
+ command,
491
+ args
492
+ });
493
+ this._eventListener(event);
494
+ }
484
495
  const transport = new StdioClientTransport({ command, args, env, stderr: "pipe" });
485
496
  if (transport.stderr) {
486
497
  transport.stderr.on("data", (chunk) => {
487
- console.log(`[MCP:${skill.name}:stderr] ${chunk.toString().trim()}`);
498
+ if (this._eventListener) {
499
+ const event = createRuntimeEvent("skillStderr", this._runId, {
500
+ skillName: skill.name,
501
+ message: chunk.toString().trim()
502
+ });
503
+ this._eventListener(event);
504
+ }
488
505
  });
489
506
  }
490
507
  const connectStartTime = Date.now();
491
508
  await this._mcpClient.connect(transport);
492
509
  const connectTime = Date.now();
493
- console.log(
494
- `[MCP] Skill ${skill.name} connected in ${connectTime - connectStartTime}ms (total: ${connectTime - startTime}ms)`
495
- );
496
510
  if (this._eventListener) {
497
511
  const serverInfo = this._mcpClient.getServerVersion();
498
512
  const event = createRuntimeEvent("skillConnected", this._runId, {
499
513
  skillName: skill.name,
500
- serverInfo: serverInfo ? { name: serverInfo.name, version: serverInfo.version } : void 0
514
+ serverInfo: serverInfo ? { name: serverInfo.name, version: serverInfo.version } : void 0,
515
+ connectDurationMs: connectTime - connectStartTime,
516
+ totalDurationMs: connectTime - startTime
501
517
  });
502
518
  this._eventListener(event);
503
519
  }
@@ -729,10 +745,14 @@ async function callingDelegateLogic({
729
745
  step,
730
746
  skillManagers
731
747
  }) {
732
- if (!step.toolCall) {
733
- throw new Error("No tool call found");
748
+ if (!step.pendingToolCalls || step.pendingToolCalls.length === 0) {
749
+ throw new Error("No pending tool calls found");
734
750
  }
735
- const { id, toolName, args } = step.toolCall;
751
+ const toolCall = step.pendingToolCalls[0];
752
+ if (!toolCall) {
753
+ throw new Error("No pending tool call found");
754
+ }
755
+ const { id, toolName, args } = toolCall;
736
756
  const skillManager = await getSkillManagerByToolName(skillManagers, toolName);
737
757
  if (!skillManager.expert) {
738
758
  throw new Error(`Delegation error: skill manager "${toolName}" not found`);
@@ -740,6 +760,8 @@ async function callingDelegateLogic({
740
760
  if (!args || !args.query || typeof args.query !== "string") {
741
761
  throw new Error("Delegation error: query is undefined");
742
762
  }
763
+ const currentToolCall = step.pendingToolCalls[0];
764
+ const remainingToolCalls = step.pendingToolCalls.slice(1);
743
765
  return stopRunByDelegate(setting, checkpoint, {
744
766
  checkpoint: {
745
767
  ...checkpoint,
@@ -753,7 +775,9 @@ async function callingDelegateLogic({
753
775
  toolCallId: id,
754
776
  toolName,
755
777
  query: args.query
756
- }
778
+ },
779
+ pendingToolCalls: [currentToolCall, ...remainingToolCalls],
780
+ partialToolResults: step.partialToolResults
757
781
  },
758
782
  step: {
759
783
  ...step,
@@ -766,10 +790,17 @@ async function callingInteractiveToolLogic({
766
790
  checkpoint,
767
791
  step
768
792
  }) {
793
+ if (!step.pendingToolCalls || step.pendingToolCalls.length === 0) {
794
+ throw new Error("No pending tool calls found");
795
+ }
796
+ const currentToolCall = step.pendingToolCalls[0];
797
+ const remainingToolCalls = step.pendingToolCalls.slice(1);
769
798
  return stopRunByInteractiveTool(setting, checkpoint, {
770
799
  checkpoint: {
771
800
  ...checkpoint,
772
- status: "stoppedByInteractiveTool"
801
+ status: "stoppedByInteractiveTool",
802
+ pendingToolCalls: [currentToolCall, ...remainingToolCalls],
803
+ partialToolResults: step.partialToolResults
773
804
  },
774
805
  step: {
775
806
  ...step,
@@ -777,37 +808,155 @@ async function callingInteractiveToolLogic({
777
808
  }
778
809
  });
779
810
  }
811
+ function hasRemainingTodos(toolResult) {
812
+ const firstPart = toolResult.result[0];
813
+ if (!firstPart || firstPart.type !== "textPart") {
814
+ return false;
815
+ }
816
+ try {
817
+ const parsed = JSON.parse(firstPart.text);
818
+ return Array.isArray(parsed.remainingTodos) && parsed.remainingTodos.length > 0;
819
+ } catch {
820
+ return false;
821
+ }
822
+ }
823
+ function isFileInfo(value) {
824
+ return typeof value === "object" && value !== null && "path" in value && "mimeType" in value && "size" in value && typeof value.path === "string" && typeof value.mimeType === "string" && typeof value.size === "number";
825
+ }
826
+ async function processFileToolResult(toolResult, toolName) {
827
+ const processedContents = [];
828
+ for (const part of toolResult.result) {
829
+ if (part.type !== "textPart") {
830
+ processedContents.push(part);
831
+ continue;
832
+ }
833
+ let fileInfo;
834
+ try {
835
+ const parsed = JSON.parse(part.text);
836
+ if (isFileInfo(parsed)) {
837
+ fileInfo = parsed;
838
+ }
839
+ } catch {
840
+ processedContents.push(part);
841
+ continue;
842
+ }
843
+ if (!fileInfo) {
844
+ processedContents.push(part);
845
+ continue;
846
+ }
847
+ const { path: path2, mimeType } = fileInfo;
848
+ try {
849
+ const buffer = await readFile(path2);
850
+ if (toolName === "readImageFile") {
851
+ processedContents.push({
852
+ type: "imageInlinePart",
853
+ id: part.id,
854
+ encodedData: buffer.toString("base64"),
855
+ mimeType
856
+ });
857
+ } else {
858
+ processedContents.push({
859
+ type: "fileInlinePart",
860
+ id: part.id,
861
+ encodedData: buffer.toString("base64"),
862
+ mimeType
863
+ });
864
+ }
865
+ } catch (error) {
866
+ processedContents.push({
867
+ type: "textPart",
868
+ id: part.id,
869
+ text: `Failed to read file "${path2}": ${error instanceof Error ? error.message : String(error)}`
870
+ });
871
+ }
872
+ }
873
+ return { ...toolResult, result: processedContents };
874
+ }
875
+ async function executeMcpToolCall(toolCall, skillManagers) {
876
+ const skillManager = await getSkillManagerByToolName(skillManagers, toolCall.toolName);
877
+ if (skillManager.type !== "mcp") {
878
+ throw new Error(`Incorrect SkillType, required MCP, got ${skillManager.type}`);
879
+ }
880
+ const result = await skillManager.callTool(toolCall.toolName, toolCall.args);
881
+ const toolResult = {
882
+ id: toolCall.id,
883
+ skillName: toolCall.skillName,
884
+ toolName: toolCall.toolName,
885
+ result
886
+ };
887
+ if (toolCall.toolName === "readPdfFile" || toolCall.toolName === "readImageFile") {
888
+ return processFileToolResult(toolResult, toolCall.toolName);
889
+ }
890
+ return toolResult;
891
+ }
892
+ async function getToolType(toolCall, skillManagers) {
893
+ const skillManager = await getSkillManagerByToolName(skillManagers, toolCall.toolName);
894
+ return skillManager.type;
895
+ }
780
896
  async function callingToolLogic({
781
897
  setting,
782
898
  checkpoint,
783
899
  step,
784
900
  skillManagers
785
901
  }) {
786
- if (!step.toolCall) {
787
- throw new Error("No tool call found");
902
+ const pendingToolCalls = step.pendingToolCalls ?? step.toolCalls ?? [];
903
+ if (pendingToolCalls.length === 0) {
904
+ throw new Error("No tool calls found");
788
905
  }
789
- const { id, skillName, toolName, args } = step.toolCall;
790
- const skillManager = await getSkillManagerByToolName(skillManagers, toolName);
791
- if (skillManager.type !== "mcp") {
792
- throw new Error(`Incorrect SkillType, required MCP, got ${skillManager.type}`);
906
+ const toolResults = step.toolResults ? [...step.toolResults] : [];
907
+ const attemptCompletionTool = pendingToolCalls.find(
908
+ (tc) => tc.skillName === "@perstack/base" && tc.toolName === "attemptCompletion"
909
+ );
910
+ if (attemptCompletionTool) {
911
+ const toolResult = await executeMcpToolCall(attemptCompletionTool, skillManagers);
912
+ if (hasRemainingTodos(toolResult)) {
913
+ return resolveToolResults(setting, checkpoint, { toolResults: [toolResult] });
914
+ }
915
+ return attemptCompletion(setting, checkpoint, { toolResult });
916
+ }
917
+ const toolCallTypes = await Promise.all(
918
+ pendingToolCalls.map(async (tc) => ({
919
+ toolCall: tc,
920
+ type: await getToolType(tc, skillManagers)
921
+ }))
922
+ );
923
+ const mcpToolCalls = toolCallTypes.filter((t) => t.type === "mcp").map((t) => t.toolCall);
924
+ const delegateToolCalls = toolCallTypes.filter((t) => t.type === "delegate").map((t) => t.toolCall);
925
+ const interactiveToolCalls = toolCallTypes.filter((t) => t.type === "interactive").map((t) => t.toolCall);
926
+ if (mcpToolCalls.length > 0) {
927
+ const mcpResults = await Promise.all(
928
+ mcpToolCalls.map((tc) => executeMcpToolCall(tc, skillManagers))
929
+ );
930
+ toolResults.push(...mcpResults);
931
+ }
932
+ const remainingToolCalls = [...delegateToolCalls, ...interactiveToolCalls];
933
+ if (delegateToolCalls.length > 0) {
934
+ const delegateToolCall = delegateToolCalls[0];
935
+ if (!delegateToolCall) {
936
+ throw new Error("No delegate tool call found");
937
+ }
938
+ step.partialToolResults = toolResults;
939
+ step.pendingToolCalls = remainingToolCalls;
940
+ return callDelegate(setting, checkpoint, {
941
+ newMessage: checkpoint.messages[checkpoint.messages.length - 1],
942
+ toolCall: delegateToolCall,
943
+ usage: step.usage
944
+ });
793
945
  }
794
- const result = await skillManager.callTool(toolName, args);
795
- const toolResult = { id, skillName, toolName, result };
796
- if (skillName === "@perstack/base") {
797
- if (toolName === "think") {
798
- return resolveThought(setting, checkpoint, { toolResult });
799
- }
800
- if (toolName === "attemptCompletion") {
801
- return attemptCompletion(setting, checkpoint, { toolResult });
802
- }
803
- if (toolName === "readPdfFile") {
804
- return resolvePdfFile(setting, checkpoint, { toolResult });
805
- }
806
- if (toolName === "readImageFile") {
807
- return resolveImageFile(setting, checkpoint, { toolResult });
808
- }
946
+ if (interactiveToolCalls.length > 0) {
947
+ const interactiveToolCall = interactiveToolCalls[0];
948
+ if (!interactiveToolCall) {
949
+ throw new Error("No interactive tool call found");
950
+ }
951
+ step.partialToolResults = toolResults;
952
+ step.pendingToolCalls = remainingToolCalls;
953
+ return callInteractiveTool(setting, checkpoint, {
954
+ newMessage: checkpoint.messages[checkpoint.messages.length - 1],
955
+ toolCall: interactiveToolCall,
956
+ usage: step.usage
957
+ });
809
958
  }
810
- return resolveToolResult(setting, checkpoint, { toolResult });
959
+ return resolveToolResults(setting, checkpoint, { toolResults });
811
960
  }
812
961
  async function finishingStepLogic({
813
962
  setting,
@@ -1023,9 +1172,12 @@ function toolResultPartToCoreToolResultPart(part) {
1023
1172
  output: { type: "text", value: contents[0].text }
1024
1173
  };
1025
1174
  }
1026
- const contentValue = contents.map(
1027
- (content) => content.type === "textPart" ? { type: "text", text: content.text } : { type: "media", data: content.encodedData, mediaType: content.mimeType }
1028
- );
1175
+ const contentValue = contents.map((content) => {
1176
+ if (content.type === "textPart") {
1177
+ return { type: "text", text: content.text };
1178
+ }
1179
+ return { type: "media", data: content.encodedData, mediaType: content.mimeType };
1180
+ });
1029
1181
  return {
1030
1182
  type: "tool-result",
1031
1183
  toolCallId: part.toolCallId,
@@ -1040,21 +1192,21 @@ async function generatingRunResultLogic({
1040
1192
  checkpoint,
1041
1193
  step
1042
1194
  }) {
1043
- if (!step.toolCall || !step.toolResult) {
1044
- throw new Error("No tool call or tool result found");
1195
+ if (!step.toolCalls || !step.toolResults || step.toolResults.length === 0) {
1196
+ throw new Error("No tool calls or tool results found");
1045
1197
  }
1046
- const { id, toolName } = step.toolCall;
1047
- const { result } = step.toolResult;
1048
- const toolMessage = createToolMessage([
1049
- {
1198
+ const toolResultParts = step.toolResults.map((toolResult) => {
1199
+ const toolCall = step.toolCalls?.find((tc) => tc.id === toolResult.id);
1200
+ return {
1050
1201
  type: "toolResultPart",
1051
- toolCallId: id,
1052
- toolName,
1053
- contents: result.filter(
1054
- (part) => part.type === "textPart" || part.type === "imageInlinePart"
1202
+ toolCallId: toolResult.id,
1203
+ toolName: toolCall?.toolName ?? toolResult.toolName,
1204
+ contents: toolResult.result.filter(
1205
+ (part) => part.type === "textPart" || part.type === "imageInlinePart" || part.type === "fileInlinePart"
1055
1206
  )
1056
- }
1057
- ]);
1207
+ };
1208
+ });
1209
+ const toolMessage = createToolMessage(toolResultParts);
1058
1210
  const model = getModel(setting.model, setting.providerConfig);
1059
1211
  const { messages } = checkpoint;
1060
1212
  let generationResult;
@@ -1091,13 +1243,48 @@ async function generatingRunResultLogic({
1091
1243
  step: {
1092
1244
  ...step,
1093
1245
  newMessages: [...step.newMessages, ...newMessages],
1094
- finishedAt: (/* @__PURE__ */ new Date()).getTime(),
1246
+ finishedAt: Date.now(),
1095
1247
  usage: sumUsage(step.usage, usage)
1096
1248
  },
1097
1249
  text,
1098
1250
  usage
1099
1251
  });
1100
1252
  }
1253
+ async function classifyToolCalls(toolCalls, skillManagers) {
1254
+ return Promise.all(
1255
+ toolCalls.map(async (tc) => {
1256
+ const skillManager = await getSkillManagerByToolName(skillManagers, tc.toolName);
1257
+ return {
1258
+ toolCallId: tc.toolCallId,
1259
+ toolName: tc.toolName,
1260
+ input: tc.input,
1261
+ skillManager
1262
+ };
1263
+ })
1264
+ );
1265
+ }
1266
+ function sortToolCallsByPriority(toolCalls) {
1267
+ const priority = { mcp: 0, delegate: 1, interactive: 2 };
1268
+ return [...toolCalls].sort(
1269
+ (a, b) => (priority[a.skillManager.type] ?? 99) - (priority[b.skillManager.type] ?? 99)
1270
+ );
1271
+ }
1272
+ function buildToolCallParts(toolCalls) {
1273
+ return toolCalls.map((tc) => ({
1274
+ type: "toolCallPart",
1275
+ toolCallId: tc.toolCallId,
1276
+ toolName: tc.toolName,
1277
+ args: tc.input
1278
+ }));
1279
+ }
1280
+ function buildToolCalls(toolCalls) {
1281
+ return toolCalls.map((tc) => ({
1282
+ id: tc.toolCallId,
1283
+ skillName: tc.skillManager.name,
1284
+ toolName: tc.toolName,
1285
+ args: tc.input
1286
+ }));
1287
+ }
1101
1288
  async function generatingToolCallLogic({
1102
1289
  setting,
1103
1290
  checkpoint,
@@ -1129,8 +1316,7 @@ async function generatingToolCallLogic({
1129
1316
  }
1130
1317
  const usage = usageFromGenerateTextResult(result);
1131
1318
  const { text, toolCalls, finishReason } = result;
1132
- const toolCall = toolCalls[0];
1133
- if (!toolCall) {
1319
+ if (toolCalls.length === 0) {
1134
1320
  const reason = JSON.stringify({
1135
1321
  error: "Error: No tool call generated",
1136
1322
  message: "You must generate a tool call. Try again."
@@ -1141,42 +1327,26 @@ async function generatingToolCallLogic({
1141
1327
  usage
1142
1328
  });
1143
1329
  }
1144
- const contents = [
1145
- {
1146
- type: "toolCallPart",
1147
- toolCallId: toolCall.toolCallId,
1148
- toolName: toolCall.toolName,
1149
- args: toolCall.input
1150
- }
1151
- ];
1152
- if (text) {
1153
- contents.push({
1154
- type: "textPart",
1155
- text
1156
- });
1157
- }
1158
- const skillManager = await getSkillManagerByToolName(skillManagers, toolCall.toolName);
1159
- const eventPayload = {
1160
- newMessage: createExpertMessage(contents),
1161
- toolCall: {
1162
- id: toolCall.toolCallId,
1163
- skillName: skillManager.name,
1164
- toolName: toolCall.toolName,
1165
- args: toolCall.input
1166
- },
1167
- usage
1168
- };
1330
+ const classified = await classifyToolCalls(toolCalls, skillManagers);
1331
+ const sorted = sortToolCallsByPriority(classified);
1169
1332
  if (finishReason === "tool-calls" || finishReason === "stop") {
1170
- switch (skillManager.type) {
1171
- case "mcp":
1172
- return callTool(setting, checkpoint, eventPayload);
1173
- case "interactive":
1174
- return callInteractiveTool(setting, checkpoint, eventPayload);
1175
- case "delegate":
1176
- return callDelegate(setting, checkpoint, eventPayload);
1177
- }
1333
+ const toolCallParts = buildToolCallParts(sorted);
1334
+ const contents = [...toolCallParts];
1335
+ if (text) {
1336
+ contents.push({ type: "textPart", text });
1337
+ }
1338
+ const allToolCalls = buildToolCalls(sorted);
1339
+ return callTools(setting, checkpoint, {
1340
+ newMessage: createExpertMessage(contents),
1341
+ toolCalls: allToolCalls,
1342
+ usage
1343
+ });
1178
1344
  }
1179
1345
  if (finishReason === "length") {
1346
+ const firstToolCall = sorted[0];
1347
+ if (!firstToolCall) {
1348
+ throw new Error("No tool call found");
1349
+ }
1180
1350
  const reason = JSON.stringify({
1181
1351
  error: "Error: Tool call generation failed",
1182
1352
  message: "Generation length exceeded. Try again."
@@ -1187,27 +1357,36 @@ async function generatingToolCallLogic({
1187
1357
  createExpertMessage([
1188
1358
  {
1189
1359
  type: "toolCallPart",
1190
- toolCallId: toolCall.toolCallId,
1191
- toolName: toolCall.toolName,
1192
- args: toolCall.input
1360
+ toolCallId: firstToolCall.toolCallId,
1361
+ toolName: firstToolCall.toolName,
1362
+ args: firstToolCall.input
1193
1363
  }
1194
1364
  ]),
1195
1365
  createToolMessage([
1196
1366
  {
1197
1367
  type: "toolResultPart",
1198
- toolCallId: toolCall.toolCallId,
1199
- toolName: toolCall.toolName,
1368
+ toolCallId: firstToolCall.toolCallId,
1369
+ toolName: firstToolCall.toolName,
1200
1370
  contents: [{ type: "textPart", text: reason }]
1201
1371
  }
1202
1372
  ])
1203
1373
  ],
1204
- toolCall: eventPayload.toolCall,
1205
- toolResult: {
1206
- id: toolCall.toolCallId,
1207
- skillName: skillManager.name,
1208
- toolName: toolCall.toolName,
1209
- result: [{ type: "textPart", id: createId(), text: reason }]
1210
- },
1374
+ toolCalls: [
1375
+ {
1376
+ id: firstToolCall.toolCallId,
1377
+ skillName: firstToolCall.skillManager.name,
1378
+ toolName: firstToolCall.toolName,
1379
+ args: firstToolCall.input
1380
+ }
1381
+ ],
1382
+ toolResults: [
1383
+ {
1384
+ id: firstToolCall.toolCallId,
1385
+ skillName: firstToolCall.skillManager.name,
1386
+ toolName: firstToolCall.toolName,
1387
+ result: [{ type: "textPart", id: createId(), text: reason }]
1388
+ }
1389
+ ],
1211
1390
  usage
1212
1391
  });
1213
1392
  }
@@ -1336,18 +1515,26 @@ async function initLogic({
1336
1515
  if (!setting.input.interactiveToolCallResult) {
1337
1516
  throw new Error("Interactive tool call result is undefined");
1338
1517
  }
1339
- return startRun(setting, checkpoint, {
1340
- initialCheckpoint: checkpoint,
1341
- inputMessages: [
1342
- createToolMessage([
1343
- {
1344
- type: "toolResultPart",
1345
- toolCallId: setting.input.interactiveToolCallResult.toolCallId,
1346
- toolName: setting.input.interactiveToolCallResult.toolName,
1347
- contents: [{ type: "textPart", text: setting.input.interactiveToolCallResult.text }]
1348
- }
1349
- ])
1350
- ]
1518
+ const { toolCallId, toolName, text } = setting.input.interactiveToolCallResult;
1519
+ const pendingToolCalls = checkpoint.pendingToolCalls ?? [];
1520
+ const completedToolCall = pendingToolCalls.find((tc) => tc.id === toolCallId);
1521
+ const skillName = completedToolCall?.skillName ?? (checkpoint.status === "stoppedByDelegate" ? checkpoint.delegateTo?.expert.key : "") ?? "";
1522
+ const newToolResult = {
1523
+ id: toolCallId,
1524
+ skillName,
1525
+ toolName,
1526
+ result: [{ type: "textPart", id: createId(), text }]
1527
+ };
1528
+ const updatedPartialResults = [...checkpoint.partialToolResults ?? [], newToolResult];
1529
+ const updatedPendingToolCalls = pendingToolCalls.filter((tc) => tc.id !== toolCallId);
1530
+ const updatedCheckpoint = {
1531
+ ...checkpoint,
1532
+ partialToolResults: updatedPartialResults,
1533
+ pendingToolCalls: updatedPendingToolCalls.length > 0 ? updatedPendingToolCalls : void 0
1534
+ };
1535
+ return startRun(setting, updatedCheckpoint, {
1536
+ initialCheckpoint: updatedCheckpoint,
1537
+ inputMessages: []
1351
1538
  });
1352
1539
  }
1353
1540
  default:
@@ -1364,116 +1551,27 @@ async function preparingForStepLogic({
1364
1551
  setting,
1365
1552
  checkpoint
1366
1553
  }) {
1367
- return startGeneration(setting, checkpoint, {
1368
- messages: checkpoint.messages
1369
- });
1370
- }
1371
- async function resolvingImageFileLogic({
1372
- setting,
1373
- checkpoint,
1374
- step
1375
- }) {
1376
- if (!step.toolCall || !step.toolResult) {
1377
- throw new Error("No tool call or tool result found");
1378
- }
1379
- const { id, toolName } = step.toolCall;
1380
- const { result } = step.toolResult;
1381
- const textParts = result.filter((part) => part.type === "textPart");
1382
- const files = [];
1383
- for (const textPart of textParts) {
1384
- let imageInfo;
1385
- try {
1386
- imageInfo = JSON.parse(textPart.text);
1387
- } catch {
1388
- files.push({
1389
- type: "textPart",
1390
- text: textPart.text
1391
- });
1392
- continue;
1393
- }
1394
- const { path: path2, mimeType, size } = imageInfo;
1395
- try {
1396
- const buffer = await readFile(path2);
1397
- files.push({
1398
- type: "imageInlinePart",
1399
- encodedData: buffer.toString("base64"),
1400
- mimeType
1401
- });
1402
- } catch (error) {
1403
- files.push({
1404
- type: "textPart",
1405
- text: `Failed to read image file "${path2}": ${error instanceof Error ? error.message : String(error)}`
1406
- });
1407
- }
1554
+ if (checkpoint.pendingToolCalls && checkpoint.pendingToolCalls.length > 0) {
1555
+ return resumeToolCalls(setting, checkpoint, {
1556
+ pendingToolCalls: checkpoint.pendingToolCalls,
1557
+ partialToolResults: checkpoint.partialToolResults ?? []
1558
+ });
1408
1559
  }
1409
- return finishToolCall(setting, checkpoint, {
1410
- newMessages: [
1411
- createToolMessage([
1412
- {
1413
- type: "toolResultPart",
1414
- toolCallId: id,
1415
- toolName,
1416
- contents: files
1417
- }
1418
- ])
1419
- ]
1420
- });
1421
- }
1422
- async function resolvingPdfFileLogic({
1423
- setting,
1424
- checkpoint,
1425
- step
1426
- }) {
1427
- if (!step.toolCall || !step.toolResult) {
1428
- throw new Error("No tool call or tool result found");
1429
- }
1430
- const { id, toolName } = step.toolCall;
1431
- const { result } = step.toolResult;
1432
- const textParts = result.filter((part) => part.type === "textPart");
1433
- const files = [];
1434
- for (const textPart of textParts) {
1435
- let pdfInfo;
1436
- try {
1437
- pdfInfo = JSON.parse(textPart.text);
1438
- } catch {
1439
- files.push({
1440
- type: "textPart",
1441
- text: textPart.text
1442
- });
1443
- continue;
1444
- }
1445
- const { path: path2, mimeType, size } = pdfInfo;
1446
- try {
1447
- const buffer = await readFile(path2);
1448
- files.push({
1449
- type: "fileInlinePart",
1450
- encodedData: buffer.toString("base64"),
1451
- mimeType
1452
- });
1453
- } catch (error) {
1454
- files.push({
1455
- type: "textPart",
1456
- text: `Failed to read PDF file "${path2}": ${error instanceof Error ? error.message : String(error)}`
1457
- });
1458
- }
1560
+ if (checkpoint.partialToolResults && checkpoint.partialToolResults.length > 0) {
1561
+ const toolResultParts = checkpoint.partialToolResults.map((tr) => ({
1562
+ type: "toolResultPart",
1563
+ toolCallId: tr.id,
1564
+ toolName: tr.toolName,
1565
+ contents: tr.result.filter(
1566
+ (part) => part.type === "textPart" || part.type === "imageInlinePart" || part.type === "fileInlinePart"
1567
+ )
1568
+ }));
1569
+ return finishAllToolCalls(setting, checkpoint, {
1570
+ newMessages: [createToolMessage(toolResultParts)]
1571
+ });
1459
1572
  }
1460
- return finishToolCall(setting, checkpoint, {
1461
- newMessages: [
1462
- createToolMessage([
1463
- {
1464
- type: "toolResultPart",
1465
- toolCallId: id,
1466
- toolName,
1467
- contents: [
1468
- {
1469
- type: "textPart",
1470
- text: "User uploads PDF file as follows."
1471
- }
1472
- ]
1473
- }
1474
- ]),
1475
- createUserMessage(files)
1476
- ]
1573
+ return startGeneration(setting, checkpoint, {
1574
+ messages: checkpoint.messages
1477
1575
  });
1478
1576
  }
1479
1577
  async function resolvingToolResultLogic({
@@ -1481,24 +1579,22 @@ async function resolvingToolResultLogic({
1481
1579
  checkpoint,
1482
1580
  step
1483
1581
  }) {
1484
- if (!step.toolCall || !step.toolResult) {
1485
- throw new Error("No tool call or tool result found");
1582
+ if (!step.toolCalls || !step.toolResults || step.toolResults.length === 0) {
1583
+ throw new Error("No tool calls or tool results found");
1486
1584
  }
1487
- const { id, toolName } = step.toolCall;
1488
- const { result } = step.toolResult;
1585
+ const toolResultParts = step.toolResults.map((toolResult) => {
1586
+ const toolCall = step.toolCalls?.find((tc) => tc.id === toolResult.id);
1587
+ return {
1588
+ type: "toolResultPart",
1589
+ toolCallId: toolResult.id,
1590
+ toolName: toolCall?.toolName ?? toolResult.toolName,
1591
+ contents: toolResult.result.filter(
1592
+ (part) => part.type === "textPart" || part.type === "imageInlinePart" || part.type === "fileInlinePart"
1593
+ )
1594
+ };
1595
+ });
1489
1596
  return finishToolCall(setting, checkpoint, {
1490
- newMessages: [
1491
- createToolMessage([
1492
- {
1493
- type: "toolResultPart",
1494
- toolCallId: id,
1495
- toolName,
1496
- contents: result.filter(
1497
- (part) => part.type === "textPart" || part.type === "imageInlinePart"
1498
- )
1499
- }
1500
- ])
1501
- ]
1597
+ newMessages: [createToolMessage(toolResultParts)]
1502
1598
  });
1503
1599
  }
1504
1600
 
@@ -1540,7 +1636,9 @@ var runtimeStateMachine = setup({
1540
1636
  checkpoint: ({ context, event }) => ({
1541
1637
  ...context.checkpoint,
1542
1638
  status: "proceeding",
1543
- messages: [...context.checkpoint.messages, ...event.inputMessages]
1639
+ messages: [...context.checkpoint.messages, ...event.inputMessages],
1640
+ pendingToolCalls: event.initialCheckpoint.pendingToolCalls,
1641
+ partialToolResults: event.initialCheckpoint.partialToolResults
1544
1642
  }),
1545
1643
  step: ({ context, event }) => ({
1546
1644
  ...context.step,
@@ -1563,6 +1661,38 @@ var runtimeStateMachine = setup({
1563
1661
  startedAt: event.timestamp
1564
1662
  })
1565
1663
  })
1664
+ },
1665
+ resumeToolCalls: {
1666
+ target: "CallingTool",
1667
+ actions: assign({
1668
+ step: ({ context, event }) => ({
1669
+ stepNumber: context.checkpoint.stepNumber,
1670
+ inputMessages: context.step.inputMessages ?? [],
1671
+ newMessages: context.step.newMessages,
1672
+ toolCalls: context.step.toolCalls,
1673
+ toolResults: event.partialToolResults,
1674
+ pendingToolCalls: event.pendingToolCalls,
1675
+ usage: context.step.usage,
1676
+ startedAt: context.step.startedAt
1677
+ })
1678
+ })
1679
+ },
1680
+ finishAllToolCalls: {
1681
+ target: "FinishingStep",
1682
+ actions: assign({
1683
+ checkpoint: ({ context, event }) => ({
1684
+ ...context.checkpoint,
1685
+ messages: [...context.checkpoint.messages, ...event.newMessages],
1686
+ pendingToolCalls: void 0,
1687
+ partialToolResults: void 0
1688
+ }),
1689
+ step: ({ context, event }) => ({
1690
+ ...context.step,
1691
+ newMessages: [...context.step.newMessages, ...event.newMessages],
1692
+ toolResults: context.checkpoint.partialToolResults,
1693
+ pendingToolCalls: void 0
1694
+ })
1695
+ })
1566
1696
  }
1567
1697
  }
1568
1698
  },
@@ -1579,13 +1709,13 @@ var runtimeStateMachine = setup({
1579
1709
  step: ({ context, event }) => ({
1580
1710
  ...context.step,
1581
1711
  newMessages: event.newMessages,
1582
- toolCall: event.toolCall,
1583
- toolResult: event.toolResult,
1712
+ toolCalls: event.toolCalls,
1713
+ toolResults: event.toolResults,
1584
1714
  usage: sumUsage(context.step.usage, event.usage)
1585
1715
  })
1586
1716
  })
1587
1717
  },
1588
- callTool: {
1718
+ callTools: {
1589
1719
  target: "CallingTool",
1590
1720
  actions: assign({
1591
1721
  checkpoint: ({ context, event }) => ({
@@ -1597,7 +1727,7 @@ var runtimeStateMachine = setup({
1597
1727
  step: ({ context, event }) => ({
1598
1728
  ...context.step,
1599
1729
  newMessages: [event.newMessage],
1600
- toolCall: event.toolCall,
1730
+ toolCalls: event.toolCalls,
1601
1731
  usage: sumUsage(context.step.usage, event.usage)
1602
1732
  })
1603
1733
  })
@@ -1614,7 +1744,7 @@ var runtimeStateMachine = setup({
1614
1744
  step: ({ context, event }) => ({
1615
1745
  ...context.step,
1616
1746
  newMessages: [event.newMessage],
1617
- toolCall: event.toolCall,
1747
+ toolCalls: [event.toolCall],
1618
1748
  usage: sumUsage(context.step.usage, event.usage)
1619
1749
  })
1620
1750
  })
@@ -1631,7 +1761,7 @@ var runtimeStateMachine = setup({
1631
1761
  step: ({ context, event }) => ({
1632
1762
  ...context.step,
1633
1763
  newMessages: [event.newMessage],
1634
- toolCall: event.toolCall,
1764
+ toolCalls: [event.toolCall],
1635
1765
  usage: sumUsage(context.step.usage, event.usage)
1636
1766
  })
1637
1767
  })
@@ -1640,12 +1770,13 @@ var runtimeStateMachine = setup({
1640
1770
  },
1641
1771
  CallingTool: {
1642
1772
  on: {
1643
- resolveToolResult: {
1773
+ resolveToolResults: {
1644
1774
  target: "ResolvingToolResult",
1645
1775
  actions: assign({
1646
1776
  step: ({ context, event }) => ({
1647
1777
  ...context.step,
1648
- toolResult: event.toolResult
1778
+ toolResults: event.toolResults,
1779
+ pendingToolCalls: void 0
1649
1780
  })
1650
1781
  })
1651
1782
  },
@@ -1654,34 +1785,40 @@ var runtimeStateMachine = setup({
1654
1785
  actions: assign({
1655
1786
  step: ({ context, event }) => ({
1656
1787
  ...context.step,
1657
- toolResult: event.toolResult
1788
+ toolResults: [event.toolResult]
1658
1789
  })
1659
1790
  })
1660
1791
  },
1661
- resolvePdfFile: {
1662
- target: "ResolvingPdfFile",
1792
+ attemptCompletion: {
1793
+ target: "GeneratingRunResult",
1663
1794
  actions: assign({
1664
1795
  step: ({ context, event }) => ({
1665
1796
  ...context.step,
1666
- toolResult: event.toolResult
1797
+ toolResults: [event.toolResult]
1667
1798
  })
1668
1799
  })
1669
1800
  },
1670
- resolveImageFile: {
1671
- target: "ResolvingImageFile",
1801
+ callDelegate: {
1802
+ target: "CallingDelegate",
1672
1803
  actions: assign({
1673
- step: ({ context, event }) => ({
1804
+ step: ({ context }) => ({
1674
1805
  ...context.step,
1675
- toolResult: event.toolResult
1806
+ toolCalls: context.step.toolCalls,
1807
+ toolResults: context.step.toolResults,
1808
+ pendingToolCalls: context.step.pendingToolCalls,
1809
+ partialToolResults: context.step.partialToolResults
1676
1810
  })
1677
1811
  })
1678
1812
  },
1679
- attemptCompletion: {
1680
- target: "GeneratingRunResult",
1813
+ callInteractiveTool: {
1814
+ target: "CallingInteractiveTool",
1681
1815
  actions: assign({
1682
- step: ({ context, event }) => ({
1816
+ step: ({ context }) => ({
1683
1817
  ...context.step,
1684
- toolResult: event.toolResult
1818
+ toolCalls: context.step.toolCalls,
1819
+ toolResults: context.step.toolResults,
1820
+ pendingToolCalls: context.step.pendingToolCalls,
1821
+ partialToolResults: context.step.partialToolResults
1685
1822
  })
1686
1823
  })
1687
1824
  }
@@ -1721,40 +1858,6 @@ var runtimeStateMachine = setup({
1721
1858
  }
1722
1859
  }
1723
1860
  },
1724
- ResolvingPdfFile: {
1725
- on: {
1726
- finishToolCall: {
1727
- target: "FinishingStep",
1728
- actions: assign({
1729
- checkpoint: ({ context, event }) => ({
1730
- ...context.checkpoint,
1731
- messages: [...context.checkpoint.messages, ...event.newMessages]
1732
- }),
1733
- step: ({ context, event }) => ({
1734
- ...context.step,
1735
- newMessages: [...context.step.newMessages, ...event.newMessages]
1736
- })
1737
- })
1738
- }
1739
- }
1740
- },
1741
- ResolvingImageFile: {
1742
- on: {
1743
- finishToolCall: {
1744
- target: "FinishingStep",
1745
- actions: assign({
1746
- checkpoint: ({ context, event }) => ({
1747
- ...context.checkpoint,
1748
- messages: [...context.checkpoint.messages, ...event.newMessages]
1749
- }),
1750
- step: ({ context, event }) => ({
1751
- ...context.step,
1752
- newMessages: [...context.step.newMessages, ...event.newMessages]
1753
- })
1754
- })
1755
- }
1756
- }
1757
- },
1758
1861
  GeneratingRunResult: {
1759
1862
  on: {
1760
1863
  retry: {
@@ -1768,8 +1871,8 @@ var runtimeStateMachine = setup({
1768
1871
  step: ({ context, event }) => ({
1769
1872
  ...context.step,
1770
1873
  newMessages: event.newMessages,
1771
- toolCall: event.toolCall,
1772
- toolResult: event.toolResult,
1874
+ toolCalls: event.toolCalls,
1875
+ toolResults: event.toolResults,
1773
1876
  usage: sumUsage(context.step.usage, event.usage)
1774
1877
  })
1775
1878
  })
@@ -1851,8 +1954,6 @@ var StateMachineLogics = {
1851
1954
  CallingTool: callingToolLogic,
1852
1955
  ResolvingToolResult: resolvingToolResultLogic,
1853
1956
  ResolvingThought: resolvingThoughtLogic,
1854
- ResolvingPdfFile: resolvingPdfFileLogic,
1855
- ResolvingImageFile: resolvingImageFileLogic,
1856
1957
  GeneratingRunResult: generatingRunResultLogic,
1857
1958
  CallingInteractiveTool: callingInteractiveToolLogic,
1858
1959
  CallingDelegate: callingDelegateLogic,