@perstack/runtime 0.0.61 → 0.0.63

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
@@ -1,3 +1,7 @@
1
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from 'fs';
2
+ import { readFile, mkdir, writeFile } from 'fs/promises';
3
+ import path2 from 'path';
4
+ import { jobSchema, runSettingSchema, checkpointSchema, knownModels, stopRunByDelegate, stopRunByInteractiveTool, resolveToolResults, attemptCompletion, callDelegate, callInteractiveTool, stopRunByExceededMaxSteps, continueToNextStep, retry, completeRun, callTools, startRun, resumeToolCalls, finishAllToolCalls, startGeneration, finishToolCall, runParamsSchema, createRuntimeEvent } from '@perstack/core';
1
5
  import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock';
2
6
  import { createAnthropic } from '@ai-sdk/anthropic';
3
7
  import { createAzure } from '@ai-sdk/azure';
@@ -5,11 +9,8 @@ import { createDeepSeek } from '@ai-sdk/deepseek';
5
9
  import { createGoogleGenerativeAI } from '@ai-sdk/google';
6
10
  import { createVertex } from '@ai-sdk/google-vertex';
7
11
  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';
9
12
  import { createOllama } from 'ollama-ai-provider-v2';
10
13
  import { createId } from '@paralleldrive/cuid2';
11
- import { readFile, readdir, mkdir, writeFile } from 'fs/promises';
12
- import path from 'path';
13
14
  import { setup, assign, createActor } from 'xstate';
14
15
  import { generateText, tool, jsonSchema } from 'ai';
15
16
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
@@ -21,7 +22,208 @@ import { ApiV1Client } from '@perstack/api-client/v1';
21
22
 
22
23
  // package.json
23
24
  var package_default = {
24
- version: "0.0.61"};
25
+ version: "0.0.63"};
26
+ function getJobsDir() {
27
+ return `${process.cwd()}/perstack/jobs`;
28
+ }
29
+ function getJobDir(jobId) {
30
+ return `${getJobsDir()}/${jobId}`;
31
+ }
32
+ function storeJob(job) {
33
+ const jobDir = getJobDir(job.id);
34
+ if (!existsSync(jobDir)) {
35
+ mkdirSync(jobDir, { recursive: true });
36
+ }
37
+ const jobPath = path2.resolve(jobDir, "job.json");
38
+ writeFileSync(jobPath, JSON.stringify(job, null, 2));
39
+ }
40
+ function retrieveJob(jobId) {
41
+ const jobDir = getJobDir(jobId);
42
+ const jobPath = path2.resolve(jobDir, "job.json");
43
+ if (!existsSync(jobPath)) {
44
+ return void 0;
45
+ }
46
+ const content = readFileSync(jobPath, "utf-8");
47
+ return jobSchema.parse(JSON.parse(content));
48
+ }
49
+ function getAllJobs() {
50
+ const jobsDir = getJobsDir();
51
+ if (!existsSync(jobsDir)) {
52
+ return [];
53
+ }
54
+ const jobDirNames = readdirSync(jobsDir, { withFileTypes: true }).filter((dir) => dir.isDirectory()).map((dir) => dir.name);
55
+ if (jobDirNames.length === 0) {
56
+ return [];
57
+ }
58
+ const jobs = [];
59
+ for (const jobDirName of jobDirNames) {
60
+ const jobPath = path2.resolve(jobsDir, jobDirName, "job.json");
61
+ if (!existsSync(jobPath)) {
62
+ continue;
63
+ }
64
+ try {
65
+ const content = readFileSync(jobPath, "utf-8");
66
+ jobs.push(jobSchema.parse(JSON.parse(content)));
67
+ } catch {
68
+ }
69
+ }
70
+ return jobs.sort((a, b) => b.startedAt - a.startedAt);
71
+ }
72
+ function createInitialJob(jobId, expertKey, maxSteps) {
73
+ return {
74
+ id: jobId,
75
+ status: "running",
76
+ coordinatorExpertKey: expertKey,
77
+ totalSteps: 0,
78
+ maxSteps,
79
+ usage: {
80
+ inputTokens: 0,
81
+ outputTokens: 0,
82
+ reasoningTokens: 0,
83
+ totalTokens: 0,
84
+ cachedInputTokens: 0
85
+ },
86
+ startedAt: Date.now()
87
+ };
88
+ }
89
+ async function createDefaultFileSystem() {
90
+ const fs = await import('fs');
91
+ const fsPromises = await import('fs/promises');
92
+ return {
93
+ existsSync: fs.existsSync,
94
+ mkdir: async (p, options) => {
95
+ await fsPromises.mkdir(p, options);
96
+ },
97
+ readFile: fsPromises.readFile,
98
+ writeFile: fsPromises.writeFile
99
+ };
100
+ }
101
+ function defaultGetRunDir(jobId, runId) {
102
+ return `${process.cwd()}/perstack/jobs/${jobId}/runs/${runId}`;
103
+ }
104
+ async function storeRunSetting(setting, fs, getRunDir = defaultGetRunDir) {
105
+ const fileSystem = fs ?? await createDefaultFileSystem();
106
+ const runDir = getRunDir(setting.jobId, setting.runId);
107
+ if (fileSystem.existsSync(runDir)) {
108
+ const runSettingPath = path2.resolve(runDir, "run-setting.json");
109
+ const runSetting = runSettingSchema.parse(
110
+ JSON.parse(await fileSystem.readFile(runSettingPath, "utf-8"))
111
+ );
112
+ runSetting.updatedAt = Date.now();
113
+ await fileSystem.writeFile(runSettingPath, JSON.stringify(runSetting), "utf-8");
114
+ } else {
115
+ await fileSystem.mkdir(runDir, { recursive: true });
116
+ await fileSystem.writeFile(
117
+ path2.resolve(runDir, "run-setting.json"),
118
+ JSON.stringify(setting),
119
+ "utf-8"
120
+ );
121
+ }
122
+ }
123
+ function getAllRuns() {
124
+ const jobsDir = getJobsDir();
125
+ if (!existsSync(jobsDir)) {
126
+ return [];
127
+ }
128
+ const jobDirNames = readdirSync(jobsDir, { withFileTypes: true }).filter((dir) => dir.isDirectory()).map((dir) => dir.name);
129
+ if (jobDirNames.length === 0) {
130
+ return [];
131
+ }
132
+ const runs = [];
133
+ for (const jobDirName of jobDirNames) {
134
+ const runsDir = path2.resolve(jobsDir, jobDirName, "runs");
135
+ if (!existsSync(runsDir)) {
136
+ continue;
137
+ }
138
+ const runDirNames = readdirSync(runsDir, { withFileTypes: true }).filter((dir) => dir.isDirectory()).map((dir) => dir.name);
139
+ for (const runDirName of runDirNames) {
140
+ const runSettingPath = path2.resolve(runsDir, runDirName, "run-setting.json");
141
+ if (!existsSync(runSettingPath)) {
142
+ continue;
143
+ }
144
+ try {
145
+ const content = readFileSync(runSettingPath, "utf-8");
146
+ runs.push(runSettingSchema.parse(JSON.parse(content)));
147
+ } catch {
148
+ }
149
+ }
150
+ }
151
+ return runs.sort((a, b) => b.updatedAt - a.updatedAt);
152
+ }
153
+
154
+ // src/default-store.ts
155
+ function getCheckpointDir(jobId) {
156
+ return `${getJobDir(jobId)}/checkpoints`;
157
+ }
158
+ function getCheckpointPath(jobId, checkpointId) {
159
+ return `${getCheckpointDir(jobId)}/${checkpointId}.json`;
160
+ }
161
+ async function defaultRetrieveCheckpoint(jobId, checkpointId) {
162
+ const checkpointPath = getCheckpointPath(jobId, checkpointId);
163
+ if (!existsSync(checkpointPath)) {
164
+ throw new Error(`checkpoint not found: ${checkpointId}`);
165
+ }
166
+ const checkpoint = await readFile(checkpointPath, "utf8");
167
+ return checkpointSchema.parse(JSON.parse(checkpoint));
168
+ }
169
+ async function defaultStoreCheckpoint(checkpoint) {
170
+ const { id, jobId } = checkpoint;
171
+ const checkpointDir = getCheckpointDir(jobId);
172
+ await mkdir(checkpointDir, { recursive: true });
173
+ await writeFile(getCheckpointPath(jobId, id), JSON.stringify(checkpoint));
174
+ }
175
+ async function defaultStoreEvent(event) {
176
+ const { timestamp, jobId, runId, stepNumber, type } = event;
177
+ const runDir = defaultGetRunDir(jobId, runId);
178
+ const eventPath = `${runDir}/event-${timestamp}-${stepNumber}-${type}.json`;
179
+ await mkdir(runDir, { recursive: true });
180
+ await writeFile(eventPath, JSON.stringify(event));
181
+ }
182
+ function getCheckpointsByJobId(jobId) {
183
+ const checkpointDir = getCheckpointDir(jobId);
184
+ if (!existsSync(checkpointDir)) {
185
+ return [];
186
+ }
187
+ const files = readdirSync(checkpointDir).filter((file) => file.endsWith(".json"));
188
+ const checkpoints = [];
189
+ for (const file of files) {
190
+ try {
191
+ const content = readFileSync(path2.resolve(checkpointDir, file), "utf-8");
192
+ checkpoints.push(checkpointSchema.parse(JSON.parse(content)));
193
+ } catch {
194
+ }
195
+ }
196
+ return checkpoints.sort((a, b) => a.stepNumber - b.stepNumber);
197
+ }
198
+ function getEventsByRun(jobId, runId) {
199
+ const runDir = defaultGetRunDir(jobId, runId);
200
+ if (!existsSync(runDir)) {
201
+ return [];
202
+ }
203
+ return readdirSync(runDir).filter((file) => file.startsWith("event-")).map((file) => {
204
+ const [_, timestamp, stepNumber, type] = file.split(".")[0].split("-");
205
+ return { timestamp: Number(timestamp), stepNumber: Number(stepNumber), type };
206
+ }).sort((a, b) => a.stepNumber - b.stepNumber);
207
+ }
208
+ function getEventContents(jobId, runId, maxStepNumber) {
209
+ const runDir = defaultGetRunDir(jobId, runId);
210
+ if (!existsSync(runDir)) {
211
+ return [];
212
+ }
213
+ const eventFiles = readdirSync(runDir).filter((file) => file.startsWith("event-")).map((file) => {
214
+ const [_, timestamp, step, type] = file.split(".")[0].split("-");
215
+ return { file, timestamp: Number(timestamp), stepNumber: Number(step), type };
216
+ }).filter((e) => maxStepNumber === void 0 || e.stepNumber <= maxStepNumber).sort((a, b) => a.timestamp - b.timestamp);
217
+ const events = [];
218
+ for (const { file } of eventFiles) {
219
+ try {
220
+ const content = readFileSync(path2.resolve(runDir, file), "utf-8");
221
+ events.push(JSON.parse(content));
222
+ } catch {
223
+ }
224
+ }
225
+ return events;
226
+ }
25
227
  function getModel(modelId, providerConfig) {
26
228
  switch (providerConfig.providerName) {
27
229
  case "anthropic": {
@@ -142,6 +344,7 @@ function sumUsage(a, b) {
142
344
  function createInitialCheckpoint(checkpointId, params) {
143
345
  return {
144
346
  id: checkpointId,
347
+ jobId: params.jobId,
145
348
  runId: params.runId,
146
349
  expert: {
147
350
  key: params.expertKey,
@@ -185,6 +388,7 @@ function buildDelegationReturnState(currentSetting, resultCheckpoint, parentChec
185
388
  interactiveToolCallResult: {
186
389
  toolCallId,
187
390
  toolName,
391
+ skillName: `delegate/${resultCheckpoint.expert.key}`,
188
392
  text: delegateText.text
189
393
  }
190
394
  }
@@ -192,16 +396,19 @@ function buildDelegationReturnState(currentSetting, resultCheckpoint, parentChec
192
396
  checkpoint: {
193
397
  ...parentCheckpoint,
194
398
  stepNumber: resultCheckpoint.stepNumber,
195
- usage: resultCheckpoint.usage
399
+ usage: resultCheckpoint.usage,
400
+ pendingToolCalls: parentCheckpoint.pendingToolCalls,
401
+ partialToolResults: parentCheckpoint.partialToolResults
196
402
  }
197
403
  };
198
404
  }
199
405
  function buildDelegateToState(currentSetting, resultCheckpoint, currentExpert) {
200
406
  const { delegateTo } = resultCheckpoint;
201
- if (!delegateTo) {
407
+ if (!delegateTo || delegateTo.length === 0) {
202
408
  throw new Error("delegateTo is required for buildDelegateToState");
203
409
  }
204
- const { expert, toolCallId, toolName, query } = delegateTo;
410
+ const firstDelegation = delegateTo[0];
411
+ const { expert, toolCallId, toolName, query } = firstDelegation;
205
412
  return {
206
413
  setting: {
207
414
  ...currentSetting,
@@ -229,73 +436,12 @@ function buildDelegateToState(currentSetting, resultCheckpoint, currentExpert) {
229
436
  toolName,
230
437
  checkpointId: resultCheckpoint.id
231
438
  },
232
- usage: resultCheckpoint.usage
439
+ usage: resultCheckpoint.usage,
440
+ pendingToolCalls: void 0,
441
+ partialToolResults: void 0
233
442
  }
234
443
  };
235
444
  }
236
- async function createDefaultFileSystem() {
237
- const fs = await import('fs');
238
- const fsPromises = await import('fs/promises');
239
- return {
240
- existsSync: fs.existsSync,
241
- mkdir: async (p, options) => {
242
- await fsPromises.mkdir(p, options);
243
- },
244
- readFile: fsPromises.readFile,
245
- writeFile: fsPromises.writeFile
246
- };
247
- }
248
- function defaultGetRunDir(runId) {
249
- return `${process.cwd()}/perstack/runs/${runId}`;
250
- }
251
- async function storeRunSetting(setting, fs, getRunDir = defaultGetRunDir) {
252
- const fileSystem = fs ?? await createDefaultFileSystem();
253
- const runDir = getRunDir(setting.runId);
254
- if (fileSystem.existsSync(runDir)) {
255
- const runSettingPath = path.resolve(runDir, "run-setting.json");
256
- const runSetting = runSettingSchema.parse(
257
- JSON.parse(await fileSystem.readFile(runSettingPath, "utf-8"))
258
- );
259
- runSetting.updatedAt = Date.now();
260
- await fileSystem.writeFile(runSettingPath, JSON.stringify(runSetting), "utf-8");
261
- } else {
262
- await fileSystem.mkdir(runDir, { recursive: true });
263
- await fileSystem.writeFile(
264
- path.resolve(runDir, "run-setting.json"),
265
- JSON.stringify(setting),
266
- "utf-8"
267
- );
268
- }
269
- }
270
-
271
- // src/default-store.ts
272
- async function defaultRetrieveCheckpoint(runId, checkpointId) {
273
- const runDir = defaultGetRunDir(runId);
274
- const checkpointFiles = await readdir(runDir, { withFileTypes: true }).then(
275
- (files) => files.filter((file) => file.isFile() && file.name.startsWith("checkpoint-"))
276
- );
277
- const checkpointFile = checkpointFiles.find((file) => file.name.endsWith(`-${checkpointId}.json`));
278
- if (!checkpointFile) {
279
- throw new Error(`checkpoint not found: ${runId} ${checkpointId}`);
280
- }
281
- const checkpointPath = `${runDir}/${checkpointFile.name}`;
282
- const checkpoint = await readFile(checkpointPath, "utf8");
283
- return checkpointSchema.parse(JSON.parse(checkpoint));
284
- }
285
- async function defaultStoreCheckpoint(checkpoint, timestamp) {
286
- const { id, runId, stepNumber } = checkpoint;
287
- const runDir = defaultGetRunDir(runId);
288
- const checkpointPath = `${runDir}/checkpoint-${timestamp}-${stepNumber}-${id}.json`;
289
- await mkdir(runDir, { recursive: true });
290
- await writeFile(checkpointPath, JSON.stringify(checkpoint));
291
- }
292
- async function defaultStoreEvent(event) {
293
- const { timestamp, runId, stepNumber, type } = event;
294
- const runDir = defaultGetRunDir(runId);
295
- const eventPath = `${runDir}/event-${timestamp}-${stepNumber}-${type}.json`;
296
- await mkdir(runDir, { recursive: true });
297
- await writeFile(eventPath, JSON.stringify(event));
298
- }
299
445
  var RunEventEmitter = class {
300
446
  listeners = [];
301
447
  subscribe(listener) {
@@ -328,9 +474,11 @@ var BaseSkillManager = class {
328
474
  skill;
329
475
  interactiveSkill;
330
476
  expert;
477
+ _jobId;
331
478
  _runId;
332
479
  _eventListener;
333
- constructor(runId, eventListener) {
480
+ constructor(jobId, runId, eventListener) {
481
+ this._jobId = jobId;
334
482
  this._runId = runId;
335
483
  this._eventListener = eventListener;
336
484
  }
@@ -381,8 +529,8 @@ var DelegateSkillManager = class extends BaseSkillManager {
381
529
  type = "delegate";
382
530
  lazyInit = false;
383
531
  expert;
384
- constructor(expert, runId, eventListener) {
385
- super(runId, eventListener);
532
+ constructor(expert, jobId, runId, eventListener) {
533
+ super(jobId, runId, eventListener);
386
534
  this.name = expert.name;
387
535
  this.expert = expert;
388
536
  }
@@ -414,8 +562,8 @@ var InteractiveSkillManager = class extends BaseSkillManager {
414
562
  type = "interactive";
415
563
  lazyInit = false;
416
564
  interactiveSkill;
417
- constructor(interactiveSkill, runId, eventListener) {
418
- super(runId, eventListener);
565
+ constructor(interactiveSkill, jobId, runId, eventListener) {
566
+ super(jobId, runId, eventListener);
419
567
  this.name = interactiveSkill.name;
420
568
  this.interactiveSkill = interactiveSkill;
421
569
  }
@@ -441,8 +589,8 @@ var McpSkillManager = class extends BaseSkillManager {
441
589
  skill;
442
590
  _mcpClient;
443
591
  _env;
444
- constructor(skill, env, runId, eventListener) {
445
- super(runId, eventListener);
592
+ constructor(skill, env, jobId, runId, eventListener) {
593
+ super(jobId, runId, eventListener);
446
594
  this.name = skill.name;
447
595
  this.skill = skill;
448
596
  this._env = env;
@@ -481,7 +629,7 @@ var McpSkillManager = class extends BaseSkillManager {
481
629
  const startTime = Date.now();
482
630
  const { command, args } = this._getCommandArgs(skill);
483
631
  if (this._eventListener) {
484
- const event = createRuntimeEvent("skillStarting", this._runId, {
632
+ const event = createRuntimeEvent("skillStarting", this._jobId, this._runId, {
485
633
  skillName: skill.name,
486
634
  command,
487
635
  args
@@ -492,7 +640,7 @@ var McpSkillManager = class extends BaseSkillManager {
492
640
  if (transport.stderr) {
493
641
  transport.stderr.on("data", (chunk) => {
494
642
  if (this._eventListener) {
495
- const event = createRuntimeEvent("skillStderr", this._runId, {
643
+ const event = createRuntimeEvent("skillStderr", this._jobId, this._runId, {
496
644
  skillName: skill.name,
497
645
  message: chunk.toString().trim()
498
646
  });
@@ -505,7 +653,7 @@ var McpSkillManager = class extends BaseSkillManager {
505
653
  const connectTime = Date.now();
506
654
  if (this._eventListener) {
507
655
  const serverInfo = this._mcpClient.getServerVersion();
508
- const event = createRuntimeEvent("skillConnected", this._runId, {
656
+ const event = createRuntimeEvent("skillConnected", this._jobId, this._runId, {
509
657
  skillName: skill.name,
510
658
  serverInfo: serverInfo ? { name: serverInfo.name, version: serverInfo.version } : void 0,
511
659
  connectDurationMs: connectTime - connectStartTime,
@@ -541,7 +689,7 @@ var McpSkillManager = class extends BaseSkillManager {
541
689
  if (this._mcpClient) {
542
690
  await this._mcpClient.close();
543
691
  if (this._eventListener && this.skill) {
544
- const event = createRuntimeEvent("skillDisconnected", this._runId, {
692
+ const event = createRuntimeEvent("skillDisconnected", this._jobId, this._runId, {
545
693
  skillName: this.skill.name
546
694
  });
547
695
  this._eventListener(event);
@@ -644,8 +792,8 @@ async function initSkillManagersWithCleanup(managers, allManagers) {
644
792
  throw firstRejected.reason;
645
793
  }
646
794
  }
647
- async function getSkillManagers(expert, experts, setting, eventListener) {
648
- const { perstackBaseSkillCommand, env, runId } = setting;
795
+ async function getSkillManagers(expert, experts, setting, eventListener, options) {
796
+ const { perstackBaseSkillCommand, env, jobId, runId } = setting;
649
797
  const { skills } = expert;
650
798
  if (!skills["@perstack/base"]) {
651
799
  throw new Error("Base skill is not defined");
@@ -672,20 +820,22 @@ async function getSkillManagers(expert, experts, setting, eventListener) {
672
820
  return skill;
673
821
  });
674
822
  const mcpSkillManagers = mcpSkills.map((skill) => {
675
- const manager = new McpSkillManager(skill, env, runId, eventListener);
823
+ const manager = new McpSkillManager(skill, env, jobId, runId, eventListener);
676
824
  allManagers.push(manager);
677
825
  return manager;
678
826
  });
679
827
  await initSkillManagersWithCleanup(mcpSkillManagers, allManagers);
680
- const interactiveSkills = Object.values(skills).filter(
681
- (skill) => skill.type === "interactiveSkill"
682
- );
683
- const interactiveSkillManagers = interactiveSkills.map((interactiveSkill) => {
684
- const manager = new InteractiveSkillManager(interactiveSkill, runId, eventListener);
685
- allManagers.push(manager);
686
- return manager;
687
- });
688
- await initSkillManagersWithCleanup(interactiveSkillManagers, allManagers);
828
+ if (!options?.isDelegatedRun) {
829
+ const interactiveSkills = Object.values(skills).filter(
830
+ (skill) => skill.type === "interactiveSkill"
831
+ );
832
+ const interactiveSkillManagers = interactiveSkills.map((interactiveSkill) => {
833
+ const manager = new InteractiveSkillManager(interactiveSkill, jobId, runId, eventListener);
834
+ allManagers.push(manager);
835
+ return manager;
836
+ });
837
+ await initSkillManagersWithCleanup(interactiveSkillManagers, allManagers);
838
+ }
689
839
  const delegateSkillManagers = [];
690
840
  for (const delegateExpertName of expert.delegates) {
691
841
  const delegate = experts[delegateExpertName];
@@ -694,7 +844,7 @@ async function getSkillManagers(expert, experts, setting, eventListener) {
694
844
  })));
695
845
  throw new Error(`Delegate expert "${delegateExpertName}" not found in experts`);
696
846
  }
697
- const manager = new DelegateSkillManager(delegate, runId, eventListener);
847
+ const manager = new DelegateSkillManager(delegate, jobId, runId, eventListener);
698
848
  allManagers.push(manager);
699
849
  delegateSkillManagers.push(manager);
700
850
  }
@@ -735,37 +885,58 @@ async function getToolSet(skillManagers) {
735
885
  }
736
886
 
737
887
  // src/states/calling-delegate.ts
888
+ async function getToolType(toolName, skillManagers) {
889
+ const skillManager = await getSkillManagerByToolName(skillManagers, toolName);
890
+ return skillManager.type;
891
+ }
738
892
  async function callingDelegateLogic({
739
893
  setting,
740
894
  checkpoint,
741
895
  step,
742
896
  skillManagers
743
897
  }) {
744
- if (!step.toolCall) {
745
- throw new Error("No tool call found");
746
- }
747
- const { id, toolName, args } = step.toolCall;
748
- const skillManager = await getSkillManagerByToolName(skillManagers, toolName);
749
- if (!skillManager.expert) {
750
- throw new Error(`Delegation error: skill manager "${toolName}" not found`);
751
- }
752
- if (!args || !args.query || typeof args.query !== "string") {
753
- throw new Error("Delegation error: query is undefined");
754
- }
755
- return stopRunByDelegate(setting, checkpoint, {
756
- checkpoint: {
757
- ...checkpoint,
758
- status: "stoppedByDelegate",
759
- delegateTo: {
898
+ if (!step.pendingToolCalls || step.pendingToolCalls.length === 0) {
899
+ throw new Error("No pending tool calls found");
900
+ }
901
+ const toolCallTypes = await Promise.all(
902
+ step.pendingToolCalls.map(async (tc) => ({
903
+ toolCall: tc,
904
+ type: await getToolType(tc.toolName, skillManagers)
905
+ }))
906
+ );
907
+ const delegateToolCalls = toolCallTypes.filter((t) => t.type === "delegate").map((t) => t.toolCall);
908
+ const nonDelegateToolCalls = toolCallTypes.filter((t) => t.type !== "delegate").map((t) => t.toolCall);
909
+ if (delegateToolCalls.length === 0) {
910
+ throw new Error("No delegate tool calls found");
911
+ }
912
+ const delegations = await Promise.all(
913
+ delegateToolCalls.map(async (tc) => {
914
+ const skillManager = await getSkillManagerByToolName(skillManagers, tc.toolName);
915
+ if (!skillManager.expert) {
916
+ throw new Error(`Delegation error: skill manager "${tc.toolName}" not found`);
917
+ }
918
+ if (!tc.args || !tc.args.query || typeof tc.args.query !== "string") {
919
+ throw new Error(`Delegation error: query is undefined for ${tc.toolName}`);
920
+ }
921
+ return {
760
922
  expert: {
761
923
  key: skillManager.expert.key,
762
924
  name: skillManager.expert.name,
763
925
  version: skillManager.expert.version
764
926
  },
765
- toolCallId: id,
766
- toolName,
767
- query: args.query
768
- }
927
+ toolCallId: tc.id,
928
+ toolName: tc.toolName,
929
+ query: tc.args.query
930
+ };
931
+ })
932
+ );
933
+ return stopRunByDelegate(setting, checkpoint, {
934
+ checkpoint: {
935
+ ...checkpoint,
936
+ status: "stoppedByDelegate",
937
+ delegateTo: delegations,
938
+ pendingToolCalls: nonDelegateToolCalls.length > 0 ? nonDelegateToolCalls : void 0,
939
+ partialToolResults: step.partialToolResults
769
940
  },
770
941
  step: {
771
942
  ...step,
@@ -778,10 +949,17 @@ async function callingInteractiveToolLogic({
778
949
  checkpoint,
779
950
  step
780
951
  }) {
952
+ if (!step.pendingToolCalls || step.pendingToolCalls.length === 0) {
953
+ throw new Error("No pending tool calls found");
954
+ }
955
+ const currentToolCall = step.pendingToolCalls[0];
956
+ const remainingToolCalls = step.pendingToolCalls.slice(1);
781
957
  return stopRunByInteractiveTool(setting, checkpoint, {
782
958
  checkpoint: {
783
959
  ...checkpoint,
784
- status: "stoppedByInteractiveTool"
960
+ status: "stoppedByInteractiveTool",
961
+ pendingToolCalls: [currentToolCall, ...remainingToolCalls],
962
+ partialToolResults: step.partialToolResults
785
963
  },
786
964
  step: {
787
965
  ...step,
@@ -789,37 +967,150 @@ async function callingInteractiveToolLogic({
789
967
  }
790
968
  });
791
969
  }
970
+ function hasRemainingTodos(toolResult) {
971
+ const firstPart = toolResult.result[0];
972
+ if (!firstPart || firstPart.type !== "textPart") {
973
+ return false;
974
+ }
975
+ try {
976
+ const parsed = JSON.parse(firstPart.text);
977
+ return Array.isArray(parsed.remainingTodos) && parsed.remainingTodos.length > 0;
978
+ } catch {
979
+ return false;
980
+ }
981
+ }
982
+ function isFileInfo(value) {
983
+ 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";
984
+ }
985
+ async function processFileToolResult(toolResult, toolName) {
986
+ const processedContents = [];
987
+ for (const part of toolResult.result) {
988
+ if (part.type !== "textPart") {
989
+ processedContents.push(part);
990
+ continue;
991
+ }
992
+ let fileInfo;
993
+ try {
994
+ const parsed = JSON.parse(part.text);
995
+ if (isFileInfo(parsed)) {
996
+ fileInfo = parsed;
997
+ }
998
+ } catch {
999
+ processedContents.push(part);
1000
+ continue;
1001
+ }
1002
+ if (!fileInfo) {
1003
+ processedContents.push(part);
1004
+ continue;
1005
+ }
1006
+ const { path: path4, mimeType } = fileInfo;
1007
+ try {
1008
+ const buffer = await readFile(path4);
1009
+ if (toolName === "readImageFile") {
1010
+ processedContents.push({
1011
+ type: "imageInlinePart",
1012
+ id: part.id,
1013
+ encodedData: buffer.toString("base64"),
1014
+ mimeType
1015
+ });
1016
+ } else {
1017
+ processedContents.push({
1018
+ type: "fileInlinePart",
1019
+ id: part.id,
1020
+ encodedData: buffer.toString("base64"),
1021
+ mimeType
1022
+ });
1023
+ }
1024
+ } catch (error) {
1025
+ processedContents.push({
1026
+ type: "textPart",
1027
+ id: part.id,
1028
+ text: `Failed to read file "${path4}": ${error instanceof Error ? error.message : String(error)}`
1029
+ });
1030
+ }
1031
+ }
1032
+ return { ...toolResult, result: processedContents };
1033
+ }
1034
+ async function executeMcpToolCall(toolCall, skillManagers) {
1035
+ const skillManager = await getSkillManagerByToolName(skillManagers, toolCall.toolName);
1036
+ if (skillManager.type !== "mcp") {
1037
+ throw new Error(`Incorrect SkillType, required MCP, got ${skillManager.type}`);
1038
+ }
1039
+ const result = await skillManager.callTool(toolCall.toolName, toolCall.args);
1040
+ const toolResult = {
1041
+ id: toolCall.id,
1042
+ skillName: toolCall.skillName,
1043
+ toolName: toolCall.toolName,
1044
+ result
1045
+ };
1046
+ if (toolCall.toolName === "readPdfFile" || toolCall.toolName === "readImageFile") {
1047
+ return processFileToolResult(toolResult, toolCall.toolName);
1048
+ }
1049
+ return toolResult;
1050
+ }
1051
+ async function getToolType2(toolCall, skillManagers) {
1052
+ const skillManager = await getSkillManagerByToolName(skillManagers, toolCall.toolName);
1053
+ return skillManager.type;
1054
+ }
792
1055
  async function callingToolLogic({
793
1056
  setting,
794
1057
  checkpoint,
795
1058
  step,
796
1059
  skillManagers
797
1060
  }) {
798
- if (!step.toolCall) {
799
- throw new Error("No tool call found");
1061
+ const pendingToolCalls = step.pendingToolCalls ?? step.toolCalls ?? [];
1062
+ if (pendingToolCalls.length === 0) {
1063
+ throw new Error("No tool calls found");
800
1064
  }
801
- const { id, skillName, toolName, args } = step.toolCall;
802
- const skillManager = await getSkillManagerByToolName(skillManagers, toolName);
803
- if (skillManager.type !== "mcp") {
804
- throw new Error(`Incorrect SkillType, required MCP, got ${skillManager.type}`);
1065
+ const toolResults = step.toolResults ? [...step.toolResults] : [];
1066
+ const attemptCompletionTool = pendingToolCalls.find(
1067
+ (tc) => tc.skillName === "@perstack/base" && tc.toolName === "attemptCompletion"
1068
+ );
1069
+ if (attemptCompletionTool) {
1070
+ const toolResult = await executeMcpToolCall(attemptCompletionTool, skillManagers);
1071
+ if (hasRemainingTodos(toolResult)) {
1072
+ return resolveToolResults(setting, checkpoint, { toolResults: [toolResult] });
1073
+ }
1074
+ return attemptCompletion(setting, checkpoint, { toolResult });
1075
+ }
1076
+ const toolCallTypes = await Promise.all(
1077
+ pendingToolCalls.map(async (tc) => ({
1078
+ toolCall: tc,
1079
+ type: await getToolType2(tc, skillManagers)
1080
+ }))
1081
+ );
1082
+ const mcpToolCalls = toolCallTypes.filter((t) => t.type === "mcp").map((t) => t.toolCall);
1083
+ const delegateToolCalls = toolCallTypes.filter((t) => t.type === "delegate").map((t) => t.toolCall);
1084
+ const interactiveToolCalls = toolCallTypes.filter((t) => t.type === "interactive").map((t) => t.toolCall);
1085
+ if (mcpToolCalls.length > 0) {
1086
+ const mcpResults = await Promise.all(
1087
+ mcpToolCalls.map((tc) => executeMcpToolCall(tc, skillManagers))
1088
+ );
1089
+ toolResults.push(...mcpResults);
1090
+ }
1091
+ if (delegateToolCalls.length > 0) {
1092
+ step.partialToolResults = toolResults;
1093
+ step.pendingToolCalls = [...delegateToolCalls, ...interactiveToolCalls];
1094
+ return callDelegate(setting, checkpoint, {
1095
+ newMessage: checkpoint.messages[checkpoint.messages.length - 1],
1096
+ toolCalls: delegateToolCalls,
1097
+ usage: step.usage
1098
+ });
805
1099
  }
806
- const result = await skillManager.callTool(toolName, args);
807
- const toolResult = { id, skillName, toolName, result };
808
- if (skillName === "@perstack/base") {
809
- if (toolName === "think") {
810
- return resolveThought(setting, checkpoint, { toolResult });
811
- }
812
- if (toolName === "attemptCompletion") {
813
- return attemptCompletion(setting, checkpoint, { toolResult });
814
- }
815
- if (toolName === "readPdfFile") {
816
- return resolvePdfFile(setting, checkpoint, { toolResult });
817
- }
818
- if (toolName === "readImageFile") {
819
- return resolveImageFile(setting, checkpoint, { toolResult });
820
- }
1100
+ if (interactiveToolCalls.length > 0) {
1101
+ const interactiveToolCall = interactiveToolCalls[0];
1102
+ if (!interactiveToolCall) {
1103
+ throw new Error("No interactive tool call found");
1104
+ }
1105
+ step.partialToolResults = toolResults;
1106
+ step.pendingToolCalls = interactiveToolCalls;
1107
+ return callInteractiveTool(setting, checkpoint, {
1108
+ newMessage: checkpoint.messages[checkpoint.messages.length - 1],
1109
+ toolCall: interactiveToolCall,
1110
+ usage: step.usage
1111
+ });
821
1112
  }
822
- return resolveToolResult(setting, checkpoint, { toolResult });
1113
+ return resolveToolResults(setting, checkpoint, { toolResults });
823
1114
  }
824
1115
  async function finishingStepLogic({
825
1116
  setting,
@@ -1035,9 +1326,12 @@ function toolResultPartToCoreToolResultPart(part) {
1035
1326
  output: { type: "text", value: contents[0].text }
1036
1327
  };
1037
1328
  }
1038
- const contentValue = contents.map(
1039
- (content) => content.type === "textPart" ? { type: "text", text: content.text } : { type: "media", data: content.encodedData, mediaType: content.mimeType }
1040
- );
1329
+ const contentValue = contents.map((content) => {
1330
+ if (content.type === "textPart") {
1331
+ return { type: "text", text: content.text };
1332
+ }
1333
+ return { type: "media", data: content.encodedData, mediaType: content.mimeType };
1334
+ });
1041
1335
  return {
1042
1336
  type: "tool-result",
1043
1337
  toolCallId: part.toolCallId,
@@ -1052,21 +1346,21 @@ async function generatingRunResultLogic({
1052
1346
  checkpoint,
1053
1347
  step
1054
1348
  }) {
1055
- if (!step.toolCall || !step.toolResult) {
1056
- throw new Error("No tool call or tool result found");
1349
+ if (!step.toolCalls || !step.toolResults || step.toolResults.length === 0) {
1350
+ throw new Error("No tool calls or tool results found");
1057
1351
  }
1058
- const { id, toolName } = step.toolCall;
1059
- const { result } = step.toolResult;
1060
- const toolMessage = createToolMessage([
1061
- {
1352
+ const toolResultParts = step.toolResults.map((toolResult) => {
1353
+ const toolCall = step.toolCalls?.find((tc) => tc.id === toolResult.id);
1354
+ return {
1062
1355
  type: "toolResultPart",
1063
- toolCallId: id,
1064
- toolName,
1065
- contents: result.filter(
1066
- (part) => part.type === "textPart" || part.type === "imageInlinePart"
1356
+ toolCallId: toolResult.id,
1357
+ toolName: toolCall?.toolName ?? toolResult.toolName,
1358
+ contents: toolResult.result.filter(
1359
+ (part) => part.type === "textPart" || part.type === "imageInlinePart" || part.type === "fileInlinePart"
1067
1360
  )
1068
- }
1069
- ]);
1361
+ };
1362
+ });
1363
+ const toolMessage = createToolMessage(toolResultParts);
1070
1364
  const model = getModel(setting.model, setting.providerConfig);
1071
1365
  const { messages } = checkpoint;
1072
1366
  let generationResult;
@@ -1103,13 +1397,48 @@ async function generatingRunResultLogic({
1103
1397
  step: {
1104
1398
  ...step,
1105
1399
  newMessages: [...step.newMessages, ...newMessages],
1106
- finishedAt: (/* @__PURE__ */ new Date()).getTime(),
1400
+ finishedAt: Date.now(),
1107
1401
  usage: sumUsage(step.usage, usage)
1108
1402
  },
1109
1403
  text,
1110
1404
  usage
1111
1405
  });
1112
1406
  }
1407
+ async function classifyToolCalls(toolCalls, skillManagers) {
1408
+ return Promise.all(
1409
+ toolCalls.map(async (tc) => {
1410
+ const skillManager = await getSkillManagerByToolName(skillManagers, tc.toolName);
1411
+ return {
1412
+ toolCallId: tc.toolCallId,
1413
+ toolName: tc.toolName,
1414
+ input: tc.input,
1415
+ skillManager
1416
+ };
1417
+ })
1418
+ );
1419
+ }
1420
+ function sortToolCallsByPriority(toolCalls) {
1421
+ const priority = { mcp: 0, delegate: 1, interactive: 2 };
1422
+ return [...toolCalls].sort(
1423
+ (a, b) => (priority[a.skillManager.type] ?? 99) - (priority[b.skillManager.type] ?? 99)
1424
+ );
1425
+ }
1426
+ function buildToolCallParts(toolCalls) {
1427
+ return toolCalls.map((tc) => ({
1428
+ type: "toolCallPart",
1429
+ toolCallId: tc.toolCallId,
1430
+ toolName: tc.toolName,
1431
+ args: tc.input
1432
+ }));
1433
+ }
1434
+ function buildToolCalls(toolCalls) {
1435
+ return toolCalls.map((tc) => ({
1436
+ id: tc.toolCallId,
1437
+ skillName: tc.skillManager.name,
1438
+ toolName: tc.toolName,
1439
+ args: tc.input
1440
+ }));
1441
+ }
1113
1442
  async function generatingToolCallLogic({
1114
1443
  setting,
1115
1444
  checkpoint,
@@ -1141,8 +1470,7 @@ async function generatingToolCallLogic({
1141
1470
  }
1142
1471
  const usage = usageFromGenerateTextResult(result);
1143
1472
  const { text, toolCalls, finishReason } = result;
1144
- const toolCall = toolCalls[0];
1145
- if (!toolCall) {
1473
+ if (toolCalls.length === 0) {
1146
1474
  const reason = JSON.stringify({
1147
1475
  error: "Error: No tool call generated",
1148
1476
  message: "You must generate a tool call. Try again."
@@ -1153,42 +1481,26 @@ async function generatingToolCallLogic({
1153
1481
  usage
1154
1482
  });
1155
1483
  }
1156
- const contents = [
1157
- {
1158
- type: "toolCallPart",
1159
- toolCallId: toolCall.toolCallId,
1160
- toolName: toolCall.toolName,
1161
- args: toolCall.input
1162
- }
1163
- ];
1164
- if (text) {
1165
- contents.push({
1166
- type: "textPart",
1167
- text
1168
- });
1169
- }
1170
- const skillManager = await getSkillManagerByToolName(skillManagers, toolCall.toolName);
1171
- const eventPayload = {
1172
- newMessage: createExpertMessage(contents),
1173
- toolCall: {
1174
- id: toolCall.toolCallId,
1175
- skillName: skillManager.name,
1176
- toolName: toolCall.toolName,
1177
- args: toolCall.input
1178
- },
1179
- usage
1180
- };
1484
+ const classified = await classifyToolCalls(toolCalls, skillManagers);
1485
+ const sorted = sortToolCallsByPriority(classified);
1181
1486
  if (finishReason === "tool-calls" || finishReason === "stop") {
1182
- switch (skillManager.type) {
1183
- case "mcp":
1184
- return callTool(setting, checkpoint, eventPayload);
1185
- case "interactive":
1186
- return callInteractiveTool(setting, checkpoint, eventPayload);
1187
- case "delegate":
1188
- return callDelegate(setting, checkpoint, eventPayload);
1189
- }
1487
+ const toolCallParts = buildToolCallParts(sorted);
1488
+ const contents = [...toolCallParts];
1489
+ if (text) {
1490
+ contents.push({ type: "textPart", text });
1491
+ }
1492
+ const allToolCalls = buildToolCalls(sorted);
1493
+ return callTools(setting, checkpoint, {
1494
+ newMessage: createExpertMessage(contents),
1495
+ toolCalls: allToolCalls,
1496
+ usage
1497
+ });
1190
1498
  }
1191
1499
  if (finishReason === "length") {
1500
+ const firstToolCall = sorted[0];
1501
+ if (!firstToolCall) {
1502
+ throw new Error("No tool call found");
1503
+ }
1192
1504
  const reason = JSON.stringify({
1193
1505
  error: "Error: Tool call generation failed",
1194
1506
  message: "Generation length exceeded. Try again."
@@ -1199,27 +1511,36 @@ async function generatingToolCallLogic({
1199
1511
  createExpertMessage([
1200
1512
  {
1201
1513
  type: "toolCallPart",
1202
- toolCallId: toolCall.toolCallId,
1203
- toolName: toolCall.toolName,
1204
- args: toolCall.input
1514
+ toolCallId: firstToolCall.toolCallId,
1515
+ toolName: firstToolCall.toolName,
1516
+ args: firstToolCall.input
1205
1517
  }
1206
1518
  ]),
1207
1519
  createToolMessage([
1208
1520
  {
1209
1521
  type: "toolResultPart",
1210
- toolCallId: toolCall.toolCallId,
1211
- toolName: toolCall.toolName,
1522
+ toolCallId: firstToolCall.toolCallId,
1523
+ toolName: firstToolCall.toolName,
1212
1524
  contents: [{ type: "textPart", text: reason }]
1213
1525
  }
1214
1526
  ])
1215
1527
  ],
1216
- toolCall: eventPayload.toolCall,
1217
- toolResult: {
1218
- id: toolCall.toolCallId,
1219
- skillName: skillManager.name,
1220
- toolName: toolCall.toolName,
1221
- result: [{ type: "textPart", id: createId(), text: reason }]
1222
- },
1528
+ toolCalls: [
1529
+ {
1530
+ id: firstToolCall.toolCallId,
1531
+ skillName: firstToolCall.skillManager.name,
1532
+ toolName: firstToolCall.toolName,
1533
+ args: firstToolCall.input
1534
+ }
1535
+ ],
1536
+ toolResults: [
1537
+ {
1538
+ id: firstToolCall.toolCallId,
1539
+ skillName: firstToolCall.skillManager.name,
1540
+ toolName: firstToolCall.toolName,
1541
+ result: [{ type: "textPart", id: createId(), text: reason }]
1542
+ }
1543
+ ],
1223
1544
  usage
1224
1545
  });
1225
1546
  }
@@ -1348,18 +1669,24 @@ async function initLogic({
1348
1669
  if (!setting.input.interactiveToolCallResult) {
1349
1670
  throw new Error("Interactive tool call result is undefined");
1350
1671
  }
1351
- return startRun(setting, checkpoint, {
1352
- initialCheckpoint: checkpoint,
1353
- inputMessages: [
1354
- createToolMessage([
1355
- {
1356
- type: "toolResultPart",
1357
- toolCallId: setting.input.interactiveToolCallResult.toolCallId,
1358
- toolName: setting.input.interactiveToolCallResult.toolName,
1359
- contents: [{ type: "textPart", text: setting.input.interactiveToolCallResult.text }]
1360
- }
1361
- ])
1362
- ]
1672
+ const { toolCallId, toolName, skillName, text } = setting.input.interactiveToolCallResult;
1673
+ const pendingToolCalls = checkpoint.pendingToolCalls ?? [];
1674
+ const newToolResult = {
1675
+ id: toolCallId,
1676
+ skillName,
1677
+ toolName,
1678
+ result: [{ type: "textPart", id: createId(), text }]
1679
+ };
1680
+ const updatedPartialResults = [...checkpoint.partialToolResults ?? [], newToolResult];
1681
+ const updatedPendingToolCalls = pendingToolCalls.filter((tc) => tc.id !== toolCallId);
1682
+ const updatedCheckpoint = {
1683
+ ...checkpoint,
1684
+ partialToolResults: updatedPartialResults,
1685
+ pendingToolCalls: updatedPendingToolCalls.length > 0 ? updatedPendingToolCalls : void 0
1686
+ };
1687
+ return startRun(setting, updatedCheckpoint, {
1688
+ initialCheckpoint: updatedCheckpoint,
1689
+ inputMessages: []
1363
1690
  });
1364
1691
  }
1365
1692
  default:
@@ -1376,116 +1703,27 @@ async function preparingForStepLogic({
1376
1703
  setting,
1377
1704
  checkpoint
1378
1705
  }) {
1379
- return startGeneration(setting, checkpoint, {
1380
- messages: checkpoint.messages
1381
- });
1382
- }
1383
- async function resolvingImageFileLogic({
1384
- setting,
1385
- checkpoint,
1386
- step
1387
- }) {
1388
- if (!step.toolCall || !step.toolResult) {
1389
- throw new Error("No tool call or tool result found");
1390
- }
1391
- const { id, toolName } = step.toolCall;
1392
- const { result } = step.toolResult;
1393
- const textParts = result.filter((part) => part.type === "textPart");
1394
- const files = [];
1395
- for (const textPart of textParts) {
1396
- let imageInfo;
1397
- try {
1398
- imageInfo = JSON.parse(textPart.text);
1399
- } catch {
1400
- files.push({
1401
- type: "textPart",
1402
- text: textPart.text
1403
- });
1404
- continue;
1405
- }
1406
- const { path: path2, mimeType, size } = imageInfo;
1407
- try {
1408
- const buffer = await readFile(path2);
1409
- files.push({
1410
- type: "imageInlinePart",
1411
- encodedData: buffer.toString("base64"),
1412
- mimeType
1413
- });
1414
- } catch (error) {
1415
- files.push({
1416
- type: "textPart",
1417
- text: `Failed to read image file "${path2}": ${error instanceof Error ? error.message : String(error)}`
1418
- });
1419
- }
1706
+ if (checkpoint.pendingToolCalls && checkpoint.pendingToolCalls.length > 0) {
1707
+ return resumeToolCalls(setting, checkpoint, {
1708
+ pendingToolCalls: checkpoint.pendingToolCalls,
1709
+ partialToolResults: checkpoint.partialToolResults ?? []
1710
+ });
1420
1711
  }
1421
- return finishToolCall(setting, checkpoint, {
1422
- newMessages: [
1423
- createToolMessage([
1424
- {
1425
- type: "toolResultPart",
1426
- toolCallId: id,
1427
- toolName,
1428
- contents: files
1429
- }
1430
- ])
1431
- ]
1432
- });
1433
- }
1434
- async function resolvingPdfFileLogic({
1435
- setting,
1436
- checkpoint,
1437
- step
1438
- }) {
1439
- if (!step.toolCall || !step.toolResult) {
1440
- throw new Error("No tool call or tool result found");
1441
- }
1442
- const { id, toolName } = step.toolCall;
1443
- const { result } = step.toolResult;
1444
- const textParts = result.filter((part) => part.type === "textPart");
1445
- const files = [];
1446
- for (const textPart of textParts) {
1447
- let pdfInfo;
1448
- try {
1449
- pdfInfo = JSON.parse(textPart.text);
1450
- } catch {
1451
- files.push({
1452
- type: "textPart",
1453
- text: textPart.text
1454
- });
1455
- continue;
1456
- }
1457
- const { path: path2, mimeType, size } = pdfInfo;
1458
- try {
1459
- const buffer = await readFile(path2);
1460
- files.push({
1461
- type: "fileInlinePart",
1462
- encodedData: buffer.toString("base64"),
1463
- mimeType
1464
- });
1465
- } catch (error) {
1466
- files.push({
1467
- type: "textPart",
1468
- text: `Failed to read PDF file "${path2}": ${error instanceof Error ? error.message : String(error)}`
1469
- });
1470
- }
1712
+ if (checkpoint.partialToolResults && checkpoint.partialToolResults.length > 0) {
1713
+ const toolResultParts = checkpoint.partialToolResults.map((tr) => ({
1714
+ type: "toolResultPart",
1715
+ toolCallId: tr.id,
1716
+ toolName: tr.toolName,
1717
+ contents: tr.result.filter(
1718
+ (part) => part.type === "textPart" || part.type === "imageInlinePart" || part.type === "fileInlinePart"
1719
+ )
1720
+ }));
1721
+ return finishAllToolCalls(setting, checkpoint, {
1722
+ newMessages: [createToolMessage(toolResultParts)]
1723
+ });
1471
1724
  }
1472
- return finishToolCall(setting, checkpoint, {
1473
- newMessages: [
1474
- createToolMessage([
1475
- {
1476
- type: "toolResultPart",
1477
- toolCallId: id,
1478
- toolName,
1479
- contents: [
1480
- {
1481
- type: "textPart",
1482
- text: "User uploads PDF file as follows."
1483
- }
1484
- ]
1485
- }
1486
- ]),
1487
- createUserMessage(files)
1488
- ]
1725
+ return startGeneration(setting, checkpoint, {
1726
+ messages: checkpoint.messages
1489
1727
  });
1490
1728
  }
1491
1729
  async function resolvingToolResultLogic({
@@ -1493,24 +1731,22 @@ async function resolvingToolResultLogic({
1493
1731
  checkpoint,
1494
1732
  step
1495
1733
  }) {
1496
- if (!step.toolCall || !step.toolResult) {
1497
- throw new Error("No tool call or tool result found");
1734
+ if (!step.toolCalls || !step.toolResults || step.toolResults.length === 0) {
1735
+ throw new Error("No tool calls or tool results found");
1498
1736
  }
1499
- const { id, toolName } = step.toolCall;
1500
- const { result } = step.toolResult;
1737
+ const toolResultParts = step.toolResults.map((toolResult) => {
1738
+ const toolCall = step.toolCalls?.find((tc) => tc.id === toolResult.id);
1739
+ return {
1740
+ type: "toolResultPart",
1741
+ toolCallId: toolResult.id,
1742
+ toolName: toolCall?.toolName ?? toolResult.toolName,
1743
+ contents: toolResult.result.filter(
1744
+ (part) => part.type === "textPart" || part.type === "imageInlinePart" || part.type === "fileInlinePart"
1745
+ )
1746
+ };
1747
+ });
1501
1748
  return finishToolCall(setting, checkpoint, {
1502
- newMessages: [
1503
- createToolMessage([
1504
- {
1505
- type: "toolResultPart",
1506
- toolCallId: id,
1507
- toolName,
1508
- contents: result.filter(
1509
- (part) => part.type === "textPart" || part.type === "imageInlinePart"
1510
- )
1511
- }
1512
- ])
1513
- ]
1749
+ newMessages: [createToolMessage(toolResultParts)]
1514
1750
  });
1515
1751
  }
1516
1752
 
@@ -1552,7 +1788,9 @@ var runtimeStateMachine = setup({
1552
1788
  checkpoint: ({ context, event }) => ({
1553
1789
  ...context.checkpoint,
1554
1790
  status: "proceeding",
1555
- messages: [...context.checkpoint.messages, ...event.inputMessages]
1791
+ messages: [...context.checkpoint.messages, ...event.inputMessages],
1792
+ pendingToolCalls: event.initialCheckpoint.pendingToolCalls,
1793
+ partialToolResults: event.initialCheckpoint.partialToolResults
1556
1794
  }),
1557
1795
  step: ({ context, event }) => ({
1558
1796
  ...context.step,
@@ -1575,6 +1813,38 @@ var runtimeStateMachine = setup({
1575
1813
  startedAt: event.timestamp
1576
1814
  })
1577
1815
  })
1816
+ },
1817
+ resumeToolCalls: {
1818
+ target: "CallingTool",
1819
+ actions: assign({
1820
+ step: ({ context, event }) => ({
1821
+ stepNumber: context.checkpoint.stepNumber,
1822
+ inputMessages: context.step.inputMessages ?? [],
1823
+ newMessages: context.step.newMessages,
1824
+ toolCalls: context.step.toolCalls,
1825
+ toolResults: event.partialToolResults,
1826
+ pendingToolCalls: event.pendingToolCalls,
1827
+ usage: context.step.usage,
1828
+ startedAt: context.step.startedAt
1829
+ })
1830
+ })
1831
+ },
1832
+ finishAllToolCalls: {
1833
+ target: "FinishingStep",
1834
+ actions: assign({
1835
+ checkpoint: ({ context, event }) => ({
1836
+ ...context.checkpoint,
1837
+ messages: [...context.checkpoint.messages, ...event.newMessages],
1838
+ pendingToolCalls: void 0,
1839
+ partialToolResults: void 0
1840
+ }),
1841
+ step: ({ context, event }) => ({
1842
+ ...context.step,
1843
+ newMessages: [...context.step.newMessages, ...event.newMessages],
1844
+ toolResults: context.checkpoint.partialToolResults,
1845
+ pendingToolCalls: void 0
1846
+ })
1847
+ })
1578
1848
  }
1579
1849
  }
1580
1850
  },
@@ -1591,13 +1861,13 @@ var runtimeStateMachine = setup({
1591
1861
  step: ({ context, event }) => ({
1592
1862
  ...context.step,
1593
1863
  newMessages: event.newMessages,
1594
- toolCall: event.toolCall,
1595
- toolResult: event.toolResult,
1864
+ toolCalls: event.toolCalls,
1865
+ toolResults: event.toolResults,
1596
1866
  usage: sumUsage(context.step.usage, event.usage)
1597
1867
  })
1598
1868
  })
1599
1869
  },
1600
- callTool: {
1870
+ callTools: {
1601
1871
  target: "CallingTool",
1602
1872
  actions: assign({
1603
1873
  checkpoint: ({ context, event }) => ({
@@ -1609,7 +1879,7 @@ var runtimeStateMachine = setup({
1609
1879
  step: ({ context, event }) => ({
1610
1880
  ...context.step,
1611
1881
  newMessages: [event.newMessage],
1612
- toolCall: event.toolCall,
1882
+ toolCalls: event.toolCalls,
1613
1883
  usage: sumUsage(context.step.usage, event.usage)
1614
1884
  })
1615
1885
  })
@@ -1626,7 +1896,7 @@ var runtimeStateMachine = setup({
1626
1896
  step: ({ context, event }) => ({
1627
1897
  ...context.step,
1628
1898
  newMessages: [event.newMessage],
1629
- toolCall: event.toolCall,
1899
+ toolCalls: [event.toolCall],
1630
1900
  usage: sumUsage(context.step.usage, event.usage)
1631
1901
  })
1632
1902
  })
@@ -1643,7 +1913,7 @@ var runtimeStateMachine = setup({
1643
1913
  step: ({ context, event }) => ({
1644
1914
  ...context.step,
1645
1915
  newMessages: [event.newMessage],
1646
- toolCall: event.toolCall,
1916
+ toolCalls: event.toolCalls,
1647
1917
  usage: sumUsage(context.step.usage, event.usage)
1648
1918
  })
1649
1919
  })
@@ -1652,12 +1922,13 @@ var runtimeStateMachine = setup({
1652
1922
  },
1653
1923
  CallingTool: {
1654
1924
  on: {
1655
- resolveToolResult: {
1925
+ resolveToolResults: {
1656
1926
  target: "ResolvingToolResult",
1657
1927
  actions: assign({
1658
1928
  step: ({ context, event }) => ({
1659
1929
  ...context.step,
1660
- toolResult: event.toolResult
1930
+ toolResults: event.toolResults,
1931
+ pendingToolCalls: void 0
1661
1932
  })
1662
1933
  })
1663
1934
  },
@@ -1666,34 +1937,40 @@ var runtimeStateMachine = setup({
1666
1937
  actions: assign({
1667
1938
  step: ({ context, event }) => ({
1668
1939
  ...context.step,
1669
- toolResult: event.toolResult
1940
+ toolResults: [event.toolResult]
1670
1941
  })
1671
1942
  })
1672
1943
  },
1673
- resolvePdfFile: {
1674
- target: "ResolvingPdfFile",
1944
+ attemptCompletion: {
1945
+ target: "GeneratingRunResult",
1675
1946
  actions: assign({
1676
1947
  step: ({ context, event }) => ({
1677
1948
  ...context.step,
1678
- toolResult: event.toolResult
1949
+ toolResults: [event.toolResult]
1679
1950
  })
1680
1951
  })
1681
1952
  },
1682
- resolveImageFile: {
1683
- target: "ResolvingImageFile",
1953
+ callDelegate: {
1954
+ target: "CallingDelegate",
1684
1955
  actions: assign({
1685
- step: ({ context, event }) => ({
1956
+ step: ({ context }) => ({
1686
1957
  ...context.step,
1687
- toolResult: event.toolResult
1958
+ toolCalls: context.step.toolCalls,
1959
+ toolResults: context.step.toolResults,
1960
+ pendingToolCalls: context.step.pendingToolCalls,
1961
+ partialToolResults: context.step.partialToolResults
1688
1962
  })
1689
1963
  })
1690
1964
  },
1691
- attemptCompletion: {
1692
- target: "GeneratingRunResult",
1965
+ callInteractiveTool: {
1966
+ target: "CallingInteractiveTool",
1693
1967
  actions: assign({
1694
- step: ({ context, event }) => ({
1968
+ step: ({ context }) => ({
1695
1969
  ...context.step,
1696
- toolResult: event.toolResult
1970
+ toolCalls: context.step.toolCalls,
1971
+ toolResults: context.step.toolResults,
1972
+ pendingToolCalls: context.step.pendingToolCalls,
1973
+ partialToolResults: context.step.partialToolResults
1697
1974
  })
1698
1975
  })
1699
1976
  }
@@ -1733,40 +2010,6 @@ var runtimeStateMachine = setup({
1733
2010
  }
1734
2011
  }
1735
2012
  },
1736
- ResolvingPdfFile: {
1737
- on: {
1738
- finishToolCall: {
1739
- target: "FinishingStep",
1740
- actions: assign({
1741
- checkpoint: ({ context, event }) => ({
1742
- ...context.checkpoint,
1743
- messages: [...context.checkpoint.messages, ...event.newMessages]
1744
- }),
1745
- step: ({ context, event }) => ({
1746
- ...context.step,
1747
- newMessages: [...context.step.newMessages, ...event.newMessages]
1748
- })
1749
- })
1750
- }
1751
- }
1752
- },
1753
- ResolvingImageFile: {
1754
- on: {
1755
- finishToolCall: {
1756
- target: "FinishingStep",
1757
- actions: assign({
1758
- checkpoint: ({ context, event }) => ({
1759
- ...context.checkpoint,
1760
- messages: [...context.checkpoint.messages, ...event.newMessages]
1761
- }),
1762
- step: ({ context, event }) => ({
1763
- ...context.step,
1764
- newMessages: [...context.step.newMessages, ...event.newMessages]
1765
- })
1766
- })
1767
- }
1768
- }
1769
- },
1770
2013
  GeneratingRunResult: {
1771
2014
  on: {
1772
2015
  retry: {
@@ -1780,8 +2023,8 @@ var runtimeStateMachine = setup({
1780
2023
  step: ({ context, event }) => ({
1781
2024
  ...context.step,
1782
2025
  newMessages: event.newMessages,
1783
- toolCall: event.toolCall,
1784
- toolResult: event.toolResult,
2026
+ toolCalls: event.toolCalls,
2027
+ toolResults: event.toolResults,
1785
2028
  usage: sumUsage(context.step.usage, event.usage)
1786
2029
  })
1787
2030
  })
@@ -1863,8 +2106,6 @@ var StateMachineLogics = {
1863
2106
  CallingTool: callingToolLogic,
1864
2107
  ResolvingToolResult: resolvingToolResultLogic,
1865
2108
  ResolvingThought: resolvingThoughtLogic,
1866
- ResolvingPdfFile: resolvingPdfFileLogic,
1867
- ResolvingImageFile: resolvingImageFileLogic,
1868
2109
  GeneratingRunResult: generatingRunResultLogic,
1869
2110
  CallingInteractiveTool: callingInteractiveToolLogic,
1870
2111
  CallingDelegate: callingDelegateLogic,
@@ -1903,7 +2144,7 @@ async function executeStateMachine(params) {
1903
2144
  } else {
1904
2145
  const event = await StateMachineLogics[runState.value](runState.context);
1905
2146
  if ("checkpoint" in event) {
1906
- await storeCheckpoint(event.checkpoint, event.timestamp);
2147
+ await storeCheckpoint(event.checkpoint);
1907
2148
  }
1908
2149
  await eventEmitter.emit(event);
1909
2150
  if (shouldContinueRun) {
@@ -1992,10 +2233,15 @@ async function run(runInput, options) {
1992
2233
  const contextWindow = getContextWindow(setting.providerConfig.providerName, setting.model);
1993
2234
  const getRunDir = options?.getRunDir ?? defaultGetRunDir;
1994
2235
  await storeRunSetting(setting, options?.fileSystem, getRunDir);
2236
+ let job = retrieveJob(setting.jobId) ?? createInitialJob(setting.jobId, setting.expertKey, setting.maxSteps);
2237
+ if (job.status !== "running") {
2238
+ job = { ...job, status: "running", finishedAt: void 0 };
2239
+ }
2240
+ storeJob(job);
1995
2241
  while (true) {
1996
2242
  const { expertToRun, experts } = await setupExperts(setting, options?.resolveExpertToRun);
1997
2243
  if (options?.eventListener) {
1998
- const initEvent = createRuntimeEvent("initializeRuntime", setting.runId, {
2244
+ const initEvent = createRuntimeEvent("initializeRuntime", setting.jobId, setting.runId, {
1999
2245
  runtimeVersion: package_default.version,
2000
2246
  expertName: expertToRun.name,
2001
2247
  experts: Object.keys(experts),
@@ -2013,9 +2259,11 @@ async function run(runInput, options) {
2013
2259
  expertToRun,
2014
2260
  experts,
2015
2261
  setting,
2016
- options?.eventListener
2262
+ options?.eventListener,
2263
+ { isDelegatedRun: !!checkpoint?.delegatedBy }
2017
2264
  );
2018
2265
  const initialCheckpoint = checkpoint ? createNextStepCheckpoint(createId(), checkpoint) : createInitialCheckpoint(createId(), {
2266
+ jobId: setting.jobId,
2019
2267
  runId: setting.runId,
2020
2268
  expertKey: setting.expertKey,
2021
2269
  expert: expertToRun,
@@ -2030,11 +2278,21 @@ async function run(runInput, options) {
2030
2278
  storeCheckpoint,
2031
2279
  shouldContinueRun: options?.shouldContinueRun
2032
2280
  });
2281
+ job = {
2282
+ ...job,
2283
+ totalSteps: runResultCheckpoint.stepNumber,
2284
+ usage: runResultCheckpoint.usage
2285
+ };
2033
2286
  switch (runResultCheckpoint.status) {
2034
2287
  case "completed": {
2288
+ if (options?.returnOnDelegationComplete) {
2289
+ storeJob(job);
2290
+ return runResultCheckpoint;
2291
+ }
2035
2292
  if (runResultCheckpoint.delegatedBy) {
2293
+ storeJob(job);
2036
2294
  const parentCheckpoint = await retrieveCheckpoint(
2037
- setting.runId,
2295
+ setting.jobId,
2038
2296
  runResultCheckpoint.delegatedBy.checkpointId
2039
2297
  );
2040
2298
  const result = buildDelegationReturnState(setting, runResultCheckpoint, parentCheckpoint);
@@ -2042,21 +2300,80 @@ async function run(runInput, options) {
2042
2300
  checkpoint = result.checkpoint;
2043
2301
  break;
2044
2302
  }
2303
+ storeJob({ ...job, status: "completed", finishedAt: Date.now() });
2045
2304
  return runResultCheckpoint;
2046
2305
  }
2047
2306
  case "stoppedByInteractiveTool": {
2307
+ storeJob({ ...job, status: "stoppedByInteractiveTool" });
2048
2308
  return runResultCheckpoint;
2049
2309
  }
2050
2310
  case "stoppedByDelegate": {
2051
- const result = buildDelegateToState(setting, runResultCheckpoint, expertToRun);
2052
- setting = result.setting;
2053
- checkpoint = result.checkpoint;
2311
+ storeJob(job);
2312
+ const { delegateTo } = runResultCheckpoint;
2313
+ if (!delegateTo || delegateTo.length === 0) {
2314
+ throw new Error("No delegations found in checkpoint");
2315
+ }
2316
+ if (delegateTo.length === 1) {
2317
+ const result = buildDelegateToState(setting, runResultCheckpoint, expertToRun);
2318
+ setting = result.setting;
2319
+ checkpoint = result.checkpoint;
2320
+ break;
2321
+ }
2322
+ const firstDelegation = delegateTo[0];
2323
+ const remainingDelegations = delegateTo.slice(1);
2324
+ const [firstResult, ...restResults] = await Promise.all(
2325
+ delegateTo.map(
2326
+ (delegation) => runDelegate(delegation, setting, runResultCheckpoint, expertToRun, options)
2327
+ )
2328
+ );
2329
+ const allResults = [firstResult, ...restResults];
2330
+ const aggregatedUsage = allResults.reduce(
2331
+ (acc, result) => sumUsage(acc, result.deltaUsage),
2332
+ runResultCheckpoint.usage
2333
+ );
2334
+ const maxStepNumber = Math.max(...allResults.map((r) => r.stepNumber));
2335
+ const restToolResults = restResults.map((result) => ({
2336
+ id: result.toolCallId,
2337
+ skillName: `delegate/${result.expertKey}`,
2338
+ toolName: result.toolName,
2339
+ result: [{ type: "textPart", id: createId(), text: result.text }]
2340
+ }));
2341
+ const processedToolCallIds = new Set(remainingDelegations.map((d) => d.toolCallId));
2342
+ const remainingToolCalls = runResultCheckpoint.pendingToolCalls?.filter(
2343
+ (tc) => !processedToolCallIds.has(tc.id) && tc.id !== firstDelegation.toolCallId
2344
+ );
2345
+ setting = {
2346
+ ...setting,
2347
+ expertKey: expertToRun.key,
2348
+ input: {
2349
+ interactiveToolCallResult: {
2350
+ toolCallId: firstResult.toolCallId,
2351
+ toolName: firstResult.toolName,
2352
+ skillName: `delegate/${firstResult.expertKey}`,
2353
+ text: firstResult.text
2354
+ }
2355
+ }
2356
+ };
2357
+ checkpoint = {
2358
+ ...runResultCheckpoint,
2359
+ status: "stoppedByDelegate",
2360
+ delegateTo: void 0,
2361
+ stepNumber: maxStepNumber,
2362
+ usage: aggregatedUsage,
2363
+ pendingToolCalls: remainingToolCalls?.length ? remainingToolCalls : void 0,
2364
+ partialToolResults: [
2365
+ ...runResultCheckpoint.partialToolResults ?? [],
2366
+ ...restToolResults
2367
+ ]
2368
+ };
2054
2369
  break;
2055
2370
  }
2056
2371
  case "stoppedByExceededMaxSteps": {
2372
+ storeJob({ ...job, status: "stoppedByMaxSteps", finishedAt: Date.now() });
2057
2373
  return runResultCheckpoint;
2058
2374
  }
2059
2375
  case "stoppedByError": {
2376
+ storeJob({ ...job, status: "stoppedByError", finishedAt: Date.now() });
2060
2377
  return runResultCheckpoint;
2061
2378
  }
2062
2379
  default:
@@ -2073,10 +2390,65 @@ function getEventListener(options) {
2073
2390
  listener(event);
2074
2391
  };
2075
2392
  }
2393
+ async function runDelegate(delegation, parentSetting, parentCheckpoint, parentExpert, options) {
2394
+ const { expert, toolCallId, toolName, query } = delegation;
2395
+ const delegateRunId = createId();
2396
+ const delegateSetting = {
2397
+ ...parentSetting,
2398
+ runId: delegateRunId,
2399
+ expertKey: expert.key,
2400
+ input: { text: query }
2401
+ };
2402
+ const delegateCheckpoint = {
2403
+ id: createId(),
2404
+ jobId: parentSetting.jobId,
2405
+ runId: delegateRunId,
2406
+ status: "init",
2407
+ stepNumber: parentCheckpoint.stepNumber,
2408
+ messages: [],
2409
+ expert: {
2410
+ key: expert.key,
2411
+ name: expert.name,
2412
+ version: expert.version
2413
+ },
2414
+ delegatedBy: {
2415
+ expert: {
2416
+ key: parentExpert.key,
2417
+ name: parentExpert.name,
2418
+ version: parentExpert.version
2419
+ },
2420
+ toolCallId,
2421
+ toolName,
2422
+ checkpointId: parentCheckpoint.id
2423
+ },
2424
+ usage: createEmptyUsage(),
2425
+ contextWindow: parentCheckpoint.contextWindow
2426
+ };
2427
+ const resultCheckpoint = await run(
2428
+ { setting: delegateSetting, checkpoint: delegateCheckpoint },
2429
+ { ...options, returnOnDelegationComplete: true }
2430
+ );
2431
+ const lastMessage = resultCheckpoint.messages[resultCheckpoint.messages.length - 1];
2432
+ if (!lastMessage || lastMessage.type !== "expertMessage") {
2433
+ throw new Error("Delegation error: delegation result message is incorrect");
2434
+ }
2435
+ const textPart = lastMessage.contents.find((c) => c.type === "textPart");
2436
+ if (!textPart || textPart.type !== "textPart") {
2437
+ throw new Error("Delegation error: delegation result message does not contain text");
2438
+ }
2439
+ return {
2440
+ toolCallId,
2441
+ toolName,
2442
+ expertKey: expert.key,
2443
+ text: textPart.text,
2444
+ stepNumber: resultCheckpoint.stepNumber,
2445
+ deltaUsage: resultCheckpoint.usage
2446
+ };
2447
+ }
2076
2448
 
2077
2449
  // src/index.ts
2078
2450
  var runtimeVersion = package_default.version;
2079
2451
 
2080
- export { StateMachineLogics, calculateContextWindowUsage, getContextWindow, getModel, defaultGetRunDir as getRunDir, run, runtimeStateMachine, runtimeVersion };
2452
+ export { StateMachineLogics, calculateContextWindowUsage, createInitialJob, getAllJobs, getAllRuns, getCheckpointDir, getCheckpointPath, getCheckpointsByJobId, getContextWindow, getEventContents, getEventsByRun, getJobDir, getJobsDir, getModel, defaultGetRunDir as getRunDir, retrieveJob, run, runtimeStateMachine, runtimeVersion, storeJob };
2081
2453
  //# sourceMappingURL=index.js.map
2082
2454
  //# sourceMappingURL=index.js.map