@perstack/runtime 0.0.67 → 0.0.69
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/bin/cli.ts +99 -0
- package/dist/bin/cli.js +1 -1
- package/dist/{chunk-AQSRQW5R.js → chunk-H65LPOAK.js} +780 -533
- package/dist/chunk-H65LPOAK.js.map +1 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.js +2 -2
- package/package.json +26 -19
- package/LICENSE +0 -202
- package/dist/chunk-AQSRQW5R.js.map +0 -1
|
@@ -11,10 +11,10 @@ import { ProxyAgent, fetch } from 'undici';
|
|
|
11
11
|
import { setup, assign, createActor } from 'xstate';
|
|
12
12
|
import { generateText, tool, jsonSchema } from 'ai';
|
|
13
13
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
14
|
-
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
15
|
-
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
16
14
|
import { McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
17
15
|
import { createId } from '@paralleldrive/cuid2';
|
|
16
|
+
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
17
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
18
18
|
import { readFile } from 'fs/promises';
|
|
19
19
|
import { dedent } from 'ts-dedent';
|
|
20
20
|
import { ApiV1Client } from '@perstack/api-client/v1';
|
|
@@ -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.
|
|
25
|
+
version: "0.0.69",
|
|
26
26
|
description: "Perstack Runtime",
|
|
27
27
|
author: "Wintermute Technologies, Inc.",
|
|
28
28
|
license: "Apache-2.0",
|
|
@@ -54,9 +54,6 @@ var package_default = {
|
|
|
54
54
|
typecheck: "tsc --noEmit"
|
|
55
55
|
},
|
|
56
56
|
dependencies: {
|
|
57
|
-
commander: "^14.0.2",
|
|
58
|
-
dotenv: "^17.2.3",
|
|
59
|
-
"smol-toml": "^1.5.2",
|
|
60
57
|
"@ai-sdk/amazon-bedrock": "^3.0.62",
|
|
61
58
|
"@ai-sdk/anthropic": "^2.0.50",
|
|
62
59
|
"@ai-sdk/azure": "^2.0.77",
|
|
@@ -69,14 +66,18 @@ var package_default = {
|
|
|
69
66
|
"@perstack/api-client": "workspace:*",
|
|
70
67
|
"@perstack/core": "workspace:*",
|
|
71
68
|
ai: "^5.0.104",
|
|
69
|
+
commander: "^14.0.2",
|
|
70
|
+
dotenv: "^17.2.3",
|
|
72
71
|
"ollama-ai-provider-v2": "^1.5.5",
|
|
72
|
+
"smol-toml": "^1.5.2",
|
|
73
73
|
"ts-dedent": "^2.2.0",
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
undici: "^7.9.0",
|
|
75
|
+
xstate: "^5.24.0"
|
|
76
76
|
},
|
|
77
77
|
devDependencies: {
|
|
78
78
|
"@tsconfig/node22": "^22.0.5",
|
|
79
79
|
"@types/node": "^24.10.1",
|
|
80
|
+
memfs: "^4.51.1",
|
|
80
81
|
tsup: "^8.5.1",
|
|
81
82
|
typescript: "^5.9.3",
|
|
82
83
|
vitest: "^4.0.14"
|
|
@@ -260,11 +261,11 @@ var BaseSkillManager = class {
|
|
|
260
261
|
this._initializing = void 0;
|
|
261
262
|
}
|
|
262
263
|
async getToolDefinitions() {
|
|
263
|
-
if (!this.isInitialized() &&
|
|
264
|
-
|
|
264
|
+
if (!this.isInitialized() && this._initializing) {
|
|
265
|
+
await this._initializing;
|
|
265
266
|
}
|
|
266
|
-
if (!this.isInitialized()
|
|
267
|
-
|
|
267
|
+
if (!this.isInitialized()) {
|
|
268
|
+
throw new Error(`Skill ${this.name} is not initialized`);
|
|
268
269
|
}
|
|
269
270
|
return this._filterTools(this._toolDefinitions);
|
|
270
271
|
}
|
|
@@ -273,6 +274,22 @@ var BaseSkillManager = class {
|
|
|
273
274
|
}
|
|
274
275
|
};
|
|
275
276
|
|
|
277
|
+
// src/skill-manager/command-args.ts
|
|
278
|
+
function getCommandArgs(skill) {
|
|
279
|
+
const { name, command, packageName, args } = skill;
|
|
280
|
+
if (!packageName && (!args || args.length === 0)) {
|
|
281
|
+
throw new Error(`Skill ${name} has no packageName or args. Please provide one of them.`);
|
|
282
|
+
}
|
|
283
|
+
if (packageName && args && args.length > 0) {
|
|
284
|
+
throw new Error(`Skill ${name} has both packageName and args. Please provide only one of them.`);
|
|
285
|
+
}
|
|
286
|
+
let newArgs = args && args.length > 0 ? args : [packageName];
|
|
287
|
+
if (command === "npx" && !newArgs.includes("-y")) {
|
|
288
|
+
newArgs = ["-y", ...newArgs];
|
|
289
|
+
}
|
|
290
|
+
return { command, args: newArgs };
|
|
291
|
+
}
|
|
292
|
+
|
|
276
293
|
// src/skill-manager/delegate.ts
|
|
277
294
|
var DelegateSkillManager = class extends BaseSkillManager {
|
|
278
295
|
name;
|
|
@@ -334,6 +351,114 @@ var InteractiveSkillManager = class extends BaseSkillManager {
|
|
|
334
351
|
return [];
|
|
335
352
|
}
|
|
336
353
|
};
|
|
354
|
+
|
|
355
|
+
// src/skill-manager/ip-validator.ts
|
|
356
|
+
function isPrivateOrLocalIP(hostname) {
|
|
357
|
+
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "0.0.0.0") {
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
const ipv4Match = hostname.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
|
|
361
|
+
if (ipv4Match) {
|
|
362
|
+
const [, a, b] = ipv4Match.map(Number);
|
|
363
|
+
if (a === 10) return true;
|
|
364
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
365
|
+
if (a === 192 && b === 168) return true;
|
|
366
|
+
if (a === 169 && b === 254) return true;
|
|
367
|
+
if (a === 127) return true;
|
|
368
|
+
}
|
|
369
|
+
if (hostname.includes(":")) {
|
|
370
|
+
if (hostname.startsWith("fe80:")) return true;
|
|
371
|
+
if (hostname.startsWith("fc") || hostname.startsWith("fd")) return true;
|
|
372
|
+
}
|
|
373
|
+
if (hostname.startsWith("::ffff:")) {
|
|
374
|
+
const ipv4Part = hostname.slice(7);
|
|
375
|
+
if (isPrivateOrLocalIP(ipv4Part)) {
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
function handleToolError(error, toolName, McpErrorClass) {
|
|
382
|
+
if (error instanceof McpErrorClass) {
|
|
383
|
+
return [
|
|
384
|
+
{
|
|
385
|
+
type: "textPart",
|
|
386
|
+
text: `Error calling tool ${toolName}: ${error.message}`,
|
|
387
|
+
id: createId()
|
|
388
|
+
}
|
|
389
|
+
];
|
|
390
|
+
}
|
|
391
|
+
throw error;
|
|
392
|
+
}
|
|
393
|
+
function convertToolResult(result, toolName, input) {
|
|
394
|
+
if (!result.content || result.content.length === 0) {
|
|
395
|
+
return [
|
|
396
|
+
{
|
|
397
|
+
type: "textPart",
|
|
398
|
+
text: `Tool ${toolName} returned nothing with arguments: ${JSON.stringify(input)}`,
|
|
399
|
+
id: createId()
|
|
400
|
+
}
|
|
401
|
+
];
|
|
402
|
+
}
|
|
403
|
+
return result.content.filter((part) => part.type !== "audio" && part.type !== "resource_link").map((part) => convertPart(part));
|
|
404
|
+
}
|
|
405
|
+
function convertPart(part) {
|
|
406
|
+
switch (part.type) {
|
|
407
|
+
case "text":
|
|
408
|
+
if (!part.text || part.text === "") {
|
|
409
|
+
return { type: "textPart", text: "Error: No content", id: createId() };
|
|
410
|
+
}
|
|
411
|
+
return { type: "textPart", text: part.text, id: createId() };
|
|
412
|
+
case "image":
|
|
413
|
+
if (!part.data || !part.mimeType) {
|
|
414
|
+
throw new Error("Image part must have both data and mimeType");
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
type: "imageInlinePart",
|
|
418
|
+
encodedData: part.data,
|
|
419
|
+
mimeType: part.mimeType,
|
|
420
|
+
id: createId()
|
|
421
|
+
};
|
|
422
|
+
case "resource":
|
|
423
|
+
if (!part.resource) {
|
|
424
|
+
throw new Error("Resource part must have resource content");
|
|
425
|
+
}
|
|
426
|
+
return convertResource(part.resource);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function convertResource(resource) {
|
|
430
|
+
if (!resource.mimeType) {
|
|
431
|
+
throw new Error(`Resource ${JSON.stringify(resource)} has no mimeType`);
|
|
432
|
+
}
|
|
433
|
+
if (resource.text && typeof resource.text === "string") {
|
|
434
|
+
return { type: "textPart", text: resource.text, id: createId() };
|
|
435
|
+
}
|
|
436
|
+
if (resource.blob && typeof resource.blob === "string") {
|
|
437
|
+
return {
|
|
438
|
+
type: "fileInlinePart",
|
|
439
|
+
encodedData: resource.blob,
|
|
440
|
+
mimeType: resource.mimeType,
|
|
441
|
+
id: createId()
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
throw new Error(`Unsupported resource type: ${JSON.stringify(resource)}`);
|
|
445
|
+
}
|
|
446
|
+
var DefaultTransportFactory = class {
|
|
447
|
+
createStdio(options) {
|
|
448
|
+
return new StdioClientTransport({
|
|
449
|
+
command: options.command,
|
|
450
|
+
args: options.args,
|
|
451
|
+
env: options.env,
|
|
452
|
+
stderr: options.stderr
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
createSse(options) {
|
|
456
|
+
return new SSEClientTransport(options.url);
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
var defaultTransportFactory = new DefaultTransportFactory();
|
|
460
|
+
|
|
461
|
+
// src/skill-manager/mcp.ts
|
|
337
462
|
var McpSkillManager = class extends BaseSkillManager {
|
|
338
463
|
name;
|
|
339
464
|
type = "mcp";
|
|
@@ -341,11 +466,13 @@ var McpSkillManager = class extends BaseSkillManager {
|
|
|
341
466
|
skill;
|
|
342
467
|
_mcpClient;
|
|
343
468
|
_env;
|
|
344
|
-
|
|
469
|
+
_transportFactory;
|
|
470
|
+
constructor(skill, env, jobId, runId, eventListener, options) {
|
|
345
471
|
super(jobId, runId, eventListener);
|
|
346
472
|
this.name = skill.name;
|
|
347
473
|
this.skill = skill;
|
|
348
474
|
this._env = env;
|
|
475
|
+
this._transportFactory = options?.transportFactory ?? defaultTransportFactory;
|
|
349
476
|
this.lazyInit = skill.type === "mcpStdioSkill" && skill.lazyInit && skill.name !== "@perstack/base";
|
|
350
477
|
}
|
|
351
478
|
async _doInit() {
|
|
@@ -353,12 +480,15 @@ var McpSkillManager = class extends BaseSkillManager {
|
|
|
353
480
|
name: `${this.skill.name}-mcp-client`,
|
|
354
481
|
version: "1.0.0"
|
|
355
482
|
});
|
|
483
|
+
let timingInfo;
|
|
356
484
|
if (this.skill.type === "mcpStdioSkill") {
|
|
357
|
-
await this._initStdio(this.skill);
|
|
485
|
+
timingInfo = await this._initStdio(this.skill);
|
|
358
486
|
} else {
|
|
359
487
|
await this._initSse(this.skill);
|
|
360
488
|
}
|
|
489
|
+
const toolDiscoveryStartTime = Date.now();
|
|
361
490
|
const { tools } = await this._mcpClient.listTools();
|
|
491
|
+
const toolDiscoveryDurationMs = Date.now() - toolDiscoveryStartTime;
|
|
362
492
|
this._toolDefinitions = tools.map((tool2) => ({
|
|
363
493
|
skillName: this.skill.name,
|
|
364
494
|
name: tool2.name,
|
|
@@ -366,6 +496,19 @@ var McpSkillManager = class extends BaseSkillManager {
|
|
|
366
496
|
inputSchema: tool2.inputSchema,
|
|
367
497
|
interactive: false
|
|
368
498
|
}));
|
|
499
|
+
if (this._eventListener && timingInfo) {
|
|
500
|
+
const totalDurationMs = Date.now() - timingInfo.startTime;
|
|
501
|
+
const event = createRuntimeEvent("skillConnected", this._jobId, this._runId, {
|
|
502
|
+
skillName: this.skill.name,
|
|
503
|
+
serverInfo: timingInfo.serverInfo,
|
|
504
|
+
spawnDurationMs: timingInfo.spawnDurationMs,
|
|
505
|
+
handshakeDurationMs: timingInfo.handshakeDurationMs,
|
|
506
|
+
toolDiscoveryDurationMs,
|
|
507
|
+
connectDurationMs: timingInfo.spawnDurationMs + timingInfo.handshakeDurationMs,
|
|
508
|
+
totalDurationMs
|
|
509
|
+
});
|
|
510
|
+
this._eventListener(event);
|
|
511
|
+
}
|
|
369
512
|
}
|
|
370
513
|
async _initStdio(skill) {
|
|
371
514
|
if (!skill.command) {
|
|
@@ -380,7 +523,7 @@ var McpSkillManager = class extends BaseSkillManager {
|
|
|
380
523
|
}
|
|
381
524
|
const env = getFilteredEnv(requiredEnv);
|
|
382
525
|
const startTime = Date.now();
|
|
383
|
-
const { command, args } =
|
|
526
|
+
const { command, args } = getCommandArgs(skill);
|
|
384
527
|
if (this._eventListener) {
|
|
385
528
|
const event = createRuntimeEvent("skillStarting", this._jobId, this._runId, {
|
|
386
529
|
skillName: skill.name,
|
|
@@ -389,7 +532,8 @@ var McpSkillManager = class extends BaseSkillManager {
|
|
|
389
532
|
});
|
|
390
533
|
this._eventListener(event);
|
|
391
534
|
}
|
|
392
|
-
const transport =
|
|
535
|
+
const transport = this._transportFactory.createStdio({ command, args, env, stderr: "pipe" });
|
|
536
|
+
const spawnDurationMs = Date.now() - startTime;
|
|
393
537
|
if (transport.stderr) {
|
|
394
538
|
transport.stderr.on("data", (chunk) => {
|
|
395
539
|
if (this._eventListener) {
|
|
@@ -403,17 +547,14 @@ var McpSkillManager = class extends BaseSkillManager {
|
|
|
403
547
|
}
|
|
404
548
|
const connectStartTime = Date.now();
|
|
405
549
|
await this._mcpClient.connect(transport);
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
});
|
|
415
|
-
this._eventListener(event);
|
|
416
|
-
}
|
|
550
|
+
const handshakeDurationMs = Date.now() - connectStartTime;
|
|
551
|
+
const serverVersion = this._mcpClient.getServerVersion();
|
|
552
|
+
return {
|
|
553
|
+
startTime,
|
|
554
|
+
spawnDurationMs,
|
|
555
|
+
handshakeDurationMs,
|
|
556
|
+
serverInfo: serverVersion ? { name: serverVersion.name, version: serverVersion.version } : void 0
|
|
557
|
+
};
|
|
417
558
|
}
|
|
418
559
|
async _initSse(skill) {
|
|
419
560
|
if (!skill.endpoint) {
|
|
@@ -423,56 +564,14 @@ var McpSkillManager = class extends BaseSkillManager {
|
|
|
423
564
|
if (url.protocol !== "https:") {
|
|
424
565
|
throw new Error(`Skill ${skill.name} SSE endpoint must use HTTPS: ${skill.endpoint}`);
|
|
425
566
|
}
|
|
426
|
-
if (
|
|
567
|
+
if (isPrivateOrLocalIP(url.hostname)) {
|
|
427
568
|
throw new Error(
|
|
428
569
|
`Skill ${skill.name} SSE endpoint cannot use private/local IP: ${skill.endpoint}`
|
|
429
570
|
);
|
|
430
571
|
}
|
|
431
|
-
const transport =
|
|
572
|
+
const transport = this._transportFactory.createSse({ url });
|
|
432
573
|
await this._mcpClient.connect(transport);
|
|
433
574
|
}
|
|
434
|
-
_isPrivateOrLocalIP(hostname) {
|
|
435
|
-
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "0.0.0.0") {
|
|
436
|
-
return true;
|
|
437
|
-
}
|
|
438
|
-
const ipv4Match = hostname.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
|
|
439
|
-
if (ipv4Match) {
|
|
440
|
-
const [, a, b] = ipv4Match.map(Number);
|
|
441
|
-
if (a === 10) return true;
|
|
442
|
-
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
443
|
-
if (a === 192 && b === 168) return true;
|
|
444
|
-
if (a === 169 && b === 254) return true;
|
|
445
|
-
if (a === 127) return true;
|
|
446
|
-
}
|
|
447
|
-
if (hostname.includes(":")) {
|
|
448
|
-
if (hostname.startsWith("fe80:") || hostname.startsWith("fc") || hostname.startsWith("fd")) {
|
|
449
|
-
return true;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
if (hostname.startsWith("::ffff:")) {
|
|
453
|
-
const ipv4Part = hostname.slice(7);
|
|
454
|
-
if (this._isPrivateOrLocalIP(ipv4Part)) {
|
|
455
|
-
return true;
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
return false;
|
|
459
|
-
}
|
|
460
|
-
_getCommandArgs(skill) {
|
|
461
|
-
const { name, command, packageName, args } = skill;
|
|
462
|
-
if (!packageName && (!args || args.length === 0)) {
|
|
463
|
-
throw new Error(`Skill ${name} has no packageName or args. Please provide one of them.`);
|
|
464
|
-
}
|
|
465
|
-
if (packageName && args && args.length > 0) {
|
|
466
|
-
throw new Error(
|
|
467
|
-
`Skill ${name} has both packageName and args. Please provide only one of them.`
|
|
468
|
-
);
|
|
469
|
-
}
|
|
470
|
-
let newArgs = args && args.length > 0 ? args : [packageName];
|
|
471
|
-
if (command === "npx" && !newArgs.includes("-y")) {
|
|
472
|
-
newArgs = ["-y", ...newArgs];
|
|
473
|
-
}
|
|
474
|
-
return { command, args: newArgs };
|
|
475
|
-
}
|
|
476
575
|
async close() {
|
|
477
576
|
if (this._mcpClient) {
|
|
478
577
|
await this._mcpClient.close();
|
|
@@ -498,77 +597,33 @@ var McpSkillManager = class extends BaseSkillManager {
|
|
|
498
597
|
name: toolName,
|
|
499
598
|
arguments: input
|
|
500
599
|
});
|
|
501
|
-
return
|
|
600
|
+
return convertToolResult(result, toolName, input);
|
|
502
601
|
} catch (error) {
|
|
503
|
-
return
|
|
602
|
+
return handleToolError(error, toolName, McpError);
|
|
504
603
|
}
|
|
505
604
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
// src/skill-manager/skill-manager-factory.ts
|
|
608
|
+
var DefaultSkillManagerFactory = class {
|
|
609
|
+
createMcp(skill, context) {
|
|
610
|
+
return new McpSkillManager(
|
|
611
|
+
skill,
|
|
612
|
+
context.env,
|
|
613
|
+
context.jobId,
|
|
614
|
+
context.runId,
|
|
615
|
+
context.eventListener,
|
|
616
|
+
context.mcpOptions
|
|
617
|
+
);
|
|
517
618
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
return [
|
|
521
|
-
{
|
|
522
|
-
type: "textPart",
|
|
523
|
-
text: `Tool ${toolName} returned nothing with arguments: ${JSON.stringify(input)}`,
|
|
524
|
-
id: createId()
|
|
525
|
-
}
|
|
526
|
-
];
|
|
527
|
-
}
|
|
528
|
-
return result.content.filter((part) => part.type !== "audio" && part.type !== "resource_link").map((part) => this._convertPart(part));
|
|
619
|
+
createInteractive(skill, context) {
|
|
620
|
+
return new InteractiveSkillManager(skill, context.jobId, context.runId, context.eventListener);
|
|
529
621
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
case "text":
|
|
533
|
-
if (!part.text || part.text === "") {
|
|
534
|
-
return { type: "textPart", text: "Error: No content", id: createId() };
|
|
535
|
-
}
|
|
536
|
-
return { type: "textPart", text: part.text, id: createId() };
|
|
537
|
-
case "image":
|
|
538
|
-
if (!part.data || !part.mimeType) {
|
|
539
|
-
throw new Error("Image part must have both data and mimeType");
|
|
540
|
-
}
|
|
541
|
-
return {
|
|
542
|
-
type: "imageInlinePart",
|
|
543
|
-
encodedData: part.data,
|
|
544
|
-
mimeType: part.mimeType,
|
|
545
|
-
id: createId()
|
|
546
|
-
};
|
|
547
|
-
case "resource":
|
|
548
|
-
if (!part.resource) {
|
|
549
|
-
throw new Error("Resource part must have resource content");
|
|
550
|
-
}
|
|
551
|
-
return this._convertResource(part.resource);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
_convertResource(resource) {
|
|
555
|
-
if (!resource.mimeType) {
|
|
556
|
-
throw new Error(`Resource ${JSON.stringify(resource)} has no mimeType`);
|
|
557
|
-
}
|
|
558
|
-
if (resource.text && typeof resource.text === "string") {
|
|
559
|
-
return { type: "textPart", text: resource.text, id: createId() };
|
|
560
|
-
}
|
|
561
|
-
if (resource.blob && typeof resource.blob === "string") {
|
|
562
|
-
return {
|
|
563
|
-
type: "fileInlinePart",
|
|
564
|
-
encodedData: resource.blob,
|
|
565
|
-
mimeType: resource.mimeType,
|
|
566
|
-
id: createId()
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
|
-
throw new Error(`Unsupported resource type: ${JSON.stringify(resource)}`);
|
|
622
|
+
createDelegate(expert, context) {
|
|
623
|
+
return new DelegateSkillManager(expert, context.jobId, context.runId, context.eventListener);
|
|
570
624
|
}
|
|
571
625
|
};
|
|
626
|
+
var defaultSkillManagerFactory = new DefaultSkillManagerFactory();
|
|
572
627
|
|
|
573
628
|
// src/skill-manager/helpers.ts
|
|
574
629
|
async function initSkillManagersWithCleanup(managers, allManagers) {
|
|
@@ -583,11 +638,20 @@ async function initSkillManagersWithCleanup(managers, allManagers) {
|
|
|
583
638
|
async function getSkillManagers(expert, experts, setting, eventListener, options) {
|
|
584
639
|
const { perstackBaseSkillCommand, env, jobId, runId } = setting;
|
|
585
640
|
const { skills } = expert;
|
|
641
|
+
const factory = options?.factory ?? defaultSkillManagerFactory;
|
|
586
642
|
if (!skills["@perstack/base"]) {
|
|
587
643
|
throw new Error("Base skill is not defined");
|
|
588
644
|
}
|
|
645
|
+
const factoryContext = {
|
|
646
|
+
env,
|
|
647
|
+
jobId,
|
|
648
|
+
runId,
|
|
649
|
+
eventListener
|
|
650
|
+
};
|
|
589
651
|
const allManagers = [];
|
|
590
|
-
const mcpSkills = Object.values(skills).filter(
|
|
652
|
+
const mcpSkills = Object.values(skills).filter(
|
|
653
|
+
(skill) => skill.type === "mcpStdioSkill" || skill.type === "mcpSseSkill"
|
|
654
|
+
).map((skill) => {
|
|
591
655
|
if (perstackBaseSkillCommand && skill.type === "mcpStdioSkill") {
|
|
592
656
|
const matchesBaseByPackage = skill.command === "npx" && skill.packageName === "@perstack/base";
|
|
593
657
|
const matchesBaseByArgs = skill.command === "npx" && Array.isArray(skill.args) && skill.args.includes("@perstack/base");
|
|
@@ -608,7 +672,7 @@ async function getSkillManagers(expert, experts, setting, eventListener, options
|
|
|
608
672
|
return skill;
|
|
609
673
|
});
|
|
610
674
|
const mcpSkillManagers = mcpSkills.map((skill) => {
|
|
611
|
-
const manager =
|
|
675
|
+
const manager = factory.createMcp(skill, factoryContext);
|
|
612
676
|
allManagers.push(manager);
|
|
613
677
|
return manager;
|
|
614
678
|
});
|
|
@@ -618,7 +682,7 @@ async function getSkillManagers(expert, experts, setting, eventListener, options
|
|
|
618
682
|
(skill) => skill.type === "interactiveSkill"
|
|
619
683
|
);
|
|
620
684
|
const interactiveSkillManagers = interactiveSkills.map((interactiveSkill) => {
|
|
621
|
-
const manager =
|
|
685
|
+
const manager = factory.createInteractive(interactiveSkill, factoryContext);
|
|
622
686
|
allManagers.push(manager);
|
|
623
687
|
return manager;
|
|
624
688
|
});
|
|
@@ -632,7 +696,7 @@ async function getSkillManagers(expert, experts, setting, eventListener, options
|
|
|
632
696
|
})));
|
|
633
697
|
throw new Error(`Delegate expert "${delegateExpertName}" not found in experts`);
|
|
634
698
|
}
|
|
635
|
-
const manager =
|
|
699
|
+
const manager = factory.createDelegate(delegate, factoryContext);
|
|
636
700
|
allManagers.push(manager);
|
|
637
701
|
delegateSkillManagers.push(manager);
|
|
638
702
|
}
|
|
@@ -671,12 +735,140 @@ async function getToolSet(skillManagers) {
|
|
|
671
735
|
}
|
|
672
736
|
return tools;
|
|
673
737
|
}
|
|
738
|
+
function isFileInfo(value) {
|
|
739
|
+
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";
|
|
740
|
+
}
|
|
741
|
+
async function processFileToolResult(toolResult, toolName) {
|
|
742
|
+
const processedContents = [];
|
|
743
|
+
for (const part of toolResult.result) {
|
|
744
|
+
if (part.type !== "textPart") {
|
|
745
|
+
processedContents.push(part);
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
let fileInfo;
|
|
749
|
+
try {
|
|
750
|
+
const parsed = JSON.parse(part.text);
|
|
751
|
+
if (isFileInfo(parsed)) {
|
|
752
|
+
fileInfo = parsed;
|
|
753
|
+
}
|
|
754
|
+
} catch {
|
|
755
|
+
processedContents.push(part);
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
if (!fileInfo) {
|
|
759
|
+
processedContents.push(part);
|
|
760
|
+
continue;
|
|
761
|
+
}
|
|
762
|
+
const { path, mimeType } = fileInfo;
|
|
763
|
+
try {
|
|
764
|
+
const buffer = await readFile(path);
|
|
765
|
+
if (toolName === "readImageFile") {
|
|
766
|
+
processedContents.push({
|
|
767
|
+
type: "imageInlinePart",
|
|
768
|
+
id: part.id,
|
|
769
|
+
encodedData: buffer.toString("base64"),
|
|
770
|
+
mimeType
|
|
771
|
+
});
|
|
772
|
+
} else {
|
|
773
|
+
processedContents.push({
|
|
774
|
+
type: "fileInlinePart",
|
|
775
|
+
id: part.id,
|
|
776
|
+
encodedData: buffer.toString("base64"),
|
|
777
|
+
mimeType
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
} catch (error) {
|
|
781
|
+
processedContents.push({
|
|
782
|
+
type: "textPart",
|
|
783
|
+
id: part.id,
|
|
784
|
+
text: `Failed to read file "${path}": ${error instanceof Error ? error.message : String(error)}`
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return { ...toolResult, result: processedContents };
|
|
789
|
+
}
|
|
790
|
+
var McpToolExecutor = class {
|
|
791
|
+
type = "mcp";
|
|
792
|
+
async execute(toolCall, skillManagers) {
|
|
793
|
+
const skillManager = await getSkillManagerByToolName(skillManagers, toolCall.toolName);
|
|
794
|
+
if (skillManager.type !== "mcp") {
|
|
795
|
+
throw new Error(`Incorrect SkillType, required MCP, got ${skillManager.type}`);
|
|
796
|
+
}
|
|
797
|
+
const result = await skillManager.callTool(
|
|
798
|
+
toolCall.toolName,
|
|
799
|
+
toolCall.args
|
|
800
|
+
);
|
|
801
|
+
const toolResult = {
|
|
802
|
+
id: toolCall.id,
|
|
803
|
+
skillName: toolCall.skillName,
|
|
804
|
+
toolName: toolCall.toolName,
|
|
805
|
+
result
|
|
806
|
+
};
|
|
807
|
+
if (toolCall.toolName === "readPdfFile" || toolCall.toolName === "readImageFile") {
|
|
808
|
+
return processFileToolResult(toolResult, toolCall.toolName);
|
|
809
|
+
}
|
|
810
|
+
return toolResult;
|
|
811
|
+
}
|
|
812
|
+
};
|
|
674
813
|
|
|
675
|
-
// src/
|
|
676
|
-
|
|
814
|
+
// src/tool-execution/executor-factory.ts
|
|
815
|
+
var ToolExecutorFactory = class {
|
|
816
|
+
executors;
|
|
817
|
+
constructor() {
|
|
818
|
+
this.executors = /* @__PURE__ */ new Map([
|
|
819
|
+
["mcp", new McpToolExecutor()]
|
|
820
|
+
// delegate and interactive are handled specially (not executed here)
|
|
821
|
+
]);
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Get the executor for a given skill type
|
|
825
|
+
*/
|
|
826
|
+
getExecutor(type) {
|
|
827
|
+
return this.executors.get(type);
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Execute a tool call using the appropriate executor
|
|
831
|
+
*/
|
|
832
|
+
async execute(toolCall, type, skillManagers) {
|
|
833
|
+
const executor = this.executors.get(type);
|
|
834
|
+
if (!executor) {
|
|
835
|
+
throw new Error(`No executor registered for skill type: ${type}`);
|
|
836
|
+
}
|
|
837
|
+
return executor.execute(toolCall, skillManagers);
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Check if a skill type can be executed locally (vs requiring delegation)
|
|
841
|
+
*/
|
|
842
|
+
canExecuteLocally(type) {
|
|
843
|
+
return type === "mcp";
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
var toolExecutorFactory = new ToolExecutorFactory();
|
|
847
|
+
|
|
848
|
+
// src/tool-execution/tool-classifier.ts
|
|
849
|
+
async function getToolTypeByName(toolName, skillManagers) {
|
|
677
850
|
const skillManager = await getSkillManagerByToolName(skillManagers, toolName);
|
|
678
851
|
return skillManager.type;
|
|
679
852
|
}
|
|
853
|
+
async function classifyToolCalls(toolCalls, skillManagers) {
|
|
854
|
+
const classified = {
|
|
855
|
+
mcp: [],
|
|
856
|
+
delegate: [],
|
|
857
|
+
interactive: []
|
|
858
|
+
};
|
|
859
|
+
const results = await Promise.all(
|
|
860
|
+
toolCalls.map(async (toolCall) => {
|
|
861
|
+
const skillManager = await getSkillManagerByToolName(skillManagers, toolCall.toolName);
|
|
862
|
+
return { toolCall, type: skillManager.type, skillManager };
|
|
863
|
+
})
|
|
864
|
+
);
|
|
865
|
+
for (const result of results) {
|
|
866
|
+
classified[result.type].push(result);
|
|
867
|
+
}
|
|
868
|
+
return classified;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// src/state-machine/states/calling-delegate.ts
|
|
680
872
|
async function callingDelegateLogic({
|
|
681
873
|
setting,
|
|
682
874
|
checkpoint,
|
|
@@ -689,7 +881,7 @@ async function callingDelegateLogic({
|
|
|
689
881
|
const toolCallTypes = await Promise.all(
|
|
690
882
|
step.pendingToolCalls.map(async (tc) => ({
|
|
691
883
|
toolCall: tc,
|
|
692
|
-
type: await
|
|
884
|
+
type: await getToolTypeByName(tc.toolName, skillManagers)
|
|
693
885
|
}))
|
|
694
886
|
);
|
|
695
887
|
const delegateToolCalls = toolCallTypes.filter((t) => t.type === "delegate").map((t) => t.toolCall);
|
|
@@ -767,79 +959,6 @@ function hasRemainingTodos(toolResult) {
|
|
|
767
959
|
return false;
|
|
768
960
|
}
|
|
769
961
|
}
|
|
770
|
-
function isFileInfo(value) {
|
|
771
|
-
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";
|
|
772
|
-
}
|
|
773
|
-
async function processFileToolResult(toolResult, toolName) {
|
|
774
|
-
const processedContents = [];
|
|
775
|
-
for (const part of toolResult.result) {
|
|
776
|
-
if (part.type !== "textPart") {
|
|
777
|
-
processedContents.push(part);
|
|
778
|
-
continue;
|
|
779
|
-
}
|
|
780
|
-
let fileInfo;
|
|
781
|
-
try {
|
|
782
|
-
const parsed = JSON.parse(part.text);
|
|
783
|
-
if (isFileInfo(parsed)) {
|
|
784
|
-
fileInfo = parsed;
|
|
785
|
-
}
|
|
786
|
-
} catch {
|
|
787
|
-
processedContents.push(part);
|
|
788
|
-
continue;
|
|
789
|
-
}
|
|
790
|
-
if (!fileInfo) {
|
|
791
|
-
processedContents.push(part);
|
|
792
|
-
continue;
|
|
793
|
-
}
|
|
794
|
-
const { path, mimeType } = fileInfo;
|
|
795
|
-
try {
|
|
796
|
-
const buffer = await readFile(path);
|
|
797
|
-
if (toolName === "readImageFile") {
|
|
798
|
-
processedContents.push({
|
|
799
|
-
type: "imageInlinePart",
|
|
800
|
-
id: part.id,
|
|
801
|
-
encodedData: buffer.toString("base64"),
|
|
802
|
-
mimeType
|
|
803
|
-
});
|
|
804
|
-
} else {
|
|
805
|
-
processedContents.push({
|
|
806
|
-
type: "fileInlinePart",
|
|
807
|
-
id: part.id,
|
|
808
|
-
encodedData: buffer.toString("base64"),
|
|
809
|
-
mimeType
|
|
810
|
-
});
|
|
811
|
-
}
|
|
812
|
-
} catch (error) {
|
|
813
|
-
processedContents.push({
|
|
814
|
-
type: "textPart",
|
|
815
|
-
id: part.id,
|
|
816
|
-
text: `Failed to read file "${path}": ${error instanceof Error ? error.message : String(error)}`
|
|
817
|
-
});
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
return { ...toolResult, result: processedContents };
|
|
821
|
-
}
|
|
822
|
-
async function executeMcpToolCall(toolCall, skillManagers) {
|
|
823
|
-
const skillManager = await getSkillManagerByToolName(skillManagers, toolCall.toolName);
|
|
824
|
-
if (skillManager.type !== "mcp") {
|
|
825
|
-
throw new Error(`Incorrect SkillType, required MCP, got ${skillManager.type}`);
|
|
826
|
-
}
|
|
827
|
-
const result = await skillManager.callTool(toolCall.toolName, toolCall.args);
|
|
828
|
-
const toolResult = {
|
|
829
|
-
id: toolCall.id,
|
|
830
|
-
skillName: toolCall.skillName,
|
|
831
|
-
toolName: toolCall.toolName,
|
|
832
|
-
result
|
|
833
|
-
};
|
|
834
|
-
if (toolCall.toolName === "readPdfFile" || toolCall.toolName === "readImageFile") {
|
|
835
|
-
return processFileToolResult(toolResult, toolCall.toolName);
|
|
836
|
-
}
|
|
837
|
-
return toolResult;
|
|
838
|
-
}
|
|
839
|
-
async function getToolType2(toolCall, skillManagers) {
|
|
840
|
-
const skillManager = await getSkillManagerByToolName(skillManagers, toolCall.toolName);
|
|
841
|
-
return skillManager.type;
|
|
842
|
-
}
|
|
843
962
|
async function callingToolLogic({
|
|
844
963
|
setting,
|
|
845
964
|
checkpoint,
|
|
@@ -855,28 +974,26 @@ async function callingToolLogic({
|
|
|
855
974
|
(tc) => tc.skillName === "@perstack/base" && tc.toolName === "attemptCompletion"
|
|
856
975
|
);
|
|
857
976
|
if (attemptCompletionTool) {
|
|
858
|
-
const toolResult = await
|
|
977
|
+
const toolResult = await toolExecutorFactory.execute(
|
|
978
|
+
attemptCompletionTool,
|
|
979
|
+
"mcp",
|
|
980
|
+
skillManagers
|
|
981
|
+
);
|
|
859
982
|
if (hasRemainingTodos(toolResult)) {
|
|
860
983
|
return resolveToolResults(setting, checkpoint, { toolResults: [toolResult] });
|
|
861
984
|
}
|
|
862
985
|
return attemptCompletion(setting, checkpoint, { toolResult });
|
|
863
986
|
}
|
|
864
|
-
const
|
|
865
|
-
|
|
866
|
-
toolCall: tc,
|
|
867
|
-
type: await getToolType2(tc, skillManagers)
|
|
868
|
-
}))
|
|
869
|
-
);
|
|
870
|
-
const mcpToolCalls = toolCallTypes.filter((t) => t.type === "mcp").map((t) => t.toolCall);
|
|
871
|
-
const delegateToolCalls = toolCallTypes.filter((t) => t.type === "delegate").map((t) => t.toolCall);
|
|
872
|
-
const interactiveToolCalls = toolCallTypes.filter((t) => t.type === "interactive").map((t) => t.toolCall);
|
|
873
|
-
if (mcpToolCalls.length > 0) {
|
|
987
|
+
const classified = await classifyToolCalls(pendingToolCalls, skillManagers);
|
|
988
|
+
if (classified.mcp.length > 0) {
|
|
874
989
|
const mcpResults = await Promise.all(
|
|
875
|
-
|
|
990
|
+
classified.mcp.map((c) => toolExecutorFactory.execute(c.toolCall, "mcp", skillManagers))
|
|
876
991
|
);
|
|
877
992
|
toolResults.push(...mcpResults);
|
|
878
993
|
}
|
|
879
|
-
if (
|
|
994
|
+
if (classified.delegate.length > 0) {
|
|
995
|
+
const delegateToolCalls = classified.delegate.map((c) => c.toolCall);
|
|
996
|
+
const interactiveToolCalls = classified.interactive.map((c) => c.toolCall);
|
|
880
997
|
step.partialToolResults = toolResults;
|
|
881
998
|
step.pendingToolCalls = [...delegateToolCalls, ...interactiveToolCalls];
|
|
882
999
|
return callDelegate(setting, checkpoint, {
|
|
@@ -885,11 +1002,12 @@ async function callingToolLogic({
|
|
|
885
1002
|
usage: step.usage
|
|
886
1003
|
});
|
|
887
1004
|
}
|
|
888
|
-
if (
|
|
889
|
-
const interactiveToolCall =
|
|
1005
|
+
if (classified.interactive.length > 0) {
|
|
1006
|
+
const interactiveToolCall = classified.interactive[0]?.toolCall;
|
|
890
1007
|
if (!interactiveToolCall) {
|
|
891
1008
|
throw new Error("No interactive tool call found");
|
|
892
1009
|
}
|
|
1010
|
+
const interactiveToolCalls = classified.interactive.map((c) => c.toolCall);
|
|
893
1011
|
step.partialToolResults = toolResults;
|
|
894
1012
|
step.pendingToolCalls = interactiveToolCalls;
|
|
895
1013
|
return callInteractiveTool(setting, checkpoint, {
|
|
@@ -1198,7 +1316,7 @@ async function generatingRunResultLogic({
|
|
|
1198
1316
|
usage
|
|
1199
1317
|
});
|
|
1200
1318
|
}
|
|
1201
|
-
async function
|
|
1319
|
+
async function classifyToolCalls2(toolCalls, skillManagers) {
|
|
1202
1320
|
return Promise.all(
|
|
1203
1321
|
toolCalls.map(async (tc) => {
|
|
1204
1322
|
const skillManager = await getSkillManagerByToolName(skillManagers, tc.toolName);
|
|
@@ -1275,7 +1393,7 @@ async function generatingToolCallLogic({
|
|
|
1275
1393
|
usage
|
|
1276
1394
|
});
|
|
1277
1395
|
}
|
|
1278
|
-
const classified = await
|
|
1396
|
+
const classified = await classifyToolCalls2(toolCalls, skillManagers);
|
|
1279
1397
|
const sorted = sortToolCallsByPriority(classified);
|
|
1280
1398
|
if (finishReason === "tool-calls" || finishReason === "stop") {
|
|
1281
1399
|
const toolCallParts = buildToolCallParts(sorted);
|
|
@@ -1905,88 +2023,115 @@ var StateMachineLogics = {
|
|
|
1905
2023
|
CallingDelegate: callingDelegateLogic,
|
|
1906
2024
|
FinishingStep: finishingStepLogic
|
|
1907
2025
|
};
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
}
|
|
1941
|
-
await eventEmitter.emit(event);
|
|
1942
|
-
if (shouldContinueRun) {
|
|
1943
|
-
const shouldContinue = await shouldContinueRun(
|
|
1944
|
-
runState.context.setting,
|
|
1945
|
-
runState.context.checkpoint,
|
|
1946
|
-
runState.context.step
|
|
1947
|
-
);
|
|
1948
|
-
if (!shouldContinue) {
|
|
1949
|
-
runActor.stop();
|
|
1950
|
-
await closeSkillManagers(runState.context.skillManagers);
|
|
1951
|
-
resolve(runState.context.checkpoint);
|
|
1952
|
-
return;
|
|
1953
|
-
}
|
|
1954
|
-
}
|
|
1955
|
-
runActor.send(event);
|
|
1956
|
-
}
|
|
1957
|
-
} catch (error) {
|
|
1958
|
-
await closeSkillManagers(skillManagers).catch(() => {
|
|
1959
|
-
});
|
|
1960
|
-
reject(error);
|
|
2026
|
+
var DefaultActorFactory = class {
|
|
2027
|
+
create(input) {
|
|
2028
|
+
return createActor(runtimeStateMachine, input);
|
|
2029
|
+
}
|
|
2030
|
+
};
|
|
2031
|
+
var defaultActorFactory = new DefaultActorFactory();
|
|
2032
|
+
|
|
2033
|
+
// src/state-machine/coordinator.ts
|
|
2034
|
+
var StateMachineCoordinator = class {
|
|
2035
|
+
constructor(params, deps = {}) {
|
|
2036
|
+
this.params = params;
|
|
2037
|
+
this.actorFactory = deps.actorFactory ?? defaultActorFactory;
|
|
2038
|
+
this.closeManagers = deps.closeSkillManagers ?? closeSkillManagers;
|
|
2039
|
+
this.logics = deps.logics ?? StateMachineLogics;
|
|
2040
|
+
}
|
|
2041
|
+
actorFactory;
|
|
2042
|
+
closeManagers;
|
|
2043
|
+
logics;
|
|
2044
|
+
actor = null;
|
|
2045
|
+
resolvePromise = null;
|
|
2046
|
+
rejectPromise = null;
|
|
2047
|
+
/**
|
|
2048
|
+
* Execute the state machine and return the final checkpoint.
|
|
2049
|
+
*/
|
|
2050
|
+
async execute() {
|
|
2051
|
+
const { setting, initialCheckpoint, eventListener, skillManagers } = this.params;
|
|
2052
|
+
this.actor = this.actorFactory.create({
|
|
2053
|
+
input: {
|
|
2054
|
+
setting,
|
|
2055
|
+
initialCheckpoint,
|
|
2056
|
+
eventListener,
|
|
2057
|
+
skillManagers
|
|
1961
2058
|
}
|
|
1962
2059
|
});
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
this.listeners.push(listener);
|
|
1970
|
-
}
|
|
1971
|
-
async emit(event) {
|
|
1972
|
-
const errors = [];
|
|
1973
|
-
for (const listener of this.listeners) {
|
|
1974
|
-
try {
|
|
1975
|
-
await listener({
|
|
1976
|
-
...event,
|
|
1977
|
-
id: createId(),
|
|
1978
|
-
timestamp: Date.now()
|
|
2060
|
+
return new Promise((resolve, reject) => {
|
|
2061
|
+
this.resolvePromise = resolve;
|
|
2062
|
+
this.rejectPromise = reject;
|
|
2063
|
+
this.actor.subscribe((runState) => {
|
|
2064
|
+
this.handleStateChange(runState).catch((error) => {
|
|
2065
|
+
this.handleError(error);
|
|
1979
2066
|
});
|
|
1980
|
-
}
|
|
1981
|
-
|
|
2067
|
+
});
|
|
2068
|
+
this.actor.start();
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Handle state changes from the actor.
|
|
2073
|
+
* Exported for testing purposes.
|
|
2074
|
+
*/
|
|
2075
|
+
async handleStateChange(runState) {
|
|
2076
|
+
if (runState.value === "Stopped") {
|
|
2077
|
+
await this.handleStoppedState(runState);
|
|
2078
|
+
} else {
|
|
2079
|
+
await this.handleActiveState(runState);
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
/**
|
|
2083
|
+
* Handle the stopped state - cleanup and resolve.
|
|
2084
|
+
*/
|
|
2085
|
+
async handleStoppedState(runState) {
|
|
2086
|
+
const { checkpoint, skillManagers } = runState.context;
|
|
2087
|
+
if (!checkpoint) {
|
|
2088
|
+
throw new Error("Checkpoint is undefined");
|
|
2089
|
+
}
|
|
2090
|
+
await this.closeManagers(skillManagers);
|
|
2091
|
+
this.resolvePromise?.(checkpoint);
|
|
2092
|
+
}
|
|
2093
|
+
/**
|
|
2094
|
+
* Handle active states - execute logic, store checkpoint, emit events.
|
|
2095
|
+
*/
|
|
2096
|
+
async handleActiveState(runState) {
|
|
2097
|
+
const { eventEmitter, storeCheckpoint, shouldContinueRun } = this.params;
|
|
2098
|
+
const stateValue = runState.value;
|
|
2099
|
+
const event = await this.logics[stateValue](runState.context);
|
|
2100
|
+
if ("checkpoint" in event) {
|
|
2101
|
+
await storeCheckpoint(event.checkpoint);
|
|
2102
|
+
}
|
|
2103
|
+
await eventEmitter.emit(event);
|
|
2104
|
+
if (shouldContinueRun) {
|
|
2105
|
+
const shouldContinue = await shouldContinueRun(
|
|
2106
|
+
runState.context.setting,
|
|
2107
|
+
runState.context.checkpoint,
|
|
2108
|
+
runState.context.step
|
|
2109
|
+
);
|
|
2110
|
+
if (!shouldContinue) {
|
|
2111
|
+
this.actor?.stop();
|
|
2112
|
+
await this.closeManagers(runState.context.skillManagers);
|
|
2113
|
+
this.resolvePromise?.(runState.context.checkpoint);
|
|
2114
|
+
return;
|
|
1982
2115
|
}
|
|
1983
2116
|
}
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
2117
|
+
this.actor?.send(event);
|
|
2118
|
+
}
|
|
2119
|
+
/**
|
|
2120
|
+
* Handle errors - cleanup and reject.
|
|
2121
|
+
*/
|
|
2122
|
+
async handleError(error) {
|
|
2123
|
+
await this.closeManagers(this.params.skillManagers).catch(() => {
|
|
2124
|
+
});
|
|
2125
|
+
this.rejectPromise?.(error instanceof Error ? error : new Error(String(error)));
|
|
1987
2126
|
}
|
|
1988
2127
|
};
|
|
1989
2128
|
|
|
2129
|
+
// src/state-machine/executor.ts
|
|
2130
|
+
async function executeStateMachine(params) {
|
|
2131
|
+
const coordinator = new StateMachineCoordinator(params);
|
|
2132
|
+
return coordinator.execute();
|
|
2133
|
+
}
|
|
2134
|
+
|
|
1990
2135
|
// src/helpers/checkpoint.ts
|
|
1991
2136
|
function createInitialCheckpoint(checkpointId, params) {
|
|
1992
2137
|
return {
|
|
@@ -2049,46 +2194,6 @@ function buildDelegationReturnState(currentSetting, resultCheckpoint, parentChec
|
|
|
2049
2194
|
}
|
|
2050
2195
|
};
|
|
2051
2196
|
}
|
|
2052
|
-
function buildDelegateToState(currentSetting, resultCheckpoint, currentExpert) {
|
|
2053
|
-
const { delegateTo } = resultCheckpoint;
|
|
2054
|
-
if (!delegateTo || delegateTo.length === 0) {
|
|
2055
|
-
throw new Error("delegateTo is required for buildDelegateToState");
|
|
2056
|
-
}
|
|
2057
|
-
const firstDelegation = delegateTo[0];
|
|
2058
|
-
const { expert, toolCallId, toolName, query } = firstDelegation;
|
|
2059
|
-
return {
|
|
2060
|
-
setting: {
|
|
2061
|
-
...currentSetting,
|
|
2062
|
-
expertKey: expert.key,
|
|
2063
|
-
input: {
|
|
2064
|
-
text: query
|
|
2065
|
-
}
|
|
2066
|
-
},
|
|
2067
|
-
checkpoint: {
|
|
2068
|
-
...resultCheckpoint,
|
|
2069
|
-
status: "init",
|
|
2070
|
-
messages: [],
|
|
2071
|
-
expert: {
|
|
2072
|
-
key: expert.key,
|
|
2073
|
-
name: expert.name,
|
|
2074
|
-
version: expert.version
|
|
2075
|
-
},
|
|
2076
|
-
delegatedBy: {
|
|
2077
|
-
expert: {
|
|
2078
|
-
key: currentExpert.key,
|
|
2079
|
-
name: currentExpert.name,
|
|
2080
|
-
version: currentExpert.version
|
|
2081
|
-
},
|
|
2082
|
-
toolCallId,
|
|
2083
|
-
toolName,
|
|
2084
|
-
checkpointId: resultCheckpoint.id
|
|
2085
|
-
},
|
|
2086
|
-
usage: resultCheckpoint.usage,
|
|
2087
|
-
pendingToolCalls: void 0,
|
|
2088
|
-
partialToolResults: void 0
|
|
2089
|
-
}
|
|
2090
|
-
};
|
|
2091
|
-
}
|
|
2092
2197
|
async function resolveExpertToRun(expertKey, experts, clientOptions) {
|
|
2093
2198
|
if (experts[expertKey]) {
|
|
2094
2199
|
return experts[expertKey];
|
|
@@ -2138,68 +2243,245 @@ async function setupExperts(setting, resolveExpertToRun2 = resolveExpertToRun) {
|
|
|
2138
2243
|
}
|
|
2139
2244
|
return { expertToRun, experts };
|
|
2140
2245
|
}
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2246
|
+
var SingleDelegationStrategy = class {
|
|
2247
|
+
async execute(delegations, setting, context, parentExpert, _runFn, _parentOptions) {
|
|
2248
|
+
if (delegations.length !== 1) {
|
|
2249
|
+
throw new Error("SingleDelegationStrategy requires exactly one delegation");
|
|
2250
|
+
}
|
|
2251
|
+
const delegation = delegations[0];
|
|
2252
|
+
const { expert, toolCallId, toolName, query } = delegation;
|
|
2253
|
+
const nextSetting = {
|
|
2254
|
+
...setting,
|
|
2255
|
+
expertKey: expert.key,
|
|
2256
|
+
input: { text: query }
|
|
2257
|
+
};
|
|
2258
|
+
const nextCheckpoint = {
|
|
2259
|
+
id: context.id,
|
|
2260
|
+
jobId: setting.jobId,
|
|
2261
|
+
runId: setting.runId,
|
|
2262
|
+
status: "init",
|
|
2263
|
+
stepNumber: context.stepNumber,
|
|
2264
|
+
messages: [],
|
|
2265
|
+
// Child starts fresh
|
|
2266
|
+
expert: {
|
|
2267
|
+
key: expert.key,
|
|
2268
|
+
name: expert.name,
|
|
2269
|
+
version: expert.version
|
|
2270
|
+
},
|
|
2271
|
+
delegatedBy: {
|
|
2272
|
+
expert: {
|
|
2273
|
+
key: parentExpert.key,
|
|
2274
|
+
name: parentExpert.name,
|
|
2275
|
+
version: parentExpert.version
|
|
2276
|
+
},
|
|
2277
|
+
toolCallId,
|
|
2278
|
+
toolName,
|
|
2279
|
+
checkpointId: context.id
|
|
2280
|
+
},
|
|
2281
|
+
usage: context.usage,
|
|
2282
|
+
contextWindow: context.contextWindow,
|
|
2283
|
+
pendingToolCalls: void 0,
|
|
2284
|
+
partialToolResults: void 0
|
|
2285
|
+
};
|
|
2286
|
+
return { nextSetting, nextCheckpoint };
|
|
2287
|
+
}
|
|
2148
2288
|
};
|
|
2149
|
-
var
|
|
2150
|
-
|
|
2151
|
-
|
|
2289
|
+
var ParallelDelegationStrategy = class {
|
|
2290
|
+
async execute(delegations, setting, context, parentExpert, runFn, parentOptions) {
|
|
2291
|
+
if (delegations.length < 2) {
|
|
2292
|
+
throw new Error("ParallelDelegationStrategy requires at least two delegations");
|
|
2293
|
+
}
|
|
2294
|
+
const [firstDelegation, ...remainingDelegations] = delegations;
|
|
2295
|
+
if (!firstDelegation) {
|
|
2296
|
+
throw new Error("No delegations found");
|
|
2297
|
+
}
|
|
2298
|
+
const allResults = await Promise.all(
|
|
2299
|
+
delegations.map(
|
|
2300
|
+
(delegation) => this.executeSingleDelegation(
|
|
2301
|
+
delegation,
|
|
2302
|
+
setting,
|
|
2303
|
+
context,
|
|
2304
|
+
parentExpert,
|
|
2305
|
+
runFn,
|
|
2306
|
+
parentOptions
|
|
2307
|
+
)
|
|
2308
|
+
)
|
|
2309
|
+
);
|
|
2310
|
+
const [firstResult, ...restResults] = allResults;
|
|
2311
|
+
if (!firstResult) {
|
|
2312
|
+
throw new Error("No delegation results");
|
|
2313
|
+
}
|
|
2314
|
+
const aggregatedUsage = allResults.reduce(
|
|
2315
|
+
(acc, result) => sumUsage(acc, result.deltaUsage),
|
|
2316
|
+
context.usage
|
|
2317
|
+
);
|
|
2318
|
+
const maxStepNumber = Math.max(...allResults.map((r) => r.stepNumber));
|
|
2319
|
+
const restToolResults = restResults.map((result) => ({
|
|
2320
|
+
id: result.toolCallId,
|
|
2321
|
+
skillName: `delegate/${result.expertKey}`,
|
|
2322
|
+
toolName: result.toolName,
|
|
2323
|
+
result: [{ type: "textPart", id: createId(), text: result.text }]
|
|
2324
|
+
}));
|
|
2325
|
+
const processedToolCallIds = new Set(remainingDelegations.map((d) => d.toolCallId));
|
|
2326
|
+
const remainingPendingToolCalls = context.pendingToolCalls?.filter(
|
|
2327
|
+
(tc) => !processedToolCallIds.has(tc.id) && tc.id !== firstDelegation.toolCallId
|
|
2328
|
+
);
|
|
2329
|
+
const nextSetting = {
|
|
2330
|
+
...setting,
|
|
2331
|
+
expertKey: parentExpert.key,
|
|
2332
|
+
input: {
|
|
2333
|
+
interactiveToolCallResult: {
|
|
2334
|
+
toolCallId: firstResult.toolCallId,
|
|
2335
|
+
toolName: firstResult.toolName,
|
|
2336
|
+
skillName: `delegate/${firstResult.expertKey}`,
|
|
2337
|
+
text: firstResult.text
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
};
|
|
2341
|
+
const nextCheckpoint = {
|
|
2342
|
+
id: context.id,
|
|
2343
|
+
jobId: setting.jobId,
|
|
2344
|
+
runId: setting.runId,
|
|
2345
|
+
status: "stoppedByDelegate",
|
|
2346
|
+
stepNumber: maxStepNumber,
|
|
2347
|
+
messages: context.messages,
|
|
2348
|
+
// Restore parent's conversation history
|
|
2349
|
+
expert: {
|
|
2350
|
+
key: parentExpert.key,
|
|
2351
|
+
name: parentExpert.name,
|
|
2352
|
+
version: parentExpert.version
|
|
2353
|
+
},
|
|
2354
|
+
usage: aggregatedUsage,
|
|
2355
|
+
contextWindow: context.contextWindow,
|
|
2356
|
+
delegatedBy: context.delegatedBy,
|
|
2357
|
+
// Preserve parent reference for nested delegations
|
|
2358
|
+
delegateTo: void 0,
|
|
2359
|
+
pendingToolCalls: remainingPendingToolCalls?.length ? remainingPendingToolCalls : void 0,
|
|
2360
|
+
partialToolResults: [...context.partialToolResults ?? [], ...restToolResults]
|
|
2361
|
+
};
|
|
2362
|
+
return { nextSetting, nextCheckpoint };
|
|
2363
|
+
}
|
|
2364
|
+
async executeSingleDelegation(delegation, parentSetting, parentContext, parentExpert, runFn, parentOptions) {
|
|
2365
|
+
const { expert, toolCallId, toolName, query } = delegation;
|
|
2366
|
+
const delegateRunId = createId();
|
|
2367
|
+
const delegateSetting = {
|
|
2368
|
+
...parentSetting,
|
|
2369
|
+
runId: delegateRunId,
|
|
2370
|
+
expertKey: expert.key,
|
|
2371
|
+
input: { text: query }
|
|
2372
|
+
};
|
|
2373
|
+
const delegateCheckpoint = {
|
|
2374
|
+
id: createId(),
|
|
2375
|
+
jobId: parentSetting.jobId,
|
|
2376
|
+
runId: delegateRunId,
|
|
2377
|
+
status: "init",
|
|
2378
|
+
stepNumber: parentContext.stepNumber,
|
|
2379
|
+
messages: [],
|
|
2380
|
+
// Child starts fresh - no parent context inheritance
|
|
2381
|
+
expert: {
|
|
2382
|
+
key: expert.key,
|
|
2383
|
+
name: expert.name,
|
|
2384
|
+
version: expert.version
|
|
2385
|
+
},
|
|
2386
|
+
delegatedBy: {
|
|
2387
|
+
expert: {
|
|
2388
|
+
key: parentExpert.key,
|
|
2389
|
+
name: parentExpert.name,
|
|
2390
|
+
version: parentExpert.version
|
|
2391
|
+
},
|
|
2392
|
+
toolCallId,
|
|
2393
|
+
toolName,
|
|
2394
|
+
checkpointId: parentContext.id
|
|
2395
|
+
},
|
|
2396
|
+
usage: createEmptyUsage(),
|
|
2397
|
+
contextWindow: parentContext.contextWindow
|
|
2398
|
+
};
|
|
2399
|
+
const resultCheckpoint = await runFn(
|
|
2400
|
+
{ setting: delegateSetting, checkpoint: delegateCheckpoint },
|
|
2401
|
+
{ ...parentOptions, returnOnDelegationComplete: true }
|
|
2402
|
+
);
|
|
2403
|
+
return this.extractDelegationResult(resultCheckpoint, toolCallId, toolName, expert.key);
|
|
2404
|
+
}
|
|
2405
|
+
extractDelegationResult(checkpoint, toolCallId, toolName, expertKey) {
|
|
2406
|
+
const lastMessage = checkpoint.messages[checkpoint.messages.length - 1];
|
|
2407
|
+
if (!lastMessage || lastMessage.type !== "expertMessage") {
|
|
2408
|
+
throw new Error("Delegation error: delegation result message is incorrect");
|
|
2409
|
+
}
|
|
2410
|
+
const textPart = lastMessage.contents.find((c) => c.type === "textPart");
|
|
2411
|
+
if (!textPart || textPart.type !== "textPart") {
|
|
2412
|
+
throw new Error("Delegation error: delegation result message does not contain text");
|
|
2413
|
+
}
|
|
2414
|
+
return {
|
|
2415
|
+
toolCallId,
|
|
2416
|
+
toolName,
|
|
2417
|
+
expertKey,
|
|
2418
|
+
text: textPart.text,
|
|
2419
|
+
stepNumber: checkpoint.stepNumber,
|
|
2420
|
+
deltaUsage: checkpoint.usage
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2152
2423
|
};
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
status: "running",
|
|
2157
|
-
totalSteps: 0,
|
|
2158
|
-
startedAt: Date.now(),
|
|
2159
|
-
maxSteps,
|
|
2160
|
-
usage: createEmptyUsage()
|
|
2161
|
-
});
|
|
2162
|
-
async function run(runInput, options) {
|
|
2163
|
-
const runParams = runParamsSchema.parse(runInput);
|
|
2164
|
-
const storeCheckpoint = options?.storeCheckpoint ?? noopStoreCheckpoint;
|
|
2165
|
-
const storeEvent = options?.storeEvent ?? noopStoreEvent;
|
|
2166
|
-
const storeJob = options?.storeJob ?? noopStoreJob;
|
|
2167
|
-
const retrieveJob = options?.retrieveJob ?? noopRetrieveJob;
|
|
2168
|
-
const retrieveCheckpoint = options?.retrieveCheckpoint ?? noopRetrieveCheckpoint;
|
|
2169
|
-
const createJob = options?.createJob ?? defaultCreateJob;
|
|
2170
|
-
const eventListener = createEventListener(options?.eventListener, storeEvent);
|
|
2171
|
-
const eventEmitter = new RunEventEmitter();
|
|
2172
|
-
eventEmitter.subscribe(eventListener);
|
|
2173
|
-
let { setting, checkpoint } = runParams;
|
|
2174
|
-
const contextWindow = getContextWindow(setting.providerConfig.providerName, setting.model);
|
|
2175
|
-
let job = retrieveJob(setting.jobId) ?? createJob(setting.jobId, setting.expertKey, setting.maxSteps);
|
|
2176
|
-
if (job.status !== "running") {
|
|
2177
|
-
job = { ...job, status: "running", finishedAt: void 0 };
|
|
2424
|
+
function selectDelegationStrategy(delegationCount) {
|
|
2425
|
+
if (delegationCount === 1) {
|
|
2426
|
+
return new SingleDelegationStrategy();
|
|
2178
2427
|
}
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2428
|
+
return new ParallelDelegationStrategy();
|
|
2429
|
+
}
|
|
2430
|
+
function buildReturnFromDelegation(currentSetting, resultCheckpoint, parentCheckpoint) {
|
|
2431
|
+
return buildDelegationReturnState(currentSetting, resultCheckpoint, parentCheckpoint);
|
|
2432
|
+
}
|
|
2433
|
+
function extractDelegationContext(checkpoint) {
|
|
2434
|
+
return {
|
|
2435
|
+
id: checkpoint.id,
|
|
2436
|
+
stepNumber: checkpoint.stepNumber,
|
|
2437
|
+
contextWindow: checkpoint.contextWindow,
|
|
2438
|
+
usage: checkpoint.usage,
|
|
2439
|
+
pendingToolCalls: checkpoint.pendingToolCalls,
|
|
2440
|
+
partialToolResults: checkpoint.partialToolResults,
|
|
2441
|
+
delegatedBy: checkpoint.delegatedBy,
|
|
2442
|
+
// Preserve for nested delegations
|
|
2443
|
+
messages: checkpoint.messages
|
|
2444
|
+
// Preserve for parent continuation after delegation
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
var RunEventEmitter = class {
|
|
2448
|
+
listeners = [];
|
|
2449
|
+
subscribe(listener) {
|
|
2450
|
+
this.listeners.push(listener);
|
|
2451
|
+
}
|
|
2452
|
+
async emit(event) {
|
|
2453
|
+
const errors = [];
|
|
2454
|
+
for (const listener of this.listeners) {
|
|
2455
|
+
try {
|
|
2456
|
+
await listener({
|
|
2457
|
+
...event,
|
|
2458
|
+
id: createId(),
|
|
2459
|
+
timestamp: Date.now()
|
|
2460
|
+
});
|
|
2461
|
+
} catch (error) {
|
|
2462
|
+
errors.push(error instanceof Error ? error : new Error(String(error)));
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
if (errors.length > 0) {
|
|
2466
|
+
throw new AggregateError(errors, "One or more event listeners failed");
|
|
2197
2467
|
}
|
|
2468
|
+
}
|
|
2469
|
+
};
|
|
2470
|
+
|
|
2471
|
+
// src/orchestration/single-run-executor.ts
|
|
2472
|
+
var SingleRunExecutor = class {
|
|
2473
|
+
constructor(options = {}) {
|
|
2474
|
+
this.options = options;
|
|
2475
|
+
}
|
|
2476
|
+
async execute(setting, checkpoint) {
|
|
2477
|
+
const contextWindow = getContextWindow(setting.providerConfig.providerName, setting.model);
|
|
2478
|
+
const { expertToRun, experts } = await setupExperts(setting, this.options.resolveExpertToRun);
|
|
2479
|
+
this.emitInitEvent(setting, expertToRun, experts);
|
|
2198
2480
|
const skillManagers = await getSkillManagers(
|
|
2199
2481
|
expertToRun,
|
|
2200
2482
|
experts,
|
|
2201
2483
|
setting,
|
|
2202
|
-
options
|
|
2484
|
+
this.options.eventListener,
|
|
2203
2485
|
{ isDelegatedRun: !!checkpoint?.delegatedBy }
|
|
2204
2486
|
);
|
|
2205
2487
|
const initialCheckpoint = checkpoint ? createNextStepCheckpoint(createId(), checkpoint) : createInitialCheckpoint(createId(), {
|
|
@@ -2209,183 +2491,148 @@ async function run(runInput, options) {
|
|
|
2209
2491
|
expert: expertToRun,
|
|
2210
2492
|
contextWindow
|
|
2211
2493
|
});
|
|
2212
|
-
const
|
|
2494
|
+
const eventEmitter = new RunEventEmitter();
|
|
2495
|
+
const eventListener = this.createEventListener();
|
|
2496
|
+
eventEmitter.subscribe(eventListener);
|
|
2497
|
+
const resultCheckpoint = await executeStateMachine({
|
|
2213
2498
|
setting: { ...setting, experts },
|
|
2214
2499
|
initialCheckpoint,
|
|
2215
2500
|
eventListener,
|
|
2216
2501
|
skillManagers,
|
|
2217
2502
|
eventEmitter,
|
|
2218
|
-
storeCheckpoint
|
|
2219
|
-
|
|
2503
|
+
storeCheckpoint: this.options.storeCheckpoint ?? (async () => {
|
|
2504
|
+
}),
|
|
2505
|
+
shouldContinueRun: this.options.shouldContinueRun
|
|
2506
|
+
});
|
|
2507
|
+
return { checkpoint: resultCheckpoint, expertToRun, experts };
|
|
2508
|
+
}
|
|
2509
|
+
createEventListener() {
|
|
2510
|
+
const userListener = this.options.eventListener;
|
|
2511
|
+
const storeEvent = this.options.storeEvent;
|
|
2512
|
+
return async (event) => {
|
|
2513
|
+
if ("stepNumber" in event && storeEvent) {
|
|
2514
|
+
await storeEvent(event);
|
|
2515
|
+
}
|
|
2516
|
+
userListener?.(event);
|
|
2517
|
+
};
|
|
2518
|
+
}
|
|
2519
|
+
emitInitEvent(setting, expertToRun, experts) {
|
|
2520
|
+
if (!this.options.eventListener) return;
|
|
2521
|
+
const initEvent = createRuntimeEvent("initializeRuntime", setting.jobId, setting.runId, {
|
|
2522
|
+
runtimeVersion: package_default.version,
|
|
2523
|
+
runtime: "local",
|
|
2524
|
+
expertName: expertToRun.name,
|
|
2525
|
+
experts: Object.keys(experts),
|
|
2526
|
+
model: setting.model,
|
|
2527
|
+
temperature: setting.temperature,
|
|
2528
|
+
maxSteps: setting.maxSteps,
|
|
2529
|
+
maxRetries: setting.maxRetries,
|
|
2530
|
+
timeout: setting.timeout,
|
|
2531
|
+
query: setting.input.text,
|
|
2532
|
+
interactiveToolCall: setting.input.interactiveToolCallResult
|
|
2220
2533
|
});
|
|
2534
|
+
this.options.eventListener(initEvent);
|
|
2535
|
+
}
|
|
2536
|
+
};
|
|
2537
|
+
|
|
2538
|
+
// src/run.ts
|
|
2539
|
+
var defaultCreateJob = (jobId, expertKey, maxSteps) => ({
|
|
2540
|
+
id: jobId,
|
|
2541
|
+
coordinatorExpertKey: expertKey,
|
|
2542
|
+
status: "running",
|
|
2543
|
+
totalSteps: 0,
|
|
2544
|
+
startedAt: Date.now(),
|
|
2545
|
+
maxSteps,
|
|
2546
|
+
usage: createEmptyUsage()
|
|
2547
|
+
});
|
|
2548
|
+
async function run(runInput, options) {
|
|
2549
|
+
const runParams = runParamsSchema.parse(runInput);
|
|
2550
|
+
let { setting, checkpoint } = runParams;
|
|
2551
|
+
const storeJob = options?.storeJob ?? (() => {
|
|
2552
|
+
});
|
|
2553
|
+
const retrieveJob = options?.retrieveJob ?? (() => void 0);
|
|
2554
|
+
const retrieveCheckpoint = options?.retrieveCheckpoint ?? (async () => {
|
|
2555
|
+
throw new Error("retrieveCheckpoint not provided");
|
|
2556
|
+
});
|
|
2557
|
+
const createJob = options?.createJob ?? defaultCreateJob;
|
|
2558
|
+
let job = retrieveJob(setting.jobId) ?? createJob(setting.jobId, setting.expertKey, setting.maxSteps);
|
|
2559
|
+
if (job.status !== "running") {
|
|
2560
|
+
job = { ...job, status: "running", finishedAt: void 0 };
|
|
2561
|
+
}
|
|
2562
|
+
storeJob(job);
|
|
2563
|
+
const runExecutor = new SingleRunExecutor({
|
|
2564
|
+
shouldContinueRun: options?.shouldContinueRun,
|
|
2565
|
+
storeCheckpoint: options?.storeCheckpoint,
|
|
2566
|
+
storeEvent: options?.storeEvent,
|
|
2567
|
+
eventListener: options?.eventListener,
|
|
2568
|
+
resolveExpertToRun: options?.resolveExpertToRun
|
|
2569
|
+
});
|
|
2570
|
+
while (true) {
|
|
2571
|
+
const runResult = await runExecutor.execute(setting, checkpoint);
|
|
2572
|
+
const resultCheckpoint = runResult.checkpoint;
|
|
2221
2573
|
job = {
|
|
2222
2574
|
...job,
|
|
2223
|
-
totalSteps:
|
|
2224
|
-
usage:
|
|
2575
|
+
totalSteps: resultCheckpoint.stepNumber,
|
|
2576
|
+
usage: resultCheckpoint.usage
|
|
2225
2577
|
};
|
|
2226
|
-
switch (
|
|
2578
|
+
switch (resultCheckpoint.status) {
|
|
2227
2579
|
case "completed": {
|
|
2228
2580
|
if (options?.returnOnDelegationComplete) {
|
|
2229
2581
|
storeJob(job);
|
|
2230
|
-
return
|
|
2582
|
+
return resultCheckpoint;
|
|
2231
2583
|
}
|
|
2232
|
-
if (
|
|
2584
|
+
if (resultCheckpoint.delegatedBy) {
|
|
2233
2585
|
storeJob(job);
|
|
2234
2586
|
const parentCheckpoint = await retrieveCheckpoint(
|
|
2235
2587
|
setting.jobId,
|
|
2236
|
-
|
|
2588
|
+
resultCheckpoint.delegatedBy.checkpointId
|
|
2237
2589
|
);
|
|
2238
|
-
const result =
|
|
2590
|
+
const result = buildReturnFromDelegation(setting, resultCheckpoint, parentCheckpoint);
|
|
2239
2591
|
setting = result.setting;
|
|
2240
2592
|
checkpoint = result.checkpoint;
|
|
2241
2593
|
break;
|
|
2242
2594
|
}
|
|
2243
2595
|
storeJob({ ...job, status: "completed", finishedAt: Date.now() });
|
|
2244
|
-
return
|
|
2596
|
+
return resultCheckpoint;
|
|
2245
2597
|
}
|
|
2246
2598
|
case "stoppedByInteractiveTool": {
|
|
2247
2599
|
storeJob({ ...job, status: "stoppedByInteractiveTool" });
|
|
2248
|
-
return
|
|
2600
|
+
return resultCheckpoint;
|
|
2249
2601
|
}
|
|
2250
2602
|
case "stoppedByDelegate": {
|
|
2251
2603
|
storeJob(job);
|
|
2252
|
-
const { delegateTo } =
|
|
2604
|
+
const { delegateTo } = resultCheckpoint;
|
|
2253
2605
|
if (!delegateTo || delegateTo.length === 0) {
|
|
2254
2606
|
throw new Error("No delegations found in checkpoint");
|
|
2255
2607
|
}
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
delegateTo.map(
|
|
2266
|
-
(delegation) => runDelegate(delegation, setting, runResultCheckpoint, expertToRun, options)
|
|
2267
|
-
)
|
|
2608
|
+
const strategy = selectDelegationStrategy(delegateTo.length);
|
|
2609
|
+
const context = extractDelegationContext(resultCheckpoint);
|
|
2610
|
+
const delegationResult = await strategy.execute(
|
|
2611
|
+
delegateTo,
|
|
2612
|
+
setting,
|
|
2613
|
+
context,
|
|
2614
|
+
runResult.expertToRun,
|
|
2615
|
+
run,
|
|
2616
|
+
options
|
|
2268
2617
|
);
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
(acc, result) => sumUsage(acc, result.deltaUsage),
|
|
2272
|
-
runResultCheckpoint.usage
|
|
2273
|
-
);
|
|
2274
|
-
const maxStepNumber = Math.max(...allResults.map((r) => r.stepNumber));
|
|
2275
|
-
const restToolResults = restResults.map((result) => ({
|
|
2276
|
-
id: result.toolCallId,
|
|
2277
|
-
skillName: `delegate/${result.expertKey}`,
|
|
2278
|
-
toolName: result.toolName,
|
|
2279
|
-
result: [{ type: "textPart", id: createId(), text: result.text }]
|
|
2280
|
-
}));
|
|
2281
|
-
const processedToolCallIds = new Set(remainingDelegations.map((d) => d.toolCallId));
|
|
2282
|
-
const remainingToolCalls = runResultCheckpoint.pendingToolCalls?.filter(
|
|
2283
|
-
(tc) => !processedToolCallIds.has(tc.id) && tc.id !== firstDelegation.toolCallId
|
|
2284
|
-
);
|
|
2285
|
-
setting = {
|
|
2286
|
-
...setting,
|
|
2287
|
-
expertKey: expertToRun.key,
|
|
2288
|
-
input: {
|
|
2289
|
-
interactiveToolCallResult: {
|
|
2290
|
-
toolCallId: firstResult.toolCallId,
|
|
2291
|
-
toolName: firstResult.toolName,
|
|
2292
|
-
skillName: `delegate/${firstResult.expertKey}`,
|
|
2293
|
-
text: firstResult.text
|
|
2294
|
-
}
|
|
2295
|
-
}
|
|
2296
|
-
};
|
|
2297
|
-
checkpoint = {
|
|
2298
|
-
...runResultCheckpoint,
|
|
2299
|
-
status: "stoppedByDelegate",
|
|
2300
|
-
delegateTo: void 0,
|
|
2301
|
-
stepNumber: maxStepNumber,
|
|
2302
|
-
usage: aggregatedUsage,
|
|
2303
|
-
pendingToolCalls: remainingToolCalls?.length ? remainingToolCalls : void 0,
|
|
2304
|
-
partialToolResults: [
|
|
2305
|
-
...runResultCheckpoint.partialToolResults ?? [],
|
|
2306
|
-
...restToolResults
|
|
2307
|
-
]
|
|
2308
|
-
};
|
|
2618
|
+
setting = delegationResult.nextSetting;
|
|
2619
|
+
checkpoint = delegationResult.nextCheckpoint;
|
|
2309
2620
|
break;
|
|
2310
2621
|
}
|
|
2311
2622
|
case "stoppedByExceededMaxSteps": {
|
|
2312
2623
|
storeJob({ ...job, status: "stoppedByMaxSteps", finishedAt: Date.now() });
|
|
2313
|
-
return
|
|
2624
|
+
return resultCheckpoint;
|
|
2314
2625
|
}
|
|
2315
2626
|
case "stoppedByError": {
|
|
2316
2627
|
storeJob({ ...job, status: "stoppedByError", finishedAt: Date.now() });
|
|
2317
|
-
return
|
|
2628
|
+
return resultCheckpoint;
|
|
2318
2629
|
}
|
|
2319
2630
|
default:
|
|
2320
2631
|
throw new Error("Run stopped by unknown reason");
|
|
2321
2632
|
}
|
|
2322
2633
|
}
|
|
2323
2634
|
}
|
|
2324
|
-
function createEventListener(userListener, storeEvent) {
|
|
2325
|
-
const listener = userListener ?? ((e) => console.log(JSON.stringify(e)));
|
|
2326
|
-
return async (event) => {
|
|
2327
|
-
if ("stepNumber" in event && storeEvent) {
|
|
2328
|
-
await storeEvent(event);
|
|
2329
|
-
}
|
|
2330
|
-
listener(event);
|
|
2331
|
-
};
|
|
2332
|
-
}
|
|
2333
|
-
async function runDelegate(delegation, parentSetting, parentCheckpoint, parentExpert, options) {
|
|
2334
|
-
const { expert, toolCallId, toolName, query } = delegation;
|
|
2335
|
-
const delegateRunId = createId();
|
|
2336
|
-
const delegateSetting = {
|
|
2337
|
-
...parentSetting,
|
|
2338
|
-
runId: delegateRunId,
|
|
2339
|
-
expertKey: expert.key,
|
|
2340
|
-
input: { text: query }
|
|
2341
|
-
};
|
|
2342
|
-
const delegateCheckpoint = {
|
|
2343
|
-
id: createId(),
|
|
2344
|
-
jobId: parentSetting.jobId,
|
|
2345
|
-
runId: delegateRunId,
|
|
2346
|
-
status: "init",
|
|
2347
|
-
stepNumber: parentCheckpoint.stepNumber,
|
|
2348
|
-
messages: [],
|
|
2349
|
-
expert: {
|
|
2350
|
-
key: expert.key,
|
|
2351
|
-
name: expert.name,
|
|
2352
|
-
version: expert.version
|
|
2353
|
-
},
|
|
2354
|
-
delegatedBy: {
|
|
2355
|
-
expert: {
|
|
2356
|
-
key: parentExpert.key,
|
|
2357
|
-
name: parentExpert.name,
|
|
2358
|
-
version: parentExpert.version
|
|
2359
|
-
},
|
|
2360
|
-
toolCallId,
|
|
2361
|
-
toolName,
|
|
2362
|
-
checkpointId: parentCheckpoint.id
|
|
2363
|
-
},
|
|
2364
|
-
usage: createEmptyUsage(),
|
|
2365
|
-
contextWindow: parentCheckpoint.contextWindow
|
|
2366
|
-
};
|
|
2367
|
-
const resultCheckpoint = await run(
|
|
2368
|
-
{ setting: delegateSetting, checkpoint: delegateCheckpoint },
|
|
2369
|
-
{ ...options, returnOnDelegationComplete: true }
|
|
2370
|
-
);
|
|
2371
|
-
const lastMessage = resultCheckpoint.messages[resultCheckpoint.messages.length - 1];
|
|
2372
|
-
if (!lastMessage || lastMessage.type !== "expertMessage") {
|
|
2373
|
-
throw new Error("Delegation error: delegation result message is incorrect");
|
|
2374
|
-
}
|
|
2375
|
-
const textPart = lastMessage.contents.find((c) => c.type === "textPart");
|
|
2376
|
-
if (!textPart || textPart.type !== "textPart") {
|
|
2377
|
-
throw new Error("Delegation error: delegation result message does not contain text");
|
|
2378
|
-
}
|
|
2379
|
-
return {
|
|
2380
|
-
toolCallId,
|
|
2381
|
-
toolName,
|
|
2382
|
-
expertKey: expert.key,
|
|
2383
|
-
text: textPart.text,
|
|
2384
|
-
stepNumber: resultCheckpoint.stepNumber,
|
|
2385
|
-
deltaUsage: resultCheckpoint.usage
|
|
2386
|
-
};
|
|
2387
|
-
}
|
|
2388
2635
|
|
|
2389
2636
|
export { getModel, package_default, run, runtimeStateMachine };
|
|
2390
|
-
//# sourceMappingURL=chunk-
|
|
2391
|
-
//# sourceMappingURL=chunk-
|
|
2637
|
+
//# sourceMappingURL=chunk-H65LPOAK.js.map
|
|
2638
|
+
//# sourceMappingURL=chunk-H65LPOAK.js.map
|