@perstack/runtime 0.0.66 → 0.0.68

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.
@@ -5,7 +5,7 @@ import { createDeepSeek } from '@ai-sdk/deepseek';
5
5
  import { createGoogleGenerativeAI } from '@ai-sdk/google';
6
6
  import { createVertex } from '@ai-sdk/google-vertex';
7
7
  import { createOpenAI } from '@ai-sdk/openai';
8
- import { runParamsSchema, createRuntimeEvent, knownModels, stopRunByExceededMaxSteps, continueToNextStep, stopRunByDelegate, stopRunByInteractiveTool, retry, completeRun, finishToolCall, resolveToolResults, attemptCompletion, callDelegate, callInteractiveTool, callTools, resumeToolCalls, finishAllToolCalls, startGeneration, startRun } from '@perstack/core';
8
+ import { runParamsSchema, createRuntimeEvent, knownModels, getFilteredEnv, stopRunByExceededMaxSteps, continueToNextStep, stopRunByDelegate, stopRunByInteractiveTool, retry, completeRun, finishToolCall, resolveToolResults, attemptCompletion, callDelegate, callInteractiveTool, callTools, resumeToolCalls, finishAllToolCalls, startGeneration, startRun } from '@perstack/core';
9
9
  import { createOllama } from 'ollama-ai-provider-v2';
10
10
  import { ProxyAgent, fetch } from 'undici';
11
11
  import { setup, assign, createActor } from 'xstate';
@@ -22,7 +22,7 @@ import { ApiV1Client } from '@perstack/api-client/v1';
22
22
  // package.json
23
23
  var package_default = {
24
24
  name: "@perstack/runtime",
25
- version: "0.0.66",
25
+ version: "0.0.68",
26
26
  description: "Perstack Runtime",
27
27
  author: "Wintermute Technologies, Inc.",
28
28
  license: "Apache-2.0",
@@ -64,7 +64,7 @@ var package_default = {
64
64
  "@ai-sdk/google": "^2.0.44",
65
65
  "@ai-sdk/google-vertex": "^3.0.24",
66
66
  "@ai-sdk/openai": "^2.0.75",
67
- "@modelcontextprotocol/sdk": "^1.23.0",
67
+ "@modelcontextprotocol/sdk": "^1.24.0",
68
68
  "@paralleldrive/cuid2": "^3.0.4",
69
69
  "@perstack/api-client": "workspace:*",
70
70
  "@perstack/core": "workspace:*",
@@ -353,12 +353,15 @@ var McpSkillManager = class extends BaseSkillManager {
353
353
  name: `${this.skill.name}-mcp-client`,
354
354
  version: "1.0.0"
355
355
  });
356
+ let timingInfo;
356
357
  if (this.skill.type === "mcpStdioSkill") {
357
- await this._initStdio(this.skill);
358
+ timingInfo = await this._initStdio(this.skill);
358
359
  } else {
359
360
  await this._initSse(this.skill);
360
361
  }
362
+ const toolDiscoveryStartTime = Date.now();
361
363
  const { tools } = await this._mcpClient.listTools();
364
+ const toolDiscoveryDurationMs = Date.now() - toolDiscoveryStartTime;
362
365
  this._toolDefinitions = tools.map((tool2) => ({
363
366
  skillName: this.skill.name,
364
367
  name: tool2.name,
@@ -366,18 +369,32 @@ var McpSkillManager = class extends BaseSkillManager {
366
369
  inputSchema: tool2.inputSchema,
367
370
  interactive: false
368
371
  }));
372
+ if (this._eventListener && timingInfo) {
373
+ const totalDurationMs = Date.now() - timingInfo.startTime;
374
+ const event = createRuntimeEvent("skillConnected", this._jobId, this._runId, {
375
+ skillName: this.skill.name,
376
+ serverInfo: timingInfo.serverInfo,
377
+ spawnDurationMs: timingInfo.spawnDurationMs,
378
+ handshakeDurationMs: timingInfo.handshakeDurationMs,
379
+ toolDiscoveryDurationMs,
380
+ connectDurationMs: timingInfo.spawnDurationMs + timingInfo.handshakeDurationMs,
381
+ totalDurationMs
382
+ });
383
+ this._eventListener(event);
384
+ }
369
385
  }
370
386
  async _initStdio(skill) {
371
387
  if (!skill.command) {
372
388
  throw new Error(`Skill ${skill.name} has no command`);
373
389
  }
374
- const env = { ...process.env };
390
+ const requiredEnv = {};
375
391
  for (const envName of skill.requiredEnv) {
376
392
  if (!this._env[envName]) {
377
393
  throw new Error(`Skill ${skill.name} requires environment variable ${envName}`);
378
394
  }
379
- env[envName] = this._env[envName];
395
+ requiredEnv[envName] = this._env[envName];
380
396
  }
397
+ const env = getFilteredEnv(requiredEnv);
381
398
  const startTime = Date.now();
382
399
  const { command, args } = this._getCommandArgs(skill);
383
400
  if (this._eventListener) {
@@ -389,6 +406,7 @@ var McpSkillManager = class extends BaseSkillManager {
389
406
  this._eventListener(event);
390
407
  }
391
408
  const transport = new StdioClientTransport({ command, args, env, stderr: "pipe" });
409
+ const spawnDurationMs = Date.now() - startTime;
392
410
  if (transport.stderr) {
393
411
  transport.stderr.on("data", (chunk) => {
394
412
  if (this._eventListener) {
@@ -402,25 +420,57 @@ var McpSkillManager = class extends BaseSkillManager {
402
420
  }
403
421
  const connectStartTime = Date.now();
404
422
  await this._mcpClient.connect(transport);
405
- const connectTime = Date.now();
406
- if (this._eventListener) {
407
- const serverInfo = this._mcpClient.getServerVersion();
408
- const event = createRuntimeEvent("skillConnected", this._jobId, this._runId, {
409
- skillName: skill.name,
410
- serverInfo: serverInfo ? { name: serverInfo.name, version: serverInfo.version } : void 0,
411
- connectDurationMs: connectTime - connectStartTime,
412
- totalDurationMs: connectTime - startTime
413
- });
414
- this._eventListener(event);
415
- }
423
+ const handshakeDurationMs = Date.now() - connectStartTime;
424
+ const serverVersion = this._mcpClient.getServerVersion();
425
+ return {
426
+ startTime,
427
+ spawnDurationMs,
428
+ handshakeDurationMs,
429
+ serverInfo: serverVersion ? { name: serverVersion.name, version: serverVersion.version } : void 0
430
+ };
416
431
  }
417
432
  async _initSse(skill) {
418
433
  if (!skill.endpoint) {
419
434
  throw new Error(`Skill ${skill.name} has no endpoint`);
420
435
  }
421
- const transport = new SSEClientTransport(new URL(skill.endpoint));
436
+ const url = new URL(skill.endpoint);
437
+ if (url.protocol !== "https:") {
438
+ throw new Error(`Skill ${skill.name} SSE endpoint must use HTTPS: ${skill.endpoint}`);
439
+ }
440
+ if (this._isPrivateOrLocalIP(url.hostname)) {
441
+ throw new Error(
442
+ `Skill ${skill.name} SSE endpoint cannot use private/local IP: ${skill.endpoint}`
443
+ );
444
+ }
445
+ const transport = new SSEClientTransport(url);
422
446
  await this._mcpClient.connect(transport);
423
447
  }
448
+ _isPrivateOrLocalIP(hostname) {
449
+ if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "0.0.0.0") {
450
+ return true;
451
+ }
452
+ const ipv4Match = hostname.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
453
+ if (ipv4Match) {
454
+ const [, a, b] = ipv4Match.map(Number);
455
+ if (a === 10) return true;
456
+ if (a === 172 && b >= 16 && b <= 31) return true;
457
+ if (a === 192 && b === 168) return true;
458
+ if (a === 169 && b === 254) return true;
459
+ if (a === 127) return true;
460
+ }
461
+ if (hostname.includes(":")) {
462
+ if (hostname.startsWith("fe80:") || hostname.startsWith("fc") || hostname.startsWith("fd")) {
463
+ return true;
464
+ }
465
+ }
466
+ if (hostname.startsWith("::ffff:")) {
467
+ const ipv4Part = hostname.slice(7);
468
+ if (this._isPrivateOrLocalIP(ipv4Part)) {
469
+ return true;
470
+ }
471
+ }
472
+ return false;
473
+ }
424
474
  _getCommandArgs(skill) {
425
475
  const { name, command, packageName, args } = skill;
426
476
  if (!packageName && (!args || args.length === 0)) {
@@ -635,12 +685,140 @@ async function getToolSet(skillManagers) {
635
685
  }
636
686
  return tools;
637
687
  }
688
+ function isFileInfo(value) {
689
+ 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";
690
+ }
691
+ async function processFileToolResult(toolResult, toolName) {
692
+ const processedContents = [];
693
+ for (const part of toolResult.result) {
694
+ if (part.type !== "textPart") {
695
+ processedContents.push(part);
696
+ continue;
697
+ }
698
+ let fileInfo;
699
+ try {
700
+ const parsed = JSON.parse(part.text);
701
+ if (isFileInfo(parsed)) {
702
+ fileInfo = parsed;
703
+ }
704
+ } catch {
705
+ processedContents.push(part);
706
+ continue;
707
+ }
708
+ if (!fileInfo) {
709
+ processedContents.push(part);
710
+ continue;
711
+ }
712
+ const { path, mimeType } = fileInfo;
713
+ try {
714
+ const buffer = await readFile(path);
715
+ if (toolName === "readImageFile") {
716
+ processedContents.push({
717
+ type: "imageInlinePart",
718
+ id: part.id,
719
+ encodedData: buffer.toString("base64"),
720
+ mimeType
721
+ });
722
+ } else {
723
+ processedContents.push({
724
+ type: "fileInlinePart",
725
+ id: part.id,
726
+ encodedData: buffer.toString("base64"),
727
+ mimeType
728
+ });
729
+ }
730
+ } catch (error) {
731
+ processedContents.push({
732
+ type: "textPart",
733
+ id: part.id,
734
+ text: `Failed to read file "${path}": ${error instanceof Error ? error.message : String(error)}`
735
+ });
736
+ }
737
+ }
738
+ return { ...toolResult, result: processedContents };
739
+ }
740
+ var McpToolExecutor = class {
741
+ type = "mcp";
742
+ async execute(toolCall, skillManagers) {
743
+ const skillManager = await getSkillManagerByToolName(skillManagers, toolCall.toolName);
744
+ if (skillManager.type !== "mcp") {
745
+ throw new Error(`Incorrect SkillType, required MCP, got ${skillManager.type}`);
746
+ }
747
+ const result = await skillManager.callTool(
748
+ toolCall.toolName,
749
+ toolCall.args
750
+ );
751
+ const toolResult = {
752
+ id: toolCall.id,
753
+ skillName: toolCall.skillName,
754
+ toolName: toolCall.toolName,
755
+ result
756
+ };
757
+ if (toolCall.toolName === "readPdfFile" || toolCall.toolName === "readImageFile") {
758
+ return processFileToolResult(toolResult, toolCall.toolName);
759
+ }
760
+ return toolResult;
761
+ }
762
+ };
638
763
 
639
- // src/state-machine/states/calling-delegate.ts
640
- async function getToolType(toolName, skillManagers) {
764
+ // src/tool-execution/executor-factory.ts
765
+ var ToolExecutorFactory = class {
766
+ executors;
767
+ constructor() {
768
+ this.executors = /* @__PURE__ */ new Map([
769
+ ["mcp", new McpToolExecutor()]
770
+ // delegate and interactive are handled specially (not executed here)
771
+ ]);
772
+ }
773
+ /**
774
+ * Get the executor for a given skill type
775
+ */
776
+ getExecutor(type) {
777
+ return this.executors.get(type);
778
+ }
779
+ /**
780
+ * Execute a tool call using the appropriate executor
781
+ */
782
+ async execute(toolCall, type, skillManagers) {
783
+ const executor = this.executors.get(type);
784
+ if (!executor) {
785
+ throw new Error(`No executor registered for skill type: ${type}`);
786
+ }
787
+ return executor.execute(toolCall, skillManagers);
788
+ }
789
+ /**
790
+ * Check if a skill type can be executed locally (vs requiring delegation)
791
+ */
792
+ canExecuteLocally(type) {
793
+ return type === "mcp";
794
+ }
795
+ };
796
+ var toolExecutorFactory = new ToolExecutorFactory();
797
+
798
+ // src/tool-execution/tool-classifier.ts
799
+ async function getToolTypeByName(toolName, skillManagers) {
641
800
  const skillManager = await getSkillManagerByToolName(skillManagers, toolName);
642
801
  return skillManager.type;
643
802
  }
803
+ async function classifyToolCalls(toolCalls, skillManagers) {
804
+ const classified = {
805
+ mcp: [],
806
+ delegate: [],
807
+ interactive: []
808
+ };
809
+ const results = await Promise.all(
810
+ toolCalls.map(async (toolCall) => {
811
+ const skillManager = await getSkillManagerByToolName(skillManagers, toolCall.toolName);
812
+ return { toolCall, type: skillManager.type, skillManager };
813
+ })
814
+ );
815
+ for (const result of results) {
816
+ classified[result.type].push(result);
817
+ }
818
+ return classified;
819
+ }
820
+
821
+ // src/state-machine/states/calling-delegate.ts
644
822
  async function callingDelegateLogic({
645
823
  setting,
646
824
  checkpoint,
@@ -653,7 +831,7 @@ async function callingDelegateLogic({
653
831
  const toolCallTypes = await Promise.all(
654
832
  step.pendingToolCalls.map(async (tc) => ({
655
833
  toolCall: tc,
656
- type: await getToolType(tc.toolName, skillManagers)
834
+ type: await getToolTypeByName(tc.toolName, skillManagers)
657
835
  }))
658
836
  );
659
837
  const delegateToolCalls = toolCallTypes.filter((t) => t.type === "delegate").map((t) => t.toolCall);
@@ -731,79 +909,6 @@ function hasRemainingTodos(toolResult) {
731
909
  return false;
732
910
  }
733
911
  }
734
- function isFileInfo(value) {
735
- 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";
736
- }
737
- async function processFileToolResult(toolResult, toolName) {
738
- const processedContents = [];
739
- for (const part of toolResult.result) {
740
- if (part.type !== "textPart") {
741
- processedContents.push(part);
742
- continue;
743
- }
744
- let fileInfo;
745
- try {
746
- const parsed = JSON.parse(part.text);
747
- if (isFileInfo(parsed)) {
748
- fileInfo = parsed;
749
- }
750
- } catch {
751
- processedContents.push(part);
752
- continue;
753
- }
754
- if (!fileInfo) {
755
- processedContents.push(part);
756
- continue;
757
- }
758
- const { path, mimeType } = fileInfo;
759
- try {
760
- const buffer = await readFile(path);
761
- if (toolName === "readImageFile") {
762
- processedContents.push({
763
- type: "imageInlinePart",
764
- id: part.id,
765
- encodedData: buffer.toString("base64"),
766
- mimeType
767
- });
768
- } else {
769
- processedContents.push({
770
- type: "fileInlinePart",
771
- id: part.id,
772
- encodedData: buffer.toString("base64"),
773
- mimeType
774
- });
775
- }
776
- } catch (error) {
777
- processedContents.push({
778
- type: "textPart",
779
- id: part.id,
780
- text: `Failed to read file "${path}": ${error instanceof Error ? error.message : String(error)}`
781
- });
782
- }
783
- }
784
- return { ...toolResult, result: processedContents };
785
- }
786
- async function executeMcpToolCall(toolCall, skillManagers) {
787
- const skillManager = await getSkillManagerByToolName(skillManagers, toolCall.toolName);
788
- if (skillManager.type !== "mcp") {
789
- throw new Error(`Incorrect SkillType, required MCP, got ${skillManager.type}`);
790
- }
791
- const result = await skillManager.callTool(toolCall.toolName, toolCall.args);
792
- const toolResult = {
793
- id: toolCall.id,
794
- skillName: toolCall.skillName,
795
- toolName: toolCall.toolName,
796
- result
797
- };
798
- if (toolCall.toolName === "readPdfFile" || toolCall.toolName === "readImageFile") {
799
- return processFileToolResult(toolResult, toolCall.toolName);
800
- }
801
- return toolResult;
802
- }
803
- async function getToolType2(toolCall, skillManagers) {
804
- const skillManager = await getSkillManagerByToolName(skillManagers, toolCall.toolName);
805
- return skillManager.type;
806
- }
807
912
  async function callingToolLogic({
808
913
  setting,
809
914
  checkpoint,
@@ -819,28 +924,26 @@ async function callingToolLogic({
819
924
  (tc) => tc.skillName === "@perstack/base" && tc.toolName === "attemptCompletion"
820
925
  );
821
926
  if (attemptCompletionTool) {
822
- const toolResult = await executeMcpToolCall(attemptCompletionTool, skillManagers);
927
+ const toolResult = await toolExecutorFactory.execute(
928
+ attemptCompletionTool,
929
+ "mcp",
930
+ skillManagers
931
+ );
823
932
  if (hasRemainingTodos(toolResult)) {
824
933
  return resolveToolResults(setting, checkpoint, { toolResults: [toolResult] });
825
934
  }
826
935
  return attemptCompletion(setting, checkpoint, { toolResult });
827
936
  }
828
- const toolCallTypes = await Promise.all(
829
- pendingToolCalls.map(async (tc) => ({
830
- toolCall: tc,
831
- type: await getToolType2(tc, skillManagers)
832
- }))
833
- );
834
- const mcpToolCalls = toolCallTypes.filter((t) => t.type === "mcp").map((t) => t.toolCall);
835
- const delegateToolCalls = toolCallTypes.filter((t) => t.type === "delegate").map((t) => t.toolCall);
836
- const interactiveToolCalls = toolCallTypes.filter((t) => t.type === "interactive").map((t) => t.toolCall);
837
- if (mcpToolCalls.length > 0) {
937
+ const classified = await classifyToolCalls(pendingToolCalls, skillManagers);
938
+ if (classified.mcp.length > 0) {
838
939
  const mcpResults = await Promise.all(
839
- mcpToolCalls.map((tc) => executeMcpToolCall(tc, skillManagers))
940
+ classified.mcp.map((c) => toolExecutorFactory.execute(c.toolCall, "mcp", skillManagers))
840
941
  );
841
942
  toolResults.push(...mcpResults);
842
943
  }
843
- if (delegateToolCalls.length > 0) {
944
+ if (classified.delegate.length > 0) {
945
+ const delegateToolCalls = classified.delegate.map((c) => c.toolCall);
946
+ const interactiveToolCalls = classified.interactive.map((c) => c.toolCall);
844
947
  step.partialToolResults = toolResults;
845
948
  step.pendingToolCalls = [...delegateToolCalls, ...interactiveToolCalls];
846
949
  return callDelegate(setting, checkpoint, {
@@ -849,11 +952,12 @@ async function callingToolLogic({
849
952
  usage: step.usage
850
953
  });
851
954
  }
852
- if (interactiveToolCalls.length > 0) {
853
- const interactiveToolCall = interactiveToolCalls[0];
955
+ if (classified.interactive.length > 0) {
956
+ const interactiveToolCall = classified.interactive[0]?.toolCall;
854
957
  if (!interactiveToolCall) {
855
958
  throw new Error("No interactive tool call found");
856
959
  }
960
+ const interactiveToolCalls = classified.interactive.map((c) => c.toolCall);
857
961
  step.partialToolResults = toolResults;
858
962
  step.pendingToolCalls = interactiveToolCalls;
859
963
  return callInteractiveTool(setting, checkpoint, {
@@ -1162,7 +1266,7 @@ async function generatingRunResultLogic({
1162
1266
  usage
1163
1267
  });
1164
1268
  }
1165
- async function classifyToolCalls(toolCalls, skillManagers) {
1269
+ async function classifyToolCalls2(toolCalls, skillManagers) {
1166
1270
  return Promise.all(
1167
1271
  toolCalls.map(async (tc) => {
1168
1272
  const skillManager = await getSkillManagerByToolName(skillManagers, tc.toolName);
@@ -1239,7 +1343,7 @@ async function generatingToolCallLogic({
1239
1343
  usage
1240
1344
  });
1241
1345
  }
1242
- const classified = await classifyToolCalls(toolCalls, skillManagers);
1346
+ const classified = await classifyToolCalls2(toolCalls, skillManagers);
1243
1347
  const sorted = sortToolCallsByPriority(classified);
1244
1348
  if (finishReason === "tool-calls" || finishReason === "stop") {
1245
1349
  const toolCallParts = buildToolCallParts(sorted);
@@ -1927,29 +2031,6 @@ async function executeStateMachine(params) {
1927
2031
  runActor.start();
1928
2032
  });
1929
2033
  }
1930
- var RunEventEmitter = class {
1931
- listeners = [];
1932
- subscribe(listener) {
1933
- this.listeners.push(listener);
1934
- }
1935
- async emit(event) {
1936
- const errors = [];
1937
- for (const listener of this.listeners) {
1938
- try {
1939
- await listener({
1940
- ...event,
1941
- id: createId(),
1942
- timestamp: Date.now()
1943
- });
1944
- } catch (error) {
1945
- errors.push(error instanceof Error ? error : new Error(String(error)));
1946
- }
1947
- }
1948
- if (errors.length > 0) {
1949
- throw new AggregateError(errors, "One or more event listeners failed");
1950
- }
1951
- }
1952
- };
1953
2034
 
1954
2035
  // src/helpers/checkpoint.ts
1955
2036
  function createInitialCheckpoint(checkpointId, params) {
@@ -2013,46 +2094,6 @@ function buildDelegationReturnState(currentSetting, resultCheckpoint, parentChec
2013
2094
  }
2014
2095
  };
2015
2096
  }
2016
- function buildDelegateToState(currentSetting, resultCheckpoint, currentExpert) {
2017
- const { delegateTo } = resultCheckpoint;
2018
- if (!delegateTo || delegateTo.length === 0) {
2019
- throw new Error("delegateTo is required for buildDelegateToState");
2020
- }
2021
- const firstDelegation = delegateTo[0];
2022
- const { expert, toolCallId, toolName, query } = firstDelegation;
2023
- return {
2024
- setting: {
2025
- ...currentSetting,
2026
- expertKey: expert.key,
2027
- input: {
2028
- text: query
2029
- }
2030
- },
2031
- checkpoint: {
2032
- ...resultCheckpoint,
2033
- status: "init",
2034
- messages: [],
2035
- expert: {
2036
- key: expert.key,
2037
- name: expert.name,
2038
- version: expert.version
2039
- },
2040
- delegatedBy: {
2041
- expert: {
2042
- key: currentExpert.key,
2043
- name: currentExpert.name,
2044
- version: currentExpert.version
2045
- },
2046
- toolCallId,
2047
- toolName,
2048
- checkpointId: resultCheckpoint.id
2049
- },
2050
- usage: resultCheckpoint.usage,
2051
- pendingToolCalls: void 0,
2052
- partialToolResults: void 0
2053
- }
2054
- };
2055
- }
2056
2097
  async function resolveExpertToRun(expertKey, experts, clientOptions) {
2057
2098
  if (experts[expertKey]) {
2058
2099
  return experts[expertKey];
@@ -2102,68 +2143,245 @@ async function setupExperts(setting, resolveExpertToRun2 = resolveExpertToRun) {
2102
2143
  }
2103
2144
  return { expertToRun, experts };
2104
2145
  }
2105
-
2106
- // src/run.ts
2107
- var noopStoreCheckpoint = async () => {
2108
- };
2109
- var noopStoreEvent = async () => {
2110
- };
2111
- var noopStoreJob = () => {
2146
+ var SingleDelegationStrategy = class {
2147
+ async execute(delegations, setting, context, parentExpert, _runFn, _parentOptions) {
2148
+ if (delegations.length !== 1) {
2149
+ throw new Error("SingleDelegationStrategy requires exactly one delegation");
2150
+ }
2151
+ const delegation = delegations[0];
2152
+ const { expert, toolCallId, toolName, query } = delegation;
2153
+ const nextSetting = {
2154
+ ...setting,
2155
+ expertKey: expert.key,
2156
+ input: { text: query }
2157
+ };
2158
+ const nextCheckpoint = {
2159
+ id: context.id,
2160
+ jobId: setting.jobId,
2161
+ runId: setting.runId,
2162
+ status: "init",
2163
+ stepNumber: context.stepNumber,
2164
+ messages: [],
2165
+ // Child starts fresh
2166
+ expert: {
2167
+ key: expert.key,
2168
+ name: expert.name,
2169
+ version: expert.version
2170
+ },
2171
+ delegatedBy: {
2172
+ expert: {
2173
+ key: parentExpert.key,
2174
+ name: parentExpert.name,
2175
+ version: parentExpert.version
2176
+ },
2177
+ toolCallId,
2178
+ toolName,
2179
+ checkpointId: context.id
2180
+ },
2181
+ usage: context.usage,
2182
+ contextWindow: context.contextWindow,
2183
+ pendingToolCalls: void 0,
2184
+ partialToolResults: void 0
2185
+ };
2186
+ return { nextSetting, nextCheckpoint };
2187
+ }
2112
2188
  };
2113
- var noopRetrieveJob = () => void 0;
2114
- var noopRetrieveCheckpoint = async (_jobId, _checkpointId) => {
2115
- throw new Error("retrieveCheckpoint not provided");
2189
+ var ParallelDelegationStrategy = class {
2190
+ async execute(delegations, setting, context, parentExpert, runFn, parentOptions) {
2191
+ if (delegations.length < 2) {
2192
+ throw new Error("ParallelDelegationStrategy requires at least two delegations");
2193
+ }
2194
+ const [firstDelegation, ...remainingDelegations] = delegations;
2195
+ if (!firstDelegation) {
2196
+ throw new Error("No delegations found");
2197
+ }
2198
+ const allResults = await Promise.all(
2199
+ delegations.map(
2200
+ (delegation) => this.executeSingleDelegation(
2201
+ delegation,
2202
+ setting,
2203
+ context,
2204
+ parentExpert,
2205
+ runFn,
2206
+ parentOptions
2207
+ )
2208
+ )
2209
+ );
2210
+ const [firstResult, ...restResults] = allResults;
2211
+ if (!firstResult) {
2212
+ throw new Error("No delegation results");
2213
+ }
2214
+ const aggregatedUsage = allResults.reduce(
2215
+ (acc, result) => sumUsage(acc, result.deltaUsage),
2216
+ context.usage
2217
+ );
2218
+ const maxStepNumber = Math.max(...allResults.map((r) => r.stepNumber));
2219
+ const restToolResults = restResults.map((result) => ({
2220
+ id: result.toolCallId,
2221
+ skillName: `delegate/${result.expertKey}`,
2222
+ toolName: result.toolName,
2223
+ result: [{ type: "textPart", id: createId(), text: result.text }]
2224
+ }));
2225
+ const processedToolCallIds = new Set(remainingDelegations.map((d) => d.toolCallId));
2226
+ const remainingPendingToolCalls = context.pendingToolCalls?.filter(
2227
+ (tc) => !processedToolCallIds.has(tc.id) && tc.id !== firstDelegation.toolCallId
2228
+ );
2229
+ const nextSetting = {
2230
+ ...setting,
2231
+ expertKey: parentExpert.key,
2232
+ input: {
2233
+ interactiveToolCallResult: {
2234
+ toolCallId: firstResult.toolCallId,
2235
+ toolName: firstResult.toolName,
2236
+ skillName: `delegate/${firstResult.expertKey}`,
2237
+ text: firstResult.text
2238
+ }
2239
+ }
2240
+ };
2241
+ const nextCheckpoint = {
2242
+ id: context.id,
2243
+ jobId: setting.jobId,
2244
+ runId: setting.runId,
2245
+ status: "stoppedByDelegate",
2246
+ stepNumber: maxStepNumber,
2247
+ messages: context.messages,
2248
+ // Restore parent's conversation history
2249
+ expert: {
2250
+ key: parentExpert.key,
2251
+ name: parentExpert.name,
2252
+ version: parentExpert.version
2253
+ },
2254
+ usage: aggregatedUsage,
2255
+ contextWindow: context.contextWindow,
2256
+ delegatedBy: context.delegatedBy,
2257
+ // Preserve parent reference for nested delegations
2258
+ delegateTo: void 0,
2259
+ pendingToolCalls: remainingPendingToolCalls?.length ? remainingPendingToolCalls : void 0,
2260
+ partialToolResults: [...context.partialToolResults ?? [], ...restToolResults]
2261
+ };
2262
+ return { nextSetting, nextCheckpoint };
2263
+ }
2264
+ async executeSingleDelegation(delegation, parentSetting, parentContext, parentExpert, runFn, parentOptions) {
2265
+ const { expert, toolCallId, toolName, query } = delegation;
2266
+ const delegateRunId = createId();
2267
+ const delegateSetting = {
2268
+ ...parentSetting,
2269
+ runId: delegateRunId,
2270
+ expertKey: expert.key,
2271
+ input: { text: query }
2272
+ };
2273
+ const delegateCheckpoint = {
2274
+ id: createId(),
2275
+ jobId: parentSetting.jobId,
2276
+ runId: delegateRunId,
2277
+ status: "init",
2278
+ stepNumber: parentContext.stepNumber,
2279
+ messages: [],
2280
+ // Child starts fresh - no parent context inheritance
2281
+ expert: {
2282
+ key: expert.key,
2283
+ name: expert.name,
2284
+ version: expert.version
2285
+ },
2286
+ delegatedBy: {
2287
+ expert: {
2288
+ key: parentExpert.key,
2289
+ name: parentExpert.name,
2290
+ version: parentExpert.version
2291
+ },
2292
+ toolCallId,
2293
+ toolName,
2294
+ checkpointId: parentContext.id
2295
+ },
2296
+ usage: createEmptyUsage(),
2297
+ contextWindow: parentContext.contextWindow
2298
+ };
2299
+ const resultCheckpoint = await runFn(
2300
+ { setting: delegateSetting, checkpoint: delegateCheckpoint },
2301
+ { ...parentOptions, returnOnDelegationComplete: true }
2302
+ );
2303
+ return this.extractDelegationResult(resultCheckpoint, toolCallId, toolName, expert.key);
2304
+ }
2305
+ extractDelegationResult(checkpoint, toolCallId, toolName, expertKey) {
2306
+ const lastMessage = checkpoint.messages[checkpoint.messages.length - 1];
2307
+ if (!lastMessage || lastMessage.type !== "expertMessage") {
2308
+ throw new Error("Delegation error: delegation result message is incorrect");
2309
+ }
2310
+ const textPart = lastMessage.contents.find((c) => c.type === "textPart");
2311
+ if (!textPart || textPart.type !== "textPart") {
2312
+ throw new Error("Delegation error: delegation result message does not contain text");
2313
+ }
2314
+ return {
2315
+ toolCallId,
2316
+ toolName,
2317
+ expertKey,
2318
+ text: textPart.text,
2319
+ stepNumber: checkpoint.stepNumber,
2320
+ deltaUsage: checkpoint.usage
2321
+ };
2322
+ }
2116
2323
  };
2117
- var defaultCreateJob = (jobId, expertKey, maxSteps) => ({
2118
- id: jobId,
2119
- coordinatorExpertKey: expertKey,
2120
- status: "running",
2121
- totalSteps: 0,
2122
- startedAt: Date.now(),
2123
- maxSteps,
2124
- usage: createEmptyUsage()
2125
- });
2126
- async function run(runInput, options) {
2127
- const runParams = runParamsSchema.parse(runInput);
2128
- const storeCheckpoint = options?.storeCheckpoint ?? noopStoreCheckpoint;
2129
- const storeEvent = options?.storeEvent ?? noopStoreEvent;
2130
- const storeJob = options?.storeJob ?? noopStoreJob;
2131
- const retrieveJob = options?.retrieveJob ?? noopRetrieveJob;
2132
- const retrieveCheckpoint = options?.retrieveCheckpoint ?? noopRetrieveCheckpoint;
2133
- const createJob = options?.createJob ?? defaultCreateJob;
2134
- const eventListener = createEventListener(options?.eventListener, storeEvent);
2135
- const eventEmitter = new RunEventEmitter();
2136
- eventEmitter.subscribe(eventListener);
2137
- let { setting, checkpoint } = runParams;
2138
- const contextWindow = getContextWindow(setting.providerConfig.providerName, setting.model);
2139
- let job = retrieveJob(setting.jobId) ?? createJob(setting.jobId, setting.expertKey, setting.maxSteps);
2140
- if (job.status !== "running") {
2141
- job = { ...job, status: "running", finishedAt: void 0 };
2324
+ function selectDelegationStrategy(delegationCount) {
2325
+ if (delegationCount === 1) {
2326
+ return new SingleDelegationStrategy();
2142
2327
  }
2143
- storeJob(job);
2144
- while (true) {
2145
- const { expertToRun, experts } = await setupExperts(setting, options?.resolveExpertToRun);
2146
- if (options?.eventListener) {
2147
- const initEvent = createRuntimeEvent("initializeRuntime", setting.jobId, setting.runId, {
2148
- runtimeVersion: package_default.version,
2149
- runtime: "perstack",
2150
- expertName: expertToRun.name,
2151
- experts: Object.keys(experts),
2152
- model: setting.model,
2153
- temperature: setting.temperature,
2154
- maxSteps: setting.maxSteps,
2155
- maxRetries: setting.maxRetries,
2156
- timeout: setting.timeout,
2157
- query: setting.input.text,
2158
- interactiveToolCall: setting.input.interactiveToolCallResult
2159
- });
2160
- options.eventListener(initEvent);
2328
+ return new ParallelDelegationStrategy();
2329
+ }
2330
+ function buildReturnFromDelegation(currentSetting, resultCheckpoint, parentCheckpoint) {
2331
+ return buildDelegationReturnState(currentSetting, resultCheckpoint, parentCheckpoint);
2332
+ }
2333
+ function extractDelegationContext(checkpoint) {
2334
+ return {
2335
+ id: checkpoint.id,
2336
+ stepNumber: checkpoint.stepNumber,
2337
+ contextWindow: checkpoint.contextWindow,
2338
+ usage: checkpoint.usage,
2339
+ pendingToolCalls: checkpoint.pendingToolCalls,
2340
+ partialToolResults: checkpoint.partialToolResults,
2341
+ delegatedBy: checkpoint.delegatedBy,
2342
+ // Preserve for nested delegations
2343
+ messages: checkpoint.messages
2344
+ // Preserve for parent continuation after delegation
2345
+ };
2346
+ }
2347
+ var RunEventEmitter = class {
2348
+ listeners = [];
2349
+ subscribe(listener) {
2350
+ this.listeners.push(listener);
2351
+ }
2352
+ async emit(event) {
2353
+ const errors = [];
2354
+ for (const listener of this.listeners) {
2355
+ try {
2356
+ await listener({
2357
+ ...event,
2358
+ id: createId(),
2359
+ timestamp: Date.now()
2360
+ });
2361
+ } catch (error) {
2362
+ errors.push(error instanceof Error ? error : new Error(String(error)));
2363
+ }
2161
2364
  }
2365
+ if (errors.length > 0) {
2366
+ throw new AggregateError(errors, "One or more event listeners failed");
2367
+ }
2368
+ }
2369
+ };
2370
+
2371
+ // src/orchestration/single-run-executor.ts
2372
+ var SingleRunExecutor = class {
2373
+ constructor(options = {}) {
2374
+ this.options = options;
2375
+ }
2376
+ async execute(setting, checkpoint) {
2377
+ const contextWindow = getContextWindow(setting.providerConfig.providerName, setting.model);
2378
+ const { expertToRun, experts } = await setupExperts(setting, this.options.resolveExpertToRun);
2379
+ this.emitInitEvent(setting, expertToRun, experts);
2162
2380
  const skillManagers = await getSkillManagers(
2163
2381
  expertToRun,
2164
2382
  experts,
2165
2383
  setting,
2166
- options?.eventListener,
2384
+ this.options.eventListener,
2167
2385
  { isDelegatedRun: !!checkpoint?.delegatedBy }
2168
2386
  );
2169
2387
  const initialCheckpoint = checkpoint ? createNextStepCheckpoint(createId(), checkpoint) : createInitialCheckpoint(createId(), {
@@ -2173,183 +2391,148 @@ async function run(runInput, options) {
2173
2391
  expert: expertToRun,
2174
2392
  contextWindow
2175
2393
  });
2176
- const runResultCheckpoint = await executeStateMachine({
2394
+ const eventEmitter = new RunEventEmitter();
2395
+ const eventListener = this.createEventListener();
2396
+ eventEmitter.subscribe(eventListener);
2397
+ const resultCheckpoint = await executeStateMachine({
2177
2398
  setting: { ...setting, experts },
2178
2399
  initialCheckpoint,
2179
2400
  eventListener,
2180
2401
  skillManagers,
2181
2402
  eventEmitter,
2182
- storeCheckpoint,
2183
- shouldContinueRun: options?.shouldContinueRun
2403
+ storeCheckpoint: this.options.storeCheckpoint ?? (async () => {
2404
+ }),
2405
+ shouldContinueRun: this.options.shouldContinueRun
2184
2406
  });
2407
+ return { checkpoint: resultCheckpoint, expertToRun, experts };
2408
+ }
2409
+ createEventListener() {
2410
+ const userListener = this.options.eventListener;
2411
+ const storeEvent = this.options.storeEvent;
2412
+ return async (event) => {
2413
+ if ("stepNumber" in event && storeEvent) {
2414
+ await storeEvent(event);
2415
+ }
2416
+ userListener?.(event);
2417
+ };
2418
+ }
2419
+ emitInitEvent(setting, expertToRun, experts) {
2420
+ if (!this.options.eventListener) return;
2421
+ const initEvent = createRuntimeEvent("initializeRuntime", setting.jobId, setting.runId, {
2422
+ runtimeVersion: package_default.version,
2423
+ runtime: "local",
2424
+ expertName: expertToRun.name,
2425
+ experts: Object.keys(experts),
2426
+ model: setting.model,
2427
+ temperature: setting.temperature,
2428
+ maxSteps: setting.maxSteps,
2429
+ maxRetries: setting.maxRetries,
2430
+ timeout: setting.timeout,
2431
+ query: setting.input.text,
2432
+ interactiveToolCall: setting.input.interactiveToolCallResult
2433
+ });
2434
+ this.options.eventListener(initEvent);
2435
+ }
2436
+ };
2437
+
2438
+ // src/run.ts
2439
+ var defaultCreateJob = (jobId, expertKey, maxSteps) => ({
2440
+ id: jobId,
2441
+ coordinatorExpertKey: expertKey,
2442
+ status: "running",
2443
+ totalSteps: 0,
2444
+ startedAt: Date.now(),
2445
+ maxSteps,
2446
+ usage: createEmptyUsage()
2447
+ });
2448
+ async function run(runInput, options) {
2449
+ const runParams = runParamsSchema.parse(runInput);
2450
+ let { setting, checkpoint } = runParams;
2451
+ const storeJob = options?.storeJob ?? (() => {
2452
+ });
2453
+ const retrieveJob = options?.retrieveJob ?? (() => void 0);
2454
+ const retrieveCheckpoint = options?.retrieveCheckpoint ?? (async () => {
2455
+ throw new Error("retrieveCheckpoint not provided");
2456
+ });
2457
+ const createJob = options?.createJob ?? defaultCreateJob;
2458
+ let job = retrieveJob(setting.jobId) ?? createJob(setting.jobId, setting.expertKey, setting.maxSteps);
2459
+ if (job.status !== "running") {
2460
+ job = { ...job, status: "running", finishedAt: void 0 };
2461
+ }
2462
+ storeJob(job);
2463
+ const runExecutor = new SingleRunExecutor({
2464
+ shouldContinueRun: options?.shouldContinueRun,
2465
+ storeCheckpoint: options?.storeCheckpoint,
2466
+ storeEvent: options?.storeEvent,
2467
+ eventListener: options?.eventListener,
2468
+ resolveExpertToRun: options?.resolveExpertToRun
2469
+ });
2470
+ while (true) {
2471
+ const runResult = await runExecutor.execute(setting, checkpoint);
2472
+ const resultCheckpoint = runResult.checkpoint;
2185
2473
  job = {
2186
2474
  ...job,
2187
- totalSteps: runResultCheckpoint.stepNumber,
2188
- usage: runResultCheckpoint.usage
2475
+ totalSteps: resultCheckpoint.stepNumber,
2476
+ usage: resultCheckpoint.usage
2189
2477
  };
2190
- switch (runResultCheckpoint.status) {
2478
+ switch (resultCheckpoint.status) {
2191
2479
  case "completed": {
2192
2480
  if (options?.returnOnDelegationComplete) {
2193
2481
  storeJob(job);
2194
- return runResultCheckpoint;
2482
+ return resultCheckpoint;
2195
2483
  }
2196
- if (runResultCheckpoint.delegatedBy) {
2484
+ if (resultCheckpoint.delegatedBy) {
2197
2485
  storeJob(job);
2198
2486
  const parentCheckpoint = await retrieveCheckpoint(
2199
2487
  setting.jobId,
2200
- runResultCheckpoint.delegatedBy.checkpointId
2488
+ resultCheckpoint.delegatedBy.checkpointId
2201
2489
  );
2202
- const result = buildDelegationReturnState(setting, runResultCheckpoint, parentCheckpoint);
2490
+ const result = buildReturnFromDelegation(setting, resultCheckpoint, parentCheckpoint);
2203
2491
  setting = result.setting;
2204
2492
  checkpoint = result.checkpoint;
2205
2493
  break;
2206
2494
  }
2207
2495
  storeJob({ ...job, status: "completed", finishedAt: Date.now() });
2208
- return runResultCheckpoint;
2496
+ return resultCheckpoint;
2209
2497
  }
2210
2498
  case "stoppedByInteractiveTool": {
2211
2499
  storeJob({ ...job, status: "stoppedByInteractiveTool" });
2212
- return runResultCheckpoint;
2500
+ return resultCheckpoint;
2213
2501
  }
2214
2502
  case "stoppedByDelegate": {
2215
2503
  storeJob(job);
2216
- const { delegateTo } = runResultCheckpoint;
2504
+ const { delegateTo } = resultCheckpoint;
2217
2505
  if (!delegateTo || delegateTo.length === 0) {
2218
2506
  throw new Error("No delegations found in checkpoint");
2219
2507
  }
2220
- if (delegateTo.length === 1) {
2221
- const result = buildDelegateToState(setting, runResultCheckpoint, expertToRun);
2222
- setting = result.setting;
2223
- checkpoint = result.checkpoint;
2224
- break;
2225
- }
2226
- const firstDelegation = delegateTo[0];
2227
- const remainingDelegations = delegateTo.slice(1);
2228
- const [firstResult, ...restResults] = await Promise.all(
2229
- delegateTo.map(
2230
- (delegation) => runDelegate(delegation, setting, runResultCheckpoint, expertToRun, options)
2231
- )
2232
- );
2233
- const allResults = [firstResult, ...restResults];
2234
- const aggregatedUsage = allResults.reduce(
2235
- (acc, result) => sumUsage(acc, result.deltaUsage),
2236
- runResultCheckpoint.usage
2237
- );
2238
- const maxStepNumber = Math.max(...allResults.map((r) => r.stepNumber));
2239
- const restToolResults = restResults.map((result) => ({
2240
- id: result.toolCallId,
2241
- skillName: `delegate/${result.expertKey}`,
2242
- toolName: result.toolName,
2243
- result: [{ type: "textPart", id: createId(), text: result.text }]
2244
- }));
2245
- const processedToolCallIds = new Set(remainingDelegations.map((d) => d.toolCallId));
2246
- const remainingToolCalls = runResultCheckpoint.pendingToolCalls?.filter(
2247
- (tc) => !processedToolCallIds.has(tc.id) && tc.id !== firstDelegation.toolCallId
2508
+ const strategy = selectDelegationStrategy(delegateTo.length);
2509
+ const context = extractDelegationContext(resultCheckpoint);
2510
+ const delegationResult = await strategy.execute(
2511
+ delegateTo,
2512
+ setting,
2513
+ context,
2514
+ runResult.expertToRun,
2515
+ run,
2516
+ options
2248
2517
  );
2249
- setting = {
2250
- ...setting,
2251
- expertKey: expertToRun.key,
2252
- input: {
2253
- interactiveToolCallResult: {
2254
- toolCallId: firstResult.toolCallId,
2255
- toolName: firstResult.toolName,
2256
- skillName: `delegate/${firstResult.expertKey}`,
2257
- text: firstResult.text
2258
- }
2259
- }
2260
- };
2261
- checkpoint = {
2262
- ...runResultCheckpoint,
2263
- status: "stoppedByDelegate",
2264
- delegateTo: void 0,
2265
- stepNumber: maxStepNumber,
2266
- usage: aggregatedUsage,
2267
- pendingToolCalls: remainingToolCalls?.length ? remainingToolCalls : void 0,
2268
- partialToolResults: [
2269
- ...runResultCheckpoint.partialToolResults ?? [],
2270
- ...restToolResults
2271
- ]
2272
- };
2518
+ setting = delegationResult.nextSetting;
2519
+ checkpoint = delegationResult.nextCheckpoint;
2273
2520
  break;
2274
2521
  }
2275
2522
  case "stoppedByExceededMaxSteps": {
2276
2523
  storeJob({ ...job, status: "stoppedByMaxSteps", finishedAt: Date.now() });
2277
- return runResultCheckpoint;
2524
+ return resultCheckpoint;
2278
2525
  }
2279
2526
  case "stoppedByError": {
2280
2527
  storeJob({ ...job, status: "stoppedByError", finishedAt: Date.now() });
2281
- return runResultCheckpoint;
2528
+ return resultCheckpoint;
2282
2529
  }
2283
2530
  default:
2284
2531
  throw new Error("Run stopped by unknown reason");
2285
2532
  }
2286
2533
  }
2287
2534
  }
2288
- function createEventListener(userListener, storeEvent) {
2289
- const listener = userListener ?? ((e) => console.log(JSON.stringify(e)));
2290
- return async (event) => {
2291
- if ("stepNumber" in event && storeEvent) {
2292
- await storeEvent(event);
2293
- }
2294
- listener(event);
2295
- };
2296
- }
2297
- async function runDelegate(delegation, parentSetting, parentCheckpoint, parentExpert, options) {
2298
- const { expert, toolCallId, toolName, query } = delegation;
2299
- const delegateRunId = createId();
2300
- const delegateSetting = {
2301
- ...parentSetting,
2302
- runId: delegateRunId,
2303
- expertKey: expert.key,
2304
- input: { text: query }
2305
- };
2306
- const delegateCheckpoint = {
2307
- id: createId(),
2308
- jobId: parentSetting.jobId,
2309
- runId: delegateRunId,
2310
- status: "init",
2311
- stepNumber: parentCheckpoint.stepNumber,
2312
- messages: [],
2313
- expert: {
2314
- key: expert.key,
2315
- name: expert.name,
2316
- version: expert.version
2317
- },
2318
- delegatedBy: {
2319
- expert: {
2320
- key: parentExpert.key,
2321
- name: parentExpert.name,
2322
- version: parentExpert.version
2323
- },
2324
- toolCallId,
2325
- toolName,
2326
- checkpointId: parentCheckpoint.id
2327
- },
2328
- usage: createEmptyUsage(),
2329
- contextWindow: parentCheckpoint.contextWindow
2330
- };
2331
- const resultCheckpoint = await run(
2332
- { setting: delegateSetting, checkpoint: delegateCheckpoint },
2333
- { ...options, returnOnDelegationComplete: true }
2334
- );
2335
- const lastMessage = resultCheckpoint.messages[resultCheckpoint.messages.length - 1];
2336
- if (!lastMessage || lastMessage.type !== "expertMessage") {
2337
- throw new Error("Delegation error: delegation result message is incorrect");
2338
- }
2339
- const textPart = lastMessage.contents.find((c) => c.type === "textPart");
2340
- if (!textPart || textPart.type !== "textPart") {
2341
- throw new Error("Delegation error: delegation result message does not contain text");
2342
- }
2343
- return {
2344
- toolCallId,
2345
- toolName,
2346
- expertKey: expert.key,
2347
- text: textPart.text,
2348
- stepNumber: resultCheckpoint.stepNumber,
2349
- deltaUsage: resultCheckpoint.usage
2350
- };
2351
- }
2352
2535
 
2353
2536
  export { getModel, package_default, run, runtimeStateMachine };
2354
- //# sourceMappingURL=chunk-LMD6DWPY.js.map
2355
- //# sourceMappingURL=chunk-LMD6DWPY.js.map
2537
+ //# sourceMappingURL=chunk-7QLRRSNX.js.map
2538
+ //# sourceMappingURL=chunk-7QLRRSNX.js.map