@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/README.md +35 -0
- package/dist/src/index.d.ts +492 -3299
- package/dist/src/index.js +523 -455
- package/dist/src/index.js.map +1 -1
- package/package.json +4 -3
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 {
|
|
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.
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
253
|
-
if (this.isInitialized()) {
|
|
254
|
-
throw new Error(`Skill ${
|
|
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: `${
|
|
439
|
+
name: `${this.skill.name}-mcp-client`,
|
|
258
440
|
version: "1.0.0"
|
|
259
441
|
});
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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:
|
|
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
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
-
|
|
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
|
-
|
|
537
|
-
|
|
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:
|
|
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
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
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:
|
|
1271
|
-
const file = await readFile(
|
|
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:
|
|
1319
|
-
const file = await readFile(
|
|
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
|
-
|
|
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
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
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
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
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
|
|
1841
|
-
|
|
1842
|
-
|
|
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
|
-
|
|
1850
|
-
|
|
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
|
-
|
|
1877
|
-
|
|
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
|