@perstack/runtime 0.0.48 → 0.0.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/index.js CHANGED
@@ -1,27 +1,27 @@
1
1
  import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock';
2
2
  import { createAnthropic } from '@ai-sdk/anthropic';
3
3
  import { createAzure } from '@ai-sdk/azure';
4
+ import { createDeepSeek } from '@ai-sdk/deepseek';
4
5
  import { createGoogleGenerativeAI } from '@ai-sdk/google';
5
6
  import { createVertex } from '@ai-sdk/google-vertex';
6
7
  import { createOpenAI } from '@ai-sdk/openai';
7
8
  import { knownModels, stopRunByDelegate, stopRunByInteractiveTool, resolveThought, attemptCompletion, resolvePdfFile, resolveImageFile, resolveToolResult, stopRunByExceededMaxSteps, continueToNextStep, retry, completeRun, callDelegate, callInteractiveTool, callTool, startRun, startGeneration, finishToolCall, runParamsSchema, createRuntimeEvent, checkpointSchema } from '@perstack/core';
8
9
  import { createOllama } from 'ollama-ai-provider-v2';
9
- import { existsSync } from 'fs';
10
- import { readFile, writeFile, mkdir, readdir } from 'fs/promises';
11
- import path from 'path';
12
10
  import { createId } from '@paralleldrive/cuid2';
11
+ import { readFile, readdir, mkdir, writeFile } from 'fs/promises';
12
+ import path from 'path';
13
13
  import { setup, assign, createActor } from 'xstate';
14
- import { ApiV1Client } from '@perstack/api-client/v1';
14
+ import { generateText, tool, jsonSchema } from 'ai';
15
15
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
16
16
  import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
17
17
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
18
18
  import { McpError } from '@modelcontextprotocol/sdk/types.js';
19
- import { generateText, tool, jsonSchema } from 'ai';
20
19
  import { dedent } from 'ts-dedent';
20
+ import { ApiV1Client } from '@perstack/api-client/v1';
21
21
 
22
22
  // package.json
23
23
  var package_default = {
24
- version: "0.0.48"};
24
+ version: "0.0.50"};
25
25
  function getModel(modelId, providerConfig) {
26
26
  switch (providerConfig.providerName) {
27
27
  case "anthropic": {
@@ -87,6 +87,14 @@ function getModel(modelId, providerConfig) {
87
87
  });
88
88
  return vertex(modelId);
89
89
  }
90
+ case "deepseek": {
91
+ const deepseek = createDeepSeek({
92
+ apiKey: providerConfig.apiKey,
93
+ baseURL: providerConfig.baseUrl,
94
+ headers: providerConfig.headers
95
+ });
96
+ return deepseek(modelId);
97
+ }
90
98
  }
91
99
  }
92
100
  function getContextWindow(providerName, modelId) {
@@ -96,8 +104,167 @@ function getContextWindow(providerName, modelId) {
96
104
  function calculateContextWindowUsage(usage, contextWindow) {
97
105
  return (usage.inputTokens + usage.cachedInputTokens + usage.outputTokens) / contextWindow;
98
106
  }
107
+
108
+ // src/usage.ts
109
+ function createEmptyUsage() {
110
+ return {
111
+ inputTokens: 0,
112
+ outputTokens: 0,
113
+ reasoningTokens: 0,
114
+ totalTokens: 0,
115
+ cachedInputTokens: 0
116
+ };
117
+ }
118
+ function usageFromGenerateTextResult(result) {
119
+ return {
120
+ inputTokens: result.usage.inputTokens || 0,
121
+ outputTokens: result.usage.outputTokens || 0,
122
+ reasoningTokens: result.usage.reasoningTokens || 0,
123
+ totalTokens: result.usage.totalTokens || 0,
124
+ cachedInputTokens: result.usage.cachedInputTokens || 0
125
+ };
126
+ }
127
+ function sumUsage(a, b) {
128
+ return {
129
+ inputTokens: a.inputTokens + b.inputTokens,
130
+ outputTokens: a.outputTokens + b.outputTokens,
131
+ reasoningTokens: a.reasoningTokens + b.reasoningTokens,
132
+ totalTokens: a.totalTokens + b.totalTokens,
133
+ cachedInputTokens: a.cachedInputTokens + b.cachedInputTokens
134
+ };
135
+ }
136
+
137
+ // src/checkpoint-helpers.ts
138
+ function createInitialCheckpoint(checkpointId, params) {
139
+ return {
140
+ id: checkpointId,
141
+ runId: params.runId,
142
+ expert: {
143
+ key: params.expertKey,
144
+ name: params.expert.name,
145
+ version: params.expert.version
146
+ },
147
+ stepNumber: 1,
148
+ status: "init",
149
+ messages: [],
150
+ usage: createEmptyUsage(),
151
+ contextWindow: params.contextWindow,
152
+ contextWindowUsage: params.contextWindow ? 0 : void 0
153
+ };
154
+ }
155
+ function createNextStepCheckpoint(checkpointId, checkpoint) {
156
+ return {
157
+ ...checkpoint,
158
+ id: checkpointId,
159
+ stepNumber: checkpoint.stepNumber + 1
160
+ };
161
+ }
162
+ function buildDelegationReturnState(currentSetting, resultCheckpoint, parentCheckpoint) {
163
+ const { messages, delegatedBy } = resultCheckpoint;
164
+ if (!delegatedBy) {
165
+ throw new Error("delegatedBy is required for buildDelegationReturnState");
166
+ }
167
+ const delegateResultMessage = messages[messages.length - 1];
168
+ if (!delegateResultMessage || delegateResultMessage.type !== "expertMessage") {
169
+ throw new Error("Delegation error: delegation result message is incorrect");
170
+ }
171
+ const delegateText = delegateResultMessage.contents.find((content) => content.type === "textPart");
172
+ if (!delegateText) {
173
+ throw new Error("Delegation error: delegation result message does not contain a text");
174
+ }
175
+ const { expert, toolCallId, toolName } = delegatedBy;
176
+ return {
177
+ setting: {
178
+ ...currentSetting,
179
+ expertKey: expert.key,
180
+ input: {
181
+ interactiveToolCallResult: {
182
+ toolCallId,
183
+ toolName,
184
+ text: delegateText.text
185
+ }
186
+ }
187
+ },
188
+ checkpoint: {
189
+ ...parentCheckpoint,
190
+ stepNumber: resultCheckpoint.stepNumber,
191
+ usage: resultCheckpoint.usage
192
+ }
193
+ };
194
+ }
195
+ function buildDelegateToState(currentSetting, resultCheckpoint, currentExpert) {
196
+ const { delegateTo } = resultCheckpoint;
197
+ if (!delegateTo) {
198
+ throw new Error("delegateTo is required for buildDelegateToState");
199
+ }
200
+ const { expert, toolCallId, toolName, query } = delegateTo;
201
+ return {
202
+ setting: {
203
+ ...currentSetting,
204
+ expertKey: expert.key,
205
+ input: {
206
+ text: query
207
+ }
208
+ },
209
+ checkpoint: {
210
+ ...resultCheckpoint,
211
+ status: "init",
212
+ messages: [],
213
+ expert: {
214
+ key: expert.key,
215
+ name: expert.name,
216
+ version: expert.version
217
+ },
218
+ delegatedBy: {
219
+ expert: {
220
+ key: currentExpert.key,
221
+ name: currentExpert.name,
222
+ version: currentExpert.version
223
+ },
224
+ toolCallId,
225
+ toolName,
226
+ checkpointId: resultCheckpoint.id
227
+ },
228
+ usage: resultCheckpoint.usage
229
+ }
230
+ };
231
+ }
232
+ async function createDefaultFileSystem() {
233
+ const fs = await import('fs');
234
+ const fsPromises = await import('fs/promises');
235
+ return {
236
+ existsSync: fs.existsSync,
237
+ mkdir: async (p, options) => {
238
+ await fsPromises.mkdir(p, options);
239
+ },
240
+ readFile: fsPromises.readFile,
241
+ writeFile: fsPromises.writeFile
242
+ };
243
+ }
244
+ function defaultGetRunDir(runId) {
245
+ return `${process.cwd()}/perstack/runs/${runId}`;
246
+ }
247
+ async function storeRunSetting(setting, fs, getRunDir = defaultGetRunDir) {
248
+ const fileSystem = fs ?? await createDefaultFileSystem();
249
+ const runDir = getRunDir(setting.runId);
250
+ if (fileSystem.existsSync(runDir)) {
251
+ const runSettingPath = path.resolve(runDir, "run-setting.json");
252
+ const runSetting = JSON.parse(await fileSystem.readFile(runSettingPath, "utf-8"));
253
+ runSetting.updatedAt = Date.now();
254
+ await fileSystem.writeFile(runSettingPath, JSON.stringify(runSetting), "utf-8");
255
+ } else {
256
+ await fileSystem.mkdir(runDir, { recursive: true });
257
+ await fileSystem.writeFile(
258
+ path.resolve(runDir, "run-setting.json"),
259
+ JSON.stringify(setting),
260
+ "utf-8"
261
+ );
262
+ }
263
+ }
264
+
265
+ // src/default-store.ts
99
266
  async function defaultRetrieveCheckpoint(runId, checkpointId) {
100
- const runDir = getRunDir(runId);
267
+ const runDir = defaultGetRunDir(runId);
101
268
  const checkpointFiles = await readdir(runDir, { withFileTypes: true }).then(
102
269
  (files) => files.filter((file) => file.isFile() && file.name.startsWith("checkpoint-"))
103
270
  );
@@ -111,14 +278,14 @@ async function defaultRetrieveCheckpoint(runId, checkpointId) {
111
278
  }
112
279
  async function defaultStoreCheckpoint(checkpoint, timestamp) {
113
280
  const { id, runId, stepNumber } = checkpoint;
114
- const runDir = getRunDir(runId);
281
+ const runDir = defaultGetRunDir(runId);
115
282
  const checkpointPath = `${runDir}/checkpoint-${timestamp}-${stepNumber}-${id}.json`;
116
283
  await mkdir(runDir, { recursive: true });
117
284
  await writeFile(checkpointPath, JSON.stringify(checkpoint));
118
285
  }
119
286
  async function defaultStoreEvent(event) {
120
287
  const { timestamp, runId, stepNumber, type } = event;
121
- const runDir = getRunDir(runId);
288
+ const runDir = defaultGetRunDir(runId);
122
289
  const eventPath = `${runDir}/event-${timestamp}-${stepNumber}-${type}.json`;
123
290
  await mkdir(runDir, { recursive: true });
124
291
  await writeFile(eventPath, JSON.stringify(event));
@@ -138,76 +305,20 @@ var RunEventEmitter = class {
138
305
  }
139
306
  }
140
307
  };
141
- async function resolveExpertToRun(expertKey, experts, clientOptions) {
142
- if (experts[expertKey]) {
143
- return experts[expertKey];
144
- }
145
- const client = new ApiV1Client({
146
- baseUrl: clientOptions.perstackApiBaseUrl,
147
- apiKey: clientOptions.perstackApiKey
148
- });
149
- const { expert } = await client.registry.experts.get({ expertKey });
150
- experts[expertKey] = toRuntimeExpert(expert);
151
- return experts[expertKey];
152
- }
153
- function toRuntimeExpert(expert) {
154
- const skills = Object.fromEntries(
155
- Object.entries(expert.skills).map(([name, skill]) => {
156
- switch (skill.type) {
157
- case "mcpStdioSkill":
158
- return [name, { ...skill, name }];
159
- case "mcpSseSkill":
160
- return [name, { ...skill, name }];
161
- case "interactiveSkill":
162
- return [name, { ...skill, name }];
163
- default: {
164
- throw new Error(`Unknown skill type: ${skill.type}`);
165
- }
166
- }
167
- })
168
- );
169
- return { ...expert, skills };
170
- }
171
- var SkillManager = class {
308
+
309
+ // src/skill-manager/base.ts
310
+ var BaseSkillManager = class {
172
311
  _toolDefinitions = [];
173
312
  _initialized = false;
174
313
  _initializing;
175
- name;
176
- type;
177
- lazyInit;
178
314
  skill;
179
315
  interactiveSkill;
180
316
  expert;
181
- _mcpClient;
182
- _params;
183
- _env;
184
317
  _runId;
185
318
  _eventListener;
186
- constructor(params, runId, eventListener) {
187
- this._params = params;
319
+ constructor(runId, eventListener) {
188
320
  this._runId = runId;
189
321
  this._eventListener = eventListener;
190
- this.type = params.type;
191
- switch (params.type) {
192
- case "mcp":
193
- this.name = params.skill.name;
194
- this.skill = params.skill;
195
- this._env = params.env;
196
- this.lazyInit = this.skill.type === "mcpStdioSkill" && this.skill.lazyInit && this.skill.name !== "@perstack/base";
197
- break;
198
- case "interactive":
199
- this.name = params.interactiveSkill.name;
200
- this.interactiveSkill = params.interactiveSkill;
201
- this._env = {};
202
- this.lazyInit = false;
203
- break;
204
- case "delegate":
205
- this.name = params.expert.name;
206
- this.expert = params.expert;
207
- this._env = {};
208
- this.lazyInit = false;
209
- break;
210
- }
211
322
  }
212
323
  async init() {
213
324
  if (this._initialized) {
@@ -232,75 +343,146 @@ var SkillManager = class {
232
343
  return this._initialized;
233
344
  }
234
345
  async _performInit() {
235
- switch (this._params.type) {
236
- case "mcp": {
237
- await this._initMcpSkill(this._params);
238
- break;
239
- }
240
- case "interactive": {
241
- await this._initInteractiveSkill(this._params);
242
- break;
243
- }
244
- case "delegate": {
245
- await this._initDelegate(this._params);
246
- break;
247
- }
248
- }
346
+ await this._doInit();
249
347
  this._initialized = true;
250
348
  this._initializing = void 0;
251
349
  }
252
- async _initMcpSkill(params) {
253
- if (this.isInitialized()) {
254
- throw new Error(`Skill ${params.skill.name} is already initialized`);
350
+ async getToolDefinitions() {
351
+ if (!this.isInitialized() && !this.lazyInit) {
352
+ throw new Error(`Skill ${this.name} is not initialized`);
353
+ }
354
+ if (!this.isInitialized() && this.lazyInit) {
355
+ return [];
255
356
  }
357
+ return this._filterTools(this._toolDefinitions);
358
+ }
359
+ _filterTools(tools) {
360
+ return tools;
361
+ }
362
+ };
363
+
364
+ // src/skill-manager/delegate.ts
365
+ var DelegateSkillManager = class extends BaseSkillManager {
366
+ name;
367
+ type = "delegate";
368
+ lazyInit = false;
369
+ expert;
370
+ constructor(expert, runId, eventListener) {
371
+ super(runId, eventListener);
372
+ this.name = expert.name;
373
+ this.expert = expert;
374
+ }
375
+ async _doInit() {
376
+ this._toolDefinitions = [
377
+ {
378
+ skillName: this.expert.name,
379
+ name: this.expert.name.split("/").pop() ?? this.expert.name,
380
+ description: this.expert.description,
381
+ inputSchema: {
382
+ type: "object",
383
+ properties: { query: { type: "string" } },
384
+ required: ["query"]
385
+ },
386
+ interactive: false
387
+ }
388
+ ];
389
+ }
390
+ async close() {
391
+ }
392
+ async callTool(_toolName, _input) {
393
+ return [];
394
+ }
395
+ };
396
+
397
+ // src/skill-manager/interactive.ts
398
+ var InteractiveSkillManager = class extends BaseSkillManager {
399
+ name;
400
+ type = "interactive";
401
+ lazyInit = false;
402
+ interactiveSkill;
403
+ constructor(interactiveSkill, runId, eventListener) {
404
+ super(runId, eventListener);
405
+ this.name = interactiveSkill.name;
406
+ this.interactiveSkill = interactiveSkill;
407
+ }
408
+ async _doInit() {
409
+ this._toolDefinitions = Object.values(this.interactiveSkill.tools).map((tool2) => ({
410
+ skillName: this.interactiveSkill.name,
411
+ name: tool2.name,
412
+ description: tool2.description,
413
+ inputSchema: JSON.parse(tool2.inputJsonSchema),
414
+ interactive: true
415
+ }));
416
+ }
417
+ async close() {
418
+ }
419
+ async callTool(_toolName, _input) {
420
+ return [];
421
+ }
422
+ };
423
+ var McpSkillManager = class extends BaseSkillManager {
424
+ name;
425
+ type = "mcp";
426
+ lazyInit;
427
+ skill;
428
+ _mcpClient;
429
+ _env;
430
+ constructor(skill, env, runId, eventListener) {
431
+ super(runId, eventListener);
432
+ this.name = skill.name;
433
+ this.skill = skill;
434
+ this._env = env;
435
+ this.lazyInit = skill.type === "mcpStdioSkill" && skill.lazyInit && skill.name !== "@perstack/base";
436
+ }
437
+ async _doInit() {
256
438
  this._mcpClient = new Client({
257
- name: `${params.skill.name}-mcp-client`,
439
+ name: `${this.skill.name}-mcp-client`,
258
440
  version: "1.0.0"
259
441
  });
260
- switch (params.skill.type) {
261
- case "mcpStdioSkill": {
262
- if (!params.skill.command) {
263
- throw new Error(`Skill ${params.skill.name} has no command`);
264
- }
265
- const env = {};
266
- const { requiredEnv } = params.skill;
267
- for (const envName of requiredEnv) {
268
- if (!this._env[envName]) {
269
- throw new Error(`Skill ${params.skill.name} requires environment variable ${envName}`);
270
- }
271
- env[envName] = this._env[envName];
272
- }
273
- const { command, args } = this._getCommandArgs(params.skill);
274
- const transport = new StdioClientTransport({ command, args, env, stderr: "ignore" });
275
- await this._mcpClient.connect(transport);
276
- if (this._eventListener) {
277
- const serverInfo = this._mcpClient.getServerVersion();
278
- const event = createRuntimeEvent("skillConnected", this._runId, {
279
- skillName: params.skill.name,
280
- serverInfo: serverInfo ? { name: serverInfo.name, version: serverInfo.version } : void 0
281
- });
282
- this._eventListener(event);
283
- }
284
- break;
285
- }
286
- case "mcpSseSkill": {
287
- if (!params.skill.endpoint) {
288
- throw new Error(`Skill ${params.skill.name} has no endpoint`);
289
- }
290
- const transport = new SSEClientTransport(new URL(params.skill.endpoint));
291
- await this._mcpClient.connect(transport);
292
- break;
293
- }
442
+ if (this.skill.type === "mcpStdioSkill") {
443
+ await this._initStdio(this.skill);
444
+ } else {
445
+ await this._initSse(this.skill);
294
446
  }
295
447
  const { tools } = await this._mcpClient.listTools();
296
448
  this._toolDefinitions = tools.map((tool2) => ({
297
- skillName: params.skill.name,
449
+ skillName: this.skill.name,
298
450
  name: tool2.name,
299
451
  description: tool2.description,
300
452
  inputSchema: tool2.inputSchema,
301
453
  interactive: false
302
454
  }));
303
455
  }
456
+ async _initStdio(skill) {
457
+ if (!skill.command) {
458
+ throw new Error(`Skill ${skill.name} has no command`);
459
+ }
460
+ const env = {};
461
+ for (const envName of skill.requiredEnv) {
462
+ if (!this._env[envName]) {
463
+ throw new Error(`Skill ${skill.name} requires environment variable ${envName}`);
464
+ }
465
+ env[envName] = this._env[envName];
466
+ }
467
+ const { command, args } = this._getCommandArgs(skill);
468
+ const transport = new StdioClientTransport({ command, args, env, stderr: "ignore" });
469
+ await this._mcpClient.connect(transport);
470
+ if (this._eventListener) {
471
+ const serverInfo = this._mcpClient.getServerVersion();
472
+ const event = createRuntimeEvent("skillConnected", this._runId, {
473
+ skillName: skill.name,
474
+ serverInfo: serverInfo ? { name: serverInfo.name, version: serverInfo.version } : void 0
475
+ });
476
+ this._eventListener(event);
477
+ }
478
+ }
479
+ async _initSse(skill) {
480
+ if (!skill.endpoint) {
481
+ throw new Error(`Skill ${skill.name} has no endpoint`);
482
+ }
483
+ const transport = new SSEClientTransport(new URL(skill.endpoint));
484
+ await this._mcpClient.connect(transport);
485
+ }
304
486
  _getCommandArgs(skill) {
305
487
  const { name, command, packageName, args } = skill;
306
488
  if (!packageName && (!args || args.length === 0)) {
@@ -317,36 +499,6 @@ var SkillManager = class {
317
499
  }
318
500
  return { command, args: newArgs };
319
501
  }
320
- async _initInteractiveSkill(params) {
321
- if (this.isInitialized()) {
322
- throw new Error(`Skill ${params.interactiveSkill.name} is already initialized`);
323
- }
324
- this._toolDefinitions = Object.values(params.interactiveSkill.tools).map((tool2) => ({
325
- skillName: params.interactiveSkill.name,
326
- name: tool2.name,
327
- description: tool2.description,
328
- inputSchema: JSON.parse(tool2.inputJsonSchema),
329
- interactive: true
330
- }));
331
- }
332
- async _initDelegate(params) {
333
- if (this.isInitialized()) {
334
- throw new Error(`Skill ${params.expert.name} is already initialized`);
335
- }
336
- this._toolDefinitions = [
337
- {
338
- skillName: params.expert.name,
339
- name: params.expert.name.split("/").pop() ?? params.expert.name,
340
- description: params.expert.description,
341
- inputSchema: {
342
- type: "object",
343
- properties: { query: { type: "string" } },
344
- required: ["query"]
345
- },
346
- interactive: false
347
- }
348
- ];
349
- }
350
502
  async close() {
351
503
  if (this._mcpClient) {
352
504
  await this._mcpClient.close();
@@ -358,42 +510,23 @@ var SkillManager = class {
358
510
  }
359
511
  }
360
512
  }
361
- async getToolDefinitions() {
362
- if (!this.isInitialized() && !this.lazyInit) {
363
- throw new Error(`Skill ${this.name} is not initialized`);
364
- }
365
- if (!this.isInitialized() && this.lazyInit) {
366
- return [];
367
- }
368
- if (this._params.type === "mcp") {
369
- const omit = this._params.skill.omit ?? [];
370
- const pick = this._params.skill.pick ?? [];
371
- return this._toolDefinitions.filter((tool2) => omit.length > 0 ? !omit.includes(tool2.name) : true).filter((tool2) => pick.length > 0 ? pick.includes(tool2.name) : true);
372
- }
373
- return this._toolDefinitions;
513
+ _filterTools(tools) {
514
+ const omit = this.skill.omit ?? [];
515
+ const pick = this.skill.pick ?? [];
516
+ return tools.filter((tool2) => omit.length > 0 ? !omit.includes(tool2.name) : true).filter((tool2) => pick.length > 0 ? pick.includes(tool2.name) : true);
374
517
  }
375
518
  async callTool(toolName, input) {
376
- switch (this._params.type) {
377
- case "mcp": {
378
- if (!this.isInitialized() || !this._mcpClient) {
379
- throw new Error(`${this.name} is not initialized`);
380
- }
381
- try {
382
- const result = await this._mcpClient.callTool({
383
- name: toolName,
384
- arguments: input
385
- });
386
- return this._convertToolResult(result, toolName, input);
387
- } catch (error) {
388
- return this._handleToolError(error, toolName);
389
- }
390
- }
391
- case "interactive": {
392
- return [];
393
- }
394
- case "delegate": {
395
- return [];
396
- }
519
+ if (!this.isInitialized() || !this._mcpClient) {
520
+ throw new Error(`${this.name} is not initialized`);
521
+ }
522
+ try {
523
+ const result = await this._mcpClient.callTool({
524
+ name: toolName,
525
+ arguments: input
526
+ });
527
+ return this._convertToolResult(result, toolName, input);
528
+ } catch (error) {
529
+ return this._handleToolError(error, toolName);
397
530
  }
398
531
  }
399
532
  _handleToolError(error, toolName) {
@@ -462,79 +595,68 @@ var SkillManager = class {
462
595
  throw new Error(`Unsupported resource type: ${JSON.stringify(resource)}`);
463
596
  }
464
597
  };
598
+
599
+ // src/skill-manager/helpers.ts
600
+ async function initSkillManagersWithCleanup(managers, allManagers) {
601
+ const results = await Promise.allSettled(managers.map((m) => m.init()));
602
+ const firstRejected = results.find((r) => r.status === "rejected");
603
+ if (firstRejected) {
604
+ await Promise.all(allManagers.map((m) => m.close().catch(() => {
605
+ })));
606
+ throw firstRejected.reason;
607
+ }
608
+ }
465
609
  async function getSkillManagers(expert, experts, setting, eventListener) {
466
610
  const { perstackBaseSkillCommand, env, runId } = setting;
467
- const skillManagers = {};
468
611
  const { skills } = expert;
469
612
  if (!skills["@perstack/base"]) {
470
613
  throw new Error("Base skill is not defined");
471
614
  }
472
- const mcpSkillManagers = await Promise.all(
473
- Object.values(skills).filter((skill) => skill.type === "mcpStdioSkill" || skill.type === "mcpSseSkill").map(async (skill) => {
474
- if (perstackBaseSkillCommand && skill.type === "mcpStdioSkill") {
475
- const matchesBaseByPackage = skill.command === "npx" && skill.packageName === "@perstack/base";
476
- const matchesBaseByArgs = skill.command === "npx" && Array.isArray(skill.args) && skill.args.includes("@perstack/base");
477
- if (matchesBaseByPackage || matchesBaseByArgs) {
478
- const [overrideCommand, ...overrideArgs] = perstackBaseSkillCommand;
479
- if (!overrideCommand) {
480
- throw new Error("perstackBaseSkillCommand must have at least one element");
481
- }
482
- skill.command = overrideCommand;
483
- skill.packageName = void 0;
484
- skill.args = overrideArgs;
485
- skill.lazyInit = false;
615
+ const allManagers = [];
616
+ const mcpSkills = Object.values(skills).filter(
617
+ (skill) => skill.type === "mcpStdioSkill" || skill.type === "mcpSseSkill"
618
+ );
619
+ for (const skill of mcpSkills) {
620
+ if (perstackBaseSkillCommand && skill.type === "mcpStdioSkill") {
621
+ const matchesBaseByPackage = skill.command === "npx" && skill.packageName === "@perstack/base";
622
+ const matchesBaseByArgs = skill.command === "npx" && Array.isArray(skill.args) && skill.args.includes("@perstack/base");
623
+ if (matchesBaseByPackage || matchesBaseByArgs) {
624
+ const [overrideCommand, ...overrideArgs] = perstackBaseSkillCommand;
625
+ if (!overrideCommand) {
626
+ throw new Error("perstackBaseSkillCommand must have at least one element");
486
627
  }
628
+ skill.command = overrideCommand;
629
+ skill.packageName = void 0;
630
+ skill.args = overrideArgs;
631
+ skill.lazyInit = false;
487
632
  }
488
- const skillManager = new SkillManager(
489
- {
490
- type: "mcp",
491
- skill,
492
- env
493
- },
494
- runId,
495
- eventListener
496
- );
497
- await skillManager.init();
498
- return skillManager;
499
- })
500
- );
501
- const interactiveSkillManagers = await Promise.all(
502
- Object.values(skills).filter((skill) => skill.type === "interactiveSkill").map(async (interactiveSkill) => {
503
- const skillManager = new SkillManager(
504
- {
505
- type: "interactive",
506
- interactiveSkill
507
- },
508
- runId,
509
- eventListener
510
- );
511
- await skillManager.init();
512
- return skillManager;
513
- })
514
- );
515
- const delegateSkillManagers = await Promise.all(
516
- expert.delegates.map(async (delegateExpertName) => {
517
- const delegate = experts[delegateExpertName];
518
- const skillManager = new SkillManager(
519
- {
520
- type: "delegate",
521
- expert: delegate
522
- },
523
- runId,
524
- eventListener
525
- );
526
- await skillManager.init();
527
- return skillManager;
528
- })
529
- );
530
- for (const skillManager of mcpSkillManagers) {
531
- skillManagers[skillManager.name] = skillManager;
532
- }
533
- for (const skillManager of interactiveSkillManagers) {
534
- skillManagers[skillManager.name] = skillManager;
633
+ }
535
634
  }
536
- for (const skillManager of delegateSkillManagers) {
537
- skillManagers[skillManager.name] = skillManager;
635
+ const mcpSkillManagers = mcpSkills.map((skill) => {
636
+ const manager = new McpSkillManager(skill, env, runId, eventListener);
637
+ allManagers.push(manager);
638
+ return manager;
639
+ });
640
+ await initSkillManagersWithCleanup(mcpSkillManagers, allManagers);
641
+ const interactiveSkills = Object.values(skills).filter(
642
+ (skill) => skill.type === "interactiveSkill"
643
+ );
644
+ const interactiveSkillManagers = interactiveSkills.map((interactiveSkill) => {
645
+ const manager = new InteractiveSkillManager(interactiveSkill, runId, eventListener);
646
+ allManagers.push(manager);
647
+ return manager;
648
+ });
649
+ await initSkillManagersWithCleanup(interactiveSkillManagers, allManagers);
650
+ const delegateSkillManagers = expert.delegates.map((delegateExpertName) => {
651
+ const delegate = experts[delegateExpertName];
652
+ const manager = new DelegateSkillManager(delegate, runId, eventListener);
653
+ allManagers.push(manager);
654
+ return manager;
655
+ });
656
+ await initSkillManagersWithCleanup(delegateSkillManagers, allManagers);
657
+ const skillManagers = {};
658
+ for (const manager of allManagers) {
659
+ skillManagers[manager.name] = manager;
538
660
  }
539
661
  return skillManagers;
540
662
  }
@@ -603,7 +725,7 @@ async function callingDelegateLogic({
603
725
  },
604
726
  step: {
605
727
  ...step,
606
- finishedAt: (/* @__PURE__ */ new Date()).getTime()
728
+ finishedAt: Date.now()
607
729
  }
608
730
  });
609
731
  }
@@ -860,54 +982,23 @@ function toolCallPartToCoreToolCallPart(part) {
860
982
  };
861
983
  }
862
984
  function toolResultPartToCoreToolResultPart(part) {
863
- const content = part.contents[0];
864
- const output = content.type === "textPart" ? {
865
- type: "text",
866
- value: content.text
867
- } : {
868
- type: "content",
869
- value: [
870
- {
871
- type: "media",
872
- data: content.encodedData,
873
- mediaType: content.mimeType
874
- }
875
- ]
876
- };
985
+ const { contents } = part;
986
+ if (contents.length === 1 && contents[0].type === "textPart") {
987
+ return {
988
+ type: "tool-result",
989
+ toolCallId: part.toolCallId,
990
+ toolName: part.toolName,
991
+ output: { type: "text", value: contents[0].text }
992
+ };
993
+ }
994
+ const contentValue = contents.map(
995
+ (content) => content.type === "textPart" ? { type: "text", text: content.text } : { type: "media", data: content.encodedData, mediaType: content.mimeType }
996
+ );
877
997
  return {
878
998
  type: "tool-result",
879
999
  toolCallId: part.toolCallId,
880
1000
  toolName: part.toolName,
881
- output
882
- };
883
- }
884
-
885
- // src/usage.ts
886
- function createEmptyUsage() {
887
- return {
888
- inputTokens: 0,
889
- outputTokens: 0,
890
- reasoningTokens: 0,
891
- totalTokens: 0,
892
- cachedInputTokens: 0
893
- };
894
- }
895
- function usageFromGenerateTextResult(result) {
896
- return {
897
- inputTokens: result.usage.inputTokens || 0,
898
- outputTokens: result.usage.outputTokens || 0,
899
- reasoningTokens: result.usage.reasoningTokens || 0,
900
- totalTokens: result.usage.totalTokens || 0,
901
- cachedInputTokens: result.usage.cachedInputTokens || 0
902
- };
903
- }
904
- function sumUsage(a, b) {
905
- return {
906
- inputTokens: a.inputTokens + b.inputTokens,
907
- outputTokens: a.outputTokens + b.outputTokens,
908
- reasoningTokens: a.reasoningTokens + b.reasoningTokens,
909
- totalTokens: a.totalTokens + b.totalTokens,
910
- cachedInputTokens: a.cachedInputTokens + b.cachedInputTokens
1001
+ output: { type: "content", value: contentValue }
911
1002
  };
912
1003
  }
913
1004
 
@@ -995,7 +1086,6 @@ async function generatingToolCallLogic({
995
1086
  });
996
1087
  } catch (error) {
997
1088
  if (error instanceof Error) {
998
- console.error(error);
999
1089
  const reason = JSON.stringify({ error: error.name, message: error.message });
1000
1090
  return retry(setting, checkpoint, {
1001
1091
  reason,
@@ -1729,6 +1819,114 @@ var StateMachineLogics = {
1729
1819
  FinishingStep: finishingStepLogic
1730
1820
  };
1731
1821
 
1822
+ // src/execute-state-machine.ts
1823
+ async function executeStateMachine(params) {
1824
+ const {
1825
+ setting,
1826
+ initialCheckpoint,
1827
+ eventListener,
1828
+ skillManagers,
1829
+ eventEmitter,
1830
+ storeCheckpoint,
1831
+ shouldContinueRun
1832
+ } = params;
1833
+ const runActor = createActor(runtimeStateMachine, {
1834
+ input: {
1835
+ setting,
1836
+ initialCheckpoint,
1837
+ eventListener,
1838
+ skillManagers
1839
+ }
1840
+ });
1841
+ return new Promise((resolve, reject) => {
1842
+ runActor.subscribe(async (runState) => {
1843
+ try {
1844
+ if (runState.value === "Stopped") {
1845
+ const { checkpoint, skillManagers: skillManagers2 } = runState.context;
1846
+ if (!checkpoint) {
1847
+ throw new Error("Checkpoint is undefined");
1848
+ }
1849
+ await closeSkillManagers(skillManagers2);
1850
+ resolve(checkpoint);
1851
+ } else {
1852
+ const event = await StateMachineLogics[runState.value](runState.context);
1853
+ if ("checkpoint" in event) {
1854
+ await storeCheckpoint(event.checkpoint, event.timestamp);
1855
+ }
1856
+ await eventEmitter.emit(event);
1857
+ if (shouldContinueRun) {
1858
+ const shouldContinue = await shouldContinueRun(
1859
+ runState.context.setting,
1860
+ runState.context.checkpoint,
1861
+ runState.context.step
1862
+ );
1863
+ if (!shouldContinue) {
1864
+ runActor.stop();
1865
+ await closeSkillManagers(runState.context.skillManagers);
1866
+ resolve(runState.context.checkpoint);
1867
+ return;
1868
+ }
1869
+ }
1870
+ runActor.send(event);
1871
+ }
1872
+ } catch (error) {
1873
+ await closeSkillManagers(skillManagers).catch(() => {
1874
+ });
1875
+ reject(error);
1876
+ }
1877
+ });
1878
+ runActor.start();
1879
+ });
1880
+ }
1881
+ async function resolveExpertToRun(expertKey, experts, clientOptions) {
1882
+ if (experts[expertKey]) {
1883
+ return experts[expertKey];
1884
+ }
1885
+ const client = new ApiV1Client({
1886
+ baseUrl: clientOptions.perstackApiBaseUrl,
1887
+ apiKey: clientOptions.perstackApiKey
1888
+ });
1889
+ const { expert } = await client.registry.experts.get({ expertKey });
1890
+ experts[expertKey] = toRuntimeExpert(expert);
1891
+ return experts[expertKey];
1892
+ }
1893
+ function toRuntimeExpert(expert) {
1894
+ const skills = Object.fromEntries(
1895
+ Object.entries(expert.skills).map(([name, skill]) => {
1896
+ switch (skill.type) {
1897
+ case "mcpStdioSkill":
1898
+ return [name, { ...skill, name }];
1899
+ case "mcpSseSkill":
1900
+ return [name, { ...skill, name }];
1901
+ case "interactiveSkill":
1902
+ return [name, { ...skill, name }];
1903
+ default: {
1904
+ throw new Error(`Unknown skill type: ${skill.type}`);
1905
+ }
1906
+ }
1907
+ })
1908
+ );
1909
+ return { ...expert, skills };
1910
+ }
1911
+
1912
+ // src/setup-experts.ts
1913
+ async function setupExperts(setting, resolveExpertToRun2 = resolveExpertToRun) {
1914
+ const { expertKey } = setting;
1915
+ const experts = { ...setting.experts };
1916
+ const clientOptions = {
1917
+ perstackApiBaseUrl: setting.perstackApiBaseUrl,
1918
+ perstackApiKey: setting.perstackApiKey
1919
+ };
1920
+ const expertToRun = await resolveExpertToRun2(expertKey, experts, clientOptions);
1921
+ for (const delegateName of expertToRun.delegates) {
1922
+ const delegate = await resolveExpertToRun2(delegateName, experts, clientOptions);
1923
+ if (!delegate) {
1924
+ throw new Error(`Delegate ${delegateName} not found`);
1925
+ }
1926
+ }
1927
+ return { expertToRun, experts };
1928
+ }
1929
+
1732
1930
  // src/runtime.ts
1733
1931
  async function run(runInput, options) {
1734
1932
  const runParams = runParamsSchema.parse(runInput);
@@ -1739,15 +1937,10 @@ async function run(runInput, options) {
1739
1937
  eventEmitter.subscribe(eventListener);
1740
1938
  let { setting, checkpoint } = runParams;
1741
1939
  const contextWindow = getContextWindow(setting.providerConfig.providerName, setting.model);
1742
- if (setting.workspace) {
1743
- if (!path.isAbsolute(setting.workspace)) {
1744
- throw new Error(`Workspace path must be absolute: ${setting.workspace}`);
1745
- }
1746
- process.chdir(setting.workspace);
1747
- }
1748
- await storeRunSetting(setting);
1940
+ const getRunDir = options?.getRunDir ?? defaultGetRunDir;
1941
+ await storeRunSetting(setting, options?.fileSystem, getRunDir);
1749
1942
  while (true) {
1750
- const { expertToRun, experts } = await setupExperts(setting);
1943
+ const { expertToRun, experts } = await setupExperts(setting, options?.resolveExpertToRun);
1751
1944
  if (options?.eventListener) {
1752
1945
  const initEvent = createRuntimeEvent("initializeRuntime", setting.runId, {
1753
1946
  runtimeVersion: package_default.version,
@@ -1769,102 +1962,31 @@ async function run(runInput, options) {
1769
1962
  setting,
1770
1963
  options?.eventListener
1771
1964
  );
1772
- const runActor = createActor(runtimeStateMachine, {
1773
- input: {
1774
- setting: {
1775
- ...setting,
1776
- experts
1777
- },
1778
- initialCheckpoint: checkpoint ? {
1779
- ...checkpoint,
1780
- id: createId(),
1781
- stepNumber: checkpoint.stepNumber + 1
1782
- } : {
1783
- id: createId(),
1784
- runId: setting.runId,
1785
- expert: {
1786
- key: setting.expertKey,
1787
- name: expertToRun.name,
1788
- version: expertToRun.version
1789
- },
1790
- stepNumber: 1,
1791
- status: "init",
1792
- messages: [],
1793
- usage: createEmptyUsage(),
1794
- contextWindow,
1795
- contextWindowUsage: contextWindow ? 0 : void 0
1796
- },
1797
- eventListener,
1798
- skillManagers
1799
- }
1965
+ const initialCheckpoint = checkpoint ? createNextStepCheckpoint(createId(), checkpoint) : createInitialCheckpoint(createId(), {
1966
+ runId: setting.runId,
1967
+ expertKey: setting.expertKey,
1968
+ expert: expertToRun,
1969
+ contextWindow
1800
1970
  });
1801
- const runResultCheckpoint = await new Promise((resolve, reject) => {
1802
- runActor.subscribe(async (runState) => {
1803
- try {
1804
- if (runState.value === "Stopped") {
1805
- const { checkpoint: checkpoint2, skillManagers: skillManagers2 } = runState.context;
1806
- if (!checkpoint2) {
1807
- throw new Error("Checkpoint is undefined");
1808
- }
1809
- await closeSkillManagers(skillManagers2);
1810
- resolve(checkpoint2);
1811
- } else {
1812
- const event = await StateMachineLogics[runState.value](runState.context);
1813
- if ("checkpoint" in event) {
1814
- await storeCheckpoint(event.checkpoint, event.timestamp);
1815
- }
1816
- await eventEmitter.emit(event);
1817
- if (options?.shouldContinueRun) {
1818
- const shouldContinue = await options.shouldContinueRun(
1819
- runState.context.setting,
1820
- runState.context.checkpoint,
1821
- runState.context.step
1822
- );
1823
- if (!shouldContinue) {
1824
- runActor.stop();
1825
- resolve(runState.context.checkpoint);
1826
- return;
1827
- }
1828
- }
1829
- runActor.send(event);
1830
- }
1831
- } catch (error) {
1832
- reject(error);
1833
- }
1834
- });
1835
- runActor.start();
1971
+ const runResultCheckpoint = await executeStateMachine({
1972
+ setting: { ...setting, experts },
1973
+ initialCheckpoint,
1974
+ eventListener,
1975
+ skillManagers,
1976
+ eventEmitter,
1977
+ storeCheckpoint,
1978
+ shouldContinueRun: options?.shouldContinueRun
1836
1979
  });
1837
1980
  switch (runResultCheckpoint.status) {
1838
1981
  case "completed": {
1839
1982
  if (runResultCheckpoint.delegatedBy) {
1840
- const { messages, delegatedBy } = runResultCheckpoint;
1841
- const { expert, toolCallId, toolName, checkpointId } = delegatedBy;
1842
- const delegateResultMessage = messages[messages.length - 1];
1843
- if (delegateResultMessage.type !== "expertMessage") {
1844
- throw new Error("Delegation error: delegation result message is incorrect");
1845
- }
1846
- const delegateText = delegateResultMessage.contents.find(
1847
- (content) => content.type === "textPart"
1983
+ const parentCheckpoint = await retrieveCheckpoint(
1984
+ setting.runId,
1985
+ runResultCheckpoint.delegatedBy.checkpointId
1848
1986
  );
1849
- if (!delegateText) {
1850
- throw new Error("Delegation error: delegation result message does not contain a text");
1851
- }
1852
- setting = {
1853
- ...setting,
1854
- expertKey: expert.key,
1855
- input: {
1856
- interactiveToolCallResult: {
1857
- toolCallId,
1858
- toolName,
1859
- text: delegateText.text
1860
- }
1861
- }
1862
- };
1863
- checkpoint = {
1864
- ...await retrieveCheckpoint(setting.runId, checkpointId),
1865
- stepNumber: runResultCheckpoint.stepNumber,
1866
- usage: runResultCheckpoint.usage
1867
- };
1987
+ const result = buildDelegationReturnState(setting, runResultCheckpoint, parentCheckpoint);
1988
+ setting = result.setting;
1989
+ checkpoint = result.checkpoint;
1868
1990
  break;
1869
1991
  }
1870
1992
  return runResultCheckpoint;
@@ -1873,38 +1995,9 @@ async function run(runInput, options) {
1873
1995
  return runResultCheckpoint;
1874
1996
  }
1875
1997
  case "stoppedByDelegate": {
1876
- if (!runResultCheckpoint.delegateTo) {
1877
- throw new Error("Delegation error: delegate to is undefined");
1878
- }
1879
- const { expert, toolCallId, toolName, query } = runResultCheckpoint.delegateTo;
1880
- setting = {
1881
- ...setting,
1882
- expertKey: expert.key,
1883
- input: {
1884
- text: query
1885
- }
1886
- };
1887
- checkpoint = {
1888
- ...runResultCheckpoint,
1889
- status: "init",
1890
- messages: [],
1891
- expert: {
1892
- key: expert.key,
1893
- name: expert.name,
1894
- version: expert.version
1895
- },
1896
- delegatedBy: {
1897
- expert: {
1898
- key: expertToRun.key,
1899
- name: expertToRun.name,
1900
- version: expertToRun.version
1901
- },
1902
- toolCallId,
1903
- toolName,
1904
- checkpointId: runResultCheckpoint.id
1905
- },
1906
- usage: runResultCheckpoint.usage
1907
- };
1998
+ const result = buildDelegateToState(setting, runResultCheckpoint, expertToRun);
1999
+ setting = result.setting;
2000
+ checkpoint = result.checkpoint;
1908
2001
  break;
1909
2002
  }
1910
2003
  case "stoppedByExceededMaxSteps": {
@@ -1918,22 +2011,6 @@ async function run(runInput, options) {
1918
2011
  }
1919
2012
  }
1920
2013
  }
1921
- async function setupExperts(setting) {
1922
- const { expertKey } = setting;
1923
- const experts = { ...setting.experts };
1924
- const clientOptions = {
1925
- perstackApiBaseUrl: setting.perstackApiBaseUrl,
1926
- perstackApiKey: setting.perstackApiKey
1927
- };
1928
- const expertToRun = await resolveExpertToRun(expertKey, experts, clientOptions);
1929
- for (const delegateName of expertToRun.delegates) {
1930
- const delegate = await resolveExpertToRun(delegateName, experts, clientOptions);
1931
- if (!delegate) {
1932
- throw new Error(`Delegate ${delegateName} not found`);
1933
- }
1934
- }
1935
- return { expertToRun, experts };
1936
- }
1937
2014
  function getEventListener(options) {
1938
2015
  const listener = options?.eventListener ?? ((e) => console.log(JSON.stringify(e)));
1939
2016
  return async (event) => {
@@ -1943,25 +2020,10 @@ function getEventListener(options) {
1943
2020
  listener(event);
1944
2021
  };
1945
2022
  }
1946
- async function storeRunSetting(setting) {
1947
- const runDir = getRunDir(setting.runId);
1948
- if (existsSync(runDir)) {
1949
- const runSettingPath = path.resolve(runDir, "run-setting.json");
1950
- const runSetting = JSON.parse(await readFile(runSettingPath, "utf-8"));
1951
- runSetting.updatedAt = Date.now();
1952
- await writeFile(runSettingPath, JSON.stringify(runSetting), "utf-8");
1953
- } else {
1954
- await mkdir(runDir, { recursive: true });
1955
- await writeFile(path.resolve(runDir, "run-setting.json"), JSON.stringify(setting), "utf-8");
1956
- }
1957
- }
1958
- function getRunDir(runId) {
1959
- return `${process.cwd()}/perstack/runs/${runId}`;
1960
- }
1961
2023
 
1962
2024
  // src/index.ts
1963
2025
  var runtimeVersion = package_default.version;
1964
2026
 
1965
- export { StateMachineLogics, calculateContextWindowUsage, getContextWindow, getModel, getRunDir, run, runtimeStateMachine, runtimeVersion };
2027
+ export { StateMachineLogics, calculateContextWindowUsage, getContextWindow, getModel, defaultGetRunDir as getRunDir, run, runtimeStateMachine, runtimeVersion };
1966
2028
  //# sourceMappingURL=index.js.map
1967
2029
  //# sourceMappingURL=index.js.map