@perstack/runtime 0.0.48 → 0.0.49

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
10
  import path from 'path';
12
11
  import { createId } from '@paralleldrive/cuid2';
12
+ import { readFile, readdir, mkdir, writeFile } from 'fs/promises';
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.49"};
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,
@@ -1267,8 +1357,8 @@ async function resolvingImageFileLogic({
1267
1357
  });
1268
1358
  continue;
1269
1359
  }
1270
- const { path: path2, mimeType, size } = imageInfo;
1271
- const file = await readFile(path2).then((buffer) => ({
1360
+ const { path: path3, mimeType, size } = imageInfo;
1361
+ const file = await readFile(path3).then((buffer) => ({
1272
1362
  encodedData: buffer.toString("base64"),
1273
1363
  mimeType,
1274
1364
  size
@@ -1315,8 +1405,8 @@ async function resolvingPdfFileLogic({
1315
1405
  });
1316
1406
  continue;
1317
1407
  }
1318
- const { path: path2, mimeType, size } = pdfInfo;
1319
- const file = await readFile(path2).then((buffer) => ({
1408
+ const { path: path3, mimeType, size } = pdfInfo;
1409
+ const file = await readFile(path3).then((buffer) => ({
1320
1410
  encodedData: buffer.toString("base64"),
1321
1411
  mimeType,
1322
1412
  size
@@ -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);
@@ -1745,9 +1943,10 @@ async function run(runInput, options) {
1745
1943
  }
1746
1944
  process.chdir(setting.workspace);
1747
1945
  }
1748
- await storeRunSetting(setting);
1946
+ const getRunDir = options?.getRunDir ?? defaultGetRunDir;
1947
+ await storeRunSetting(setting, options?.fileSystem, getRunDir);
1749
1948
  while (true) {
1750
- const { expertToRun, experts } = await setupExperts(setting);
1949
+ const { expertToRun, experts } = await setupExperts(setting, options?.resolveExpertToRun);
1751
1950
  if (options?.eventListener) {
1752
1951
  const initEvent = createRuntimeEvent("initializeRuntime", setting.runId, {
1753
1952
  runtimeVersion: package_default.version,
@@ -1769,102 +1968,31 @@ async function run(runInput, options) {
1769
1968
  setting,
1770
1969
  options?.eventListener
1771
1970
  );
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
- }
1971
+ const initialCheckpoint = checkpoint ? createNextStepCheckpoint(createId(), checkpoint) : createInitialCheckpoint(createId(), {
1972
+ runId: setting.runId,
1973
+ expertKey: setting.expertKey,
1974
+ expert: expertToRun,
1975
+ contextWindow
1800
1976
  });
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();
1977
+ const runResultCheckpoint = await executeStateMachine({
1978
+ setting: { ...setting, experts },
1979
+ initialCheckpoint,
1980
+ eventListener,
1981
+ skillManagers,
1982
+ eventEmitter,
1983
+ storeCheckpoint,
1984
+ shouldContinueRun: options?.shouldContinueRun
1836
1985
  });
1837
1986
  switch (runResultCheckpoint.status) {
1838
1987
  case "completed": {
1839
1988
  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"
1989
+ const parentCheckpoint = await retrieveCheckpoint(
1990
+ setting.runId,
1991
+ runResultCheckpoint.delegatedBy.checkpointId
1848
1992
  );
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
- };
1993
+ const result = buildDelegationReturnState(setting, runResultCheckpoint, parentCheckpoint);
1994
+ setting = result.setting;
1995
+ checkpoint = result.checkpoint;
1868
1996
  break;
1869
1997
  }
1870
1998
  return runResultCheckpoint;
@@ -1873,38 +2001,9 @@ async function run(runInput, options) {
1873
2001
  return runResultCheckpoint;
1874
2002
  }
1875
2003
  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
- };
2004
+ const result = buildDelegateToState(setting, runResultCheckpoint, expertToRun);
2005
+ setting = result.setting;
2006
+ checkpoint = result.checkpoint;
1908
2007
  break;
1909
2008
  }
1910
2009
  case "stoppedByExceededMaxSteps": {
@@ -1918,22 +2017,6 @@ async function run(runInput, options) {
1918
2017
  }
1919
2018
  }
1920
2019
  }
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
2020
  function getEventListener(options) {
1938
2021
  const listener = options?.eventListener ?? ((e) => console.log(JSON.stringify(e)));
1939
2022
  return async (event) => {
@@ -1943,25 +2026,10 @@ function getEventListener(options) {
1943
2026
  listener(event);
1944
2027
  };
1945
2028
  }
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
2029
 
1962
2030
  // src/index.ts
1963
2031
  var runtimeVersion = package_default.version;
1964
2032
 
1965
- export { StateMachineLogics, calculateContextWindowUsage, getContextWindow, getModel, getRunDir, run, runtimeStateMachine, runtimeVersion };
2033
+ export { StateMachineLogics, calculateContextWindowUsage, getContextWindow, getModel, defaultGetRunDir as getRunDir, run, runtimeStateMachine, runtimeVersion };
1966
2034
  //# sourceMappingURL=index.js.map
1967
2035
  //# sourceMappingURL=index.js.map