@perstack/runtime 0.0.62 → 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, resolveToolResults, attemptCompletion, callDelegate, callInteractiveTool, stopRunByExceededMaxSteps, continueToNextStep, retry, completeRun, callTools, startRun, resumeToolCalls, finishAllToolCalls, 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 { readdir, readFile, 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.62"};
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
  }
@@ -200,10 +404,11 @@ function buildDelegationReturnState(currentSetting, resultCheckpoint, parentChec
200
404
  }
201
405
  function buildDelegateToState(currentSetting, resultCheckpoint, currentExpert) {
202
406
  const { delegateTo } = resultCheckpoint;
203
- if (!delegateTo) {
407
+ if (!delegateTo || delegateTo.length === 0) {
204
408
  throw new Error("delegateTo is required for buildDelegateToState");
205
409
  }
206
- const { expert, toolCallId, toolName, query } = delegateTo;
410
+ const firstDelegation = delegateTo[0];
411
+ const { expert, toolCallId, toolName, query } = firstDelegation;
207
412
  return {
208
413
  setting: {
209
414
  ...currentSetting,
@@ -237,69 +442,6 @@ function buildDelegateToState(currentSetting, resultCheckpoint, currentExpert) {
237
442
  }
238
443
  };
239
444
  }
240
- async function createDefaultFileSystem() {
241
- const fs = await import('fs');
242
- const fsPromises = await import('fs/promises');
243
- return {
244
- existsSync: fs.existsSync,
245
- mkdir: async (p, options) => {
246
- await fsPromises.mkdir(p, options);
247
- },
248
- readFile: fsPromises.readFile,
249
- writeFile: fsPromises.writeFile
250
- };
251
- }
252
- function defaultGetRunDir(runId) {
253
- return `${process.cwd()}/perstack/runs/${runId}`;
254
- }
255
- async function storeRunSetting(setting, fs, getRunDir = defaultGetRunDir) {
256
- const fileSystem = fs ?? await createDefaultFileSystem();
257
- const runDir = getRunDir(setting.runId);
258
- if (fileSystem.existsSync(runDir)) {
259
- const runSettingPath = path.resolve(runDir, "run-setting.json");
260
- const runSetting = runSettingSchema.parse(
261
- JSON.parse(await fileSystem.readFile(runSettingPath, "utf-8"))
262
- );
263
- runSetting.updatedAt = Date.now();
264
- await fileSystem.writeFile(runSettingPath, JSON.stringify(runSetting), "utf-8");
265
- } else {
266
- await fileSystem.mkdir(runDir, { recursive: true });
267
- await fileSystem.writeFile(
268
- path.resolve(runDir, "run-setting.json"),
269
- JSON.stringify(setting),
270
- "utf-8"
271
- );
272
- }
273
- }
274
-
275
- // src/default-store.ts
276
- async function defaultRetrieveCheckpoint(runId, checkpointId) {
277
- const runDir = defaultGetRunDir(runId);
278
- const checkpointFiles = await readdir(runDir, { withFileTypes: true }).then(
279
- (files) => files.filter((file) => file.isFile() && file.name.startsWith("checkpoint-"))
280
- );
281
- const checkpointFile = checkpointFiles.find((file) => file.name.endsWith(`-${checkpointId}.json`));
282
- if (!checkpointFile) {
283
- throw new Error(`checkpoint not found: ${runId} ${checkpointId}`);
284
- }
285
- const checkpointPath = `${runDir}/${checkpointFile.name}`;
286
- const checkpoint = await readFile(checkpointPath, "utf8");
287
- return checkpointSchema.parse(JSON.parse(checkpoint));
288
- }
289
- async function defaultStoreCheckpoint(checkpoint, timestamp) {
290
- const { id, runId, stepNumber } = checkpoint;
291
- const runDir = defaultGetRunDir(runId);
292
- const checkpointPath = `${runDir}/checkpoint-${timestamp}-${stepNumber}-${id}.json`;
293
- await mkdir(runDir, { recursive: true });
294
- await writeFile(checkpointPath, JSON.stringify(checkpoint));
295
- }
296
- async function defaultStoreEvent(event) {
297
- const { timestamp, runId, stepNumber, type } = event;
298
- const runDir = defaultGetRunDir(runId);
299
- const eventPath = `${runDir}/event-${timestamp}-${stepNumber}-${type}.json`;
300
- await mkdir(runDir, { recursive: true });
301
- await writeFile(eventPath, JSON.stringify(event));
302
- }
303
445
  var RunEventEmitter = class {
304
446
  listeners = [];
305
447
  subscribe(listener) {
@@ -332,9 +474,11 @@ var BaseSkillManager = class {
332
474
  skill;
333
475
  interactiveSkill;
334
476
  expert;
477
+ _jobId;
335
478
  _runId;
336
479
  _eventListener;
337
- constructor(runId, eventListener) {
480
+ constructor(jobId, runId, eventListener) {
481
+ this._jobId = jobId;
338
482
  this._runId = runId;
339
483
  this._eventListener = eventListener;
340
484
  }
@@ -385,8 +529,8 @@ var DelegateSkillManager = class extends BaseSkillManager {
385
529
  type = "delegate";
386
530
  lazyInit = false;
387
531
  expert;
388
- constructor(expert, runId, eventListener) {
389
- super(runId, eventListener);
532
+ constructor(expert, jobId, runId, eventListener) {
533
+ super(jobId, runId, eventListener);
390
534
  this.name = expert.name;
391
535
  this.expert = expert;
392
536
  }
@@ -418,8 +562,8 @@ var InteractiveSkillManager = class extends BaseSkillManager {
418
562
  type = "interactive";
419
563
  lazyInit = false;
420
564
  interactiveSkill;
421
- constructor(interactiveSkill, runId, eventListener) {
422
- super(runId, eventListener);
565
+ constructor(interactiveSkill, jobId, runId, eventListener) {
566
+ super(jobId, runId, eventListener);
423
567
  this.name = interactiveSkill.name;
424
568
  this.interactiveSkill = interactiveSkill;
425
569
  }
@@ -445,8 +589,8 @@ var McpSkillManager = class extends BaseSkillManager {
445
589
  skill;
446
590
  _mcpClient;
447
591
  _env;
448
- constructor(skill, env, runId, eventListener) {
449
- super(runId, eventListener);
592
+ constructor(skill, env, jobId, runId, eventListener) {
593
+ super(jobId, runId, eventListener);
450
594
  this.name = skill.name;
451
595
  this.skill = skill;
452
596
  this._env = env;
@@ -485,7 +629,7 @@ var McpSkillManager = class extends BaseSkillManager {
485
629
  const startTime = Date.now();
486
630
  const { command, args } = this._getCommandArgs(skill);
487
631
  if (this._eventListener) {
488
- const event = createRuntimeEvent("skillStarting", this._runId, {
632
+ const event = createRuntimeEvent("skillStarting", this._jobId, this._runId, {
489
633
  skillName: skill.name,
490
634
  command,
491
635
  args
@@ -496,7 +640,7 @@ var McpSkillManager = class extends BaseSkillManager {
496
640
  if (transport.stderr) {
497
641
  transport.stderr.on("data", (chunk) => {
498
642
  if (this._eventListener) {
499
- const event = createRuntimeEvent("skillStderr", this._runId, {
643
+ const event = createRuntimeEvent("skillStderr", this._jobId, this._runId, {
500
644
  skillName: skill.name,
501
645
  message: chunk.toString().trim()
502
646
  });
@@ -509,7 +653,7 @@ var McpSkillManager = class extends BaseSkillManager {
509
653
  const connectTime = Date.now();
510
654
  if (this._eventListener) {
511
655
  const serverInfo = this._mcpClient.getServerVersion();
512
- const event = createRuntimeEvent("skillConnected", this._runId, {
656
+ const event = createRuntimeEvent("skillConnected", this._jobId, this._runId, {
513
657
  skillName: skill.name,
514
658
  serverInfo: serverInfo ? { name: serverInfo.name, version: serverInfo.version } : void 0,
515
659
  connectDurationMs: connectTime - connectStartTime,
@@ -545,7 +689,7 @@ var McpSkillManager = class extends BaseSkillManager {
545
689
  if (this._mcpClient) {
546
690
  await this._mcpClient.close();
547
691
  if (this._eventListener && this.skill) {
548
- const event = createRuntimeEvent("skillDisconnected", this._runId, {
692
+ const event = createRuntimeEvent("skillDisconnected", this._jobId, this._runId, {
549
693
  skillName: this.skill.name
550
694
  });
551
695
  this._eventListener(event);
@@ -648,8 +792,8 @@ async function initSkillManagersWithCleanup(managers, allManagers) {
648
792
  throw firstRejected.reason;
649
793
  }
650
794
  }
651
- async function getSkillManagers(expert, experts, setting, eventListener) {
652
- const { perstackBaseSkillCommand, env, runId } = setting;
795
+ async function getSkillManagers(expert, experts, setting, eventListener, options) {
796
+ const { perstackBaseSkillCommand, env, jobId, runId } = setting;
653
797
  const { skills } = expert;
654
798
  if (!skills["@perstack/base"]) {
655
799
  throw new Error("Base skill is not defined");
@@ -676,20 +820,22 @@ async function getSkillManagers(expert, experts, setting, eventListener) {
676
820
  return skill;
677
821
  });
678
822
  const mcpSkillManagers = mcpSkills.map((skill) => {
679
- const manager = new McpSkillManager(skill, env, runId, eventListener);
823
+ const manager = new McpSkillManager(skill, env, jobId, runId, eventListener);
680
824
  allManagers.push(manager);
681
825
  return manager;
682
826
  });
683
827
  await initSkillManagersWithCleanup(mcpSkillManagers, allManagers);
684
- const interactiveSkills = Object.values(skills).filter(
685
- (skill) => skill.type === "interactiveSkill"
686
- );
687
- const interactiveSkillManagers = interactiveSkills.map((interactiveSkill) => {
688
- const manager = new InteractiveSkillManager(interactiveSkill, runId, eventListener);
689
- allManagers.push(manager);
690
- return manager;
691
- });
692
- 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
+ }
693
839
  const delegateSkillManagers = [];
694
840
  for (const delegateExpertName of expert.delegates) {
695
841
  const delegate = experts[delegateExpertName];
@@ -698,7 +844,7 @@ async function getSkillManagers(expert, experts, setting, eventListener) {
698
844
  })));
699
845
  throw new Error(`Delegate expert "${delegateExpertName}" not found in experts`);
700
846
  }
701
- const manager = new DelegateSkillManager(delegate, runId, eventListener);
847
+ const manager = new DelegateSkillManager(delegate, jobId, runId, eventListener);
702
848
  allManagers.push(manager);
703
849
  delegateSkillManagers.push(manager);
704
850
  }
@@ -739,6 +885,10 @@ async function getToolSet(skillManagers) {
739
885
  }
740
886
 
741
887
  // src/states/calling-delegate.ts
888
+ async function getToolType(toolName, skillManagers) {
889
+ const skillManager = await getSkillManagerByToolName(skillManagers, toolName);
890
+ return skillManager.type;
891
+ }
742
892
  async function callingDelegateLogic({
743
893
  setting,
744
894
  checkpoint,
@@ -748,35 +898,44 @@ async function callingDelegateLogic({
748
898
  if (!step.pendingToolCalls || step.pendingToolCalls.length === 0) {
749
899
  throw new Error("No pending tool calls found");
750
900
  }
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;
756
- const skillManager = await getSkillManagerByToolName(skillManagers, toolName);
757
- if (!skillManager.expert) {
758
- throw new Error(`Delegation error: skill manager "${toolName}" not found`);
759
- }
760
- if (!args || !args.query || typeof args.query !== "string") {
761
- throw new Error("Delegation error: query is undefined");
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");
762
911
  }
763
- const currentToolCall = step.pendingToolCalls[0];
764
- const remainingToolCalls = step.pendingToolCalls.slice(1);
765
- return stopRunByDelegate(setting, checkpoint, {
766
- checkpoint: {
767
- ...checkpoint,
768
- status: "stoppedByDelegate",
769
- delegateTo: {
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 {
770
922
  expert: {
771
923
  key: skillManager.expert.key,
772
924
  name: skillManager.expert.name,
773
925
  version: skillManager.expert.version
774
926
  },
775
- toolCallId: id,
776
- toolName,
777
- query: args.query
778
- },
779
- pendingToolCalls: [currentToolCall, ...remainingToolCalls],
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,
780
939
  partialToolResults: step.partialToolResults
781
940
  },
782
941
  step: {
@@ -844,9 +1003,9 @@ async function processFileToolResult(toolResult, toolName) {
844
1003
  processedContents.push(part);
845
1004
  continue;
846
1005
  }
847
- const { path: path2, mimeType } = fileInfo;
1006
+ const { path: path4, mimeType } = fileInfo;
848
1007
  try {
849
- const buffer = await readFile(path2);
1008
+ const buffer = await readFile(path4);
850
1009
  if (toolName === "readImageFile") {
851
1010
  processedContents.push({
852
1011
  type: "imageInlinePart",
@@ -866,7 +1025,7 @@ async function processFileToolResult(toolResult, toolName) {
866
1025
  processedContents.push({
867
1026
  type: "textPart",
868
1027
  id: part.id,
869
- text: `Failed to read file "${path2}": ${error instanceof Error ? error.message : String(error)}`
1028
+ text: `Failed to read file "${path4}": ${error instanceof Error ? error.message : String(error)}`
870
1029
  });
871
1030
  }
872
1031
  }
@@ -889,7 +1048,7 @@ async function executeMcpToolCall(toolCall, skillManagers) {
889
1048
  }
890
1049
  return toolResult;
891
1050
  }
892
- async function getToolType(toolCall, skillManagers) {
1051
+ async function getToolType2(toolCall, skillManagers) {
893
1052
  const skillManager = await getSkillManagerByToolName(skillManagers, toolCall.toolName);
894
1053
  return skillManager.type;
895
1054
  }
@@ -917,7 +1076,7 @@ async function callingToolLogic({
917
1076
  const toolCallTypes = await Promise.all(
918
1077
  pendingToolCalls.map(async (tc) => ({
919
1078
  toolCall: tc,
920
- type: await getToolType(tc, skillManagers)
1079
+ type: await getToolType2(tc, skillManagers)
921
1080
  }))
922
1081
  );
923
1082
  const mcpToolCalls = toolCallTypes.filter((t) => t.type === "mcp").map((t) => t.toolCall);
@@ -929,17 +1088,12 @@ async function callingToolLogic({
929
1088
  );
930
1089
  toolResults.push(...mcpResults);
931
1090
  }
932
- const remainingToolCalls = [...delegateToolCalls, ...interactiveToolCalls];
933
1091
  if (delegateToolCalls.length > 0) {
934
- const delegateToolCall = delegateToolCalls[0];
935
- if (!delegateToolCall) {
936
- throw new Error("No delegate tool call found");
937
- }
938
1092
  step.partialToolResults = toolResults;
939
- step.pendingToolCalls = remainingToolCalls;
1093
+ step.pendingToolCalls = [...delegateToolCalls, ...interactiveToolCalls];
940
1094
  return callDelegate(setting, checkpoint, {
941
1095
  newMessage: checkpoint.messages[checkpoint.messages.length - 1],
942
- toolCall: delegateToolCall,
1096
+ toolCalls: delegateToolCalls,
943
1097
  usage: step.usage
944
1098
  });
945
1099
  }
@@ -949,7 +1103,7 @@ async function callingToolLogic({
949
1103
  throw new Error("No interactive tool call found");
950
1104
  }
951
1105
  step.partialToolResults = toolResults;
952
- step.pendingToolCalls = remainingToolCalls;
1106
+ step.pendingToolCalls = interactiveToolCalls;
953
1107
  return callInteractiveTool(setting, checkpoint, {
954
1108
  newMessage: checkpoint.messages[checkpoint.messages.length - 1],
955
1109
  toolCall: interactiveToolCall,
@@ -1515,10 +1669,8 @@ async function initLogic({
1515
1669
  if (!setting.input.interactiveToolCallResult) {
1516
1670
  throw new Error("Interactive tool call result is undefined");
1517
1671
  }
1518
- const { toolCallId, toolName, text } = setting.input.interactiveToolCallResult;
1672
+ const { toolCallId, toolName, skillName, text } = setting.input.interactiveToolCallResult;
1519
1673
  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
1674
  const newToolResult = {
1523
1675
  id: toolCallId,
1524
1676
  skillName,
@@ -1761,7 +1913,7 @@ var runtimeStateMachine = setup({
1761
1913
  step: ({ context, event }) => ({
1762
1914
  ...context.step,
1763
1915
  newMessages: [event.newMessage],
1764
- toolCalls: [event.toolCall],
1916
+ toolCalls: event.toolCalls,
1765
1917
  usage: sumUsage(context.step.usage, event.usage)
1766
1918
  })
1767
1919
  })
@@ -1992,7 +2144,7 @@ async function executeStateMachine(params) {
1992
2144
  } else {
1993
2145
  const event = await StateMachineLogics[runState.value](runState.context);
1994
2146
  if ("checkpoint" in event) {
1995
- await storeCheckpoint(event.checkpoint, event.timestamp);
2147
+ await storeCheckpoint(event.checkpoint);
1996
2148
  }
1997
2149
  await eventEmitter.emit(event);
1998
2150
  if (shouldContinueRun) {
@@ -2081,10 +2233,15 @@ async function run(runInput, options) {
2081
2233
  const contextWindow = getContextWindow(setting.providerConfig.providerName, setting.model);
2082
2234
  const getRunDir = options?.getRunDir ?? defaultGetRunDir;
2083
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);
2084
2241
  while (true) {
2085
2242
  const { expertToRun, experts } = await setupExperts(setting, options?.resolveExpertToRun);
2086
2243
  if (options?.eventListener) {
2087
- const initEvent = createRuntimeEvent("initializeRuntime", setting.runId, {
2244
+ const initEvent = createRuntimeEvent("initializeRuntime", setting.jobId, setting.runId, {
2088
2245
  runtimeVersion: package_default.version,
2089
2246
  expertName: expertToRun.name,
2090
2247
  experts: Object.keys(experts),
@@ -2102,9 +2259,11 @@ async function run(runInput, options) {
2102
2259
  expertToRun,
2103
2260
  experts,
2104
2261
  setting,
2105
- options?.eventListener
2262
+ options?.eventListener,
2263
+ { isDelegatedRun: !!checkpoint?.delegatedBy }
2106
2264
  );
2107
2265
  const initialCheckpoint = checkpoint ? createNextStepCheckpoint(createId(), checkpoint) : createInitialCheckpoint(createId(), {
2266
+ jobId: setting.jobId,
2108
2267
  runId: setting.runId,
2109
2268
  expertKey: setting.expertKey,
2110
2269
  expert: expertToRun,
@@ -2119,11 +2278,21 @@ async function run(runInput, options) {
2119
2278
  storeCheckpoint,
2120
2279
  shouldContinueRun: options?.shouldContinueRun
2121
2280
  });
2281
+ job = {
2282
+ ...job,
2283
+ totalSteps: runResultCheckpoint.stepNumber,
2284
+ usage: runResultCheckpoint.usage
2285
+ };
2122
2286
  switch (runResultCheckpoint.status) {
2123
2287
  case "completed": {
2288
+ if (options?.returnOnDelegationComplete) {
2289
+ storeJob(job);
2290
+ return runResultCheckpoint;
2291
+ }
2124
2292
  if (runResultCheckpoint.delegatedBy) {
2293
+ storeJob(job);
2125
2294
  const parentCheckpoint = await retrieveCheckpoint(
2126
- setting.runId,
2295
+ setting.jobId,
2127
2296
  runResultCheckpoint.delegatedBy.checkpointId
2128
2297
  );
2129
2298
  const result = buildDelegationReturnState(setting, runResultCheckpoint, parentCheckpoint);
@@ -2131,21 +2300,80 @@ async function run(runInput, options) {
2131
2300
  checkpoint = result.checkpoint;
2132
2301
  break;
2133
2302
  }
2303
+ storeJob({ ...job, status: "completed", finishedAt: Date.now() });
2134
2304
  return runResultCheckpoint;
2135
2305
  }
2136
2306
  case "stoppedByInteractiveTool": {
2307
+ storeJob({ ...job, status: "stoppedByInteractiveTool" });
2137
2308
  return runResultCheckpoint;
2138
2309
  }
2139
2310
  case "stoppedByDelegate": {
2140
- const result = buildDelegateToState(setting, runResultCheckpoint, expertToRun);
2141
- setting = result.setting;
2142
- 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
+ };
2143
2369
  break;
2144
2370
  }
2145
2371
  case "stoppedByExceededMaxSteps": {
2372
+ storeJob({ ...job, status: "stoppedByMaxSteps", finishedAt: Date.now() });
2146
2373
  return runResultCheckpoint;
2147
2374
  }
2148
2375
  case "stoppedByError": {
2376
+ storeJob({ ...job, status: "stoppedByError", finishedAt: Date.now() });
2149
2377
  return runResultCheckpoint;
2150
2378
  }
2151
2379
  default:
@@ -2162,10 +2390,65 @@ function getEventListener(options) {
2162
2390
  listener(event);
2163
2391
  };
2164
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
+ }
2165
2448
 
2166
2449
  // src/index.ts
2167
2450
  var runtimeVersion = package_default.version;
2168
2451
 
2169
- 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 };
2170
2453
  //# sourceMappingURL=index.js.map
2171
2454
  //# sourceMappingURL=index.js.map