@perstack/runtime 0.0.70 → 0.0.71
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 +0 -1
- package/dist/bin/cli.js +12 -5
- package/dist/bin/cli.js.map +1 -1
- package/dist/{chunk-3RWT2GPO.js → chunk-LDJKVMQK.js} +749 -747
- package/dist/chunk-LDJKVMQK.js.map +1 -0
- package/dist/chunk-RG4QHAGG.js +935 -0
- package/dist/chunk-RG4QHAGG.js.map +1 -0
- package/dist/lockfile-skill-manager-LG2E4KAZ.js +3 -0
- package/dist/lockfile-skill-manager-LG2E4KAZ.js.map +1 -0
- package/dist/src/index.d.ts +160 -16
- package/dist/src/index.js +8 -6
- package/dist/src/index.js.map +1 -1
- package/package.json +21 -11
- package/dist/chunk-3RWT2GPO.js.map +0 -1
|
@@ -0,0 +1,935 @@
|
|
|
1
|
+
import { tool, jsonSchema } from 'ai';
|
|
2
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
3
|
+
import { McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
import { BASE_SKILL_NAME, createBaseServer, BASE_SKILL_VERSION } from '@perstack/base';
|
|
5
|
+
import { createRuntimeEvent, getFilteredEnv } from '@perstack/core';
|
|
6
|
+
import { createId } from '@paralleldrive/cuid2';
|
|
7
|
+
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
8
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
9
|
+
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
|
|
10
|
+
|
|
11
|
+
// src/skill-manager/base.ts
|
|
12
|
+
var BaseSkillManager = class {
|
|
13
|
+
_toolDefinitions = [];
|
|
14
|
+
_initialized = false;
|
|
15
|
+
_initializing;
|
|
16
|
+
skill;
|
|
17
|
+
interactiveSkill;
|
|
18
|
+
expert;
|
|
19
|
+
_jobId;
|
|
20
|
+
_runId;
|
|
21
|
+
_eventListener;
|
|
22
|
+
constructor(jobId, runId, eventListener) {
|
|
23
|
+
this._jobId = jobId;
|
|
24
|
+
this._runId = runId;
|
|
25
|
+
this._eventListener = eventListener;
|
|
26
|
+
}
|
|
27
|
+
async init() {
|
|
28
|
+
if (this._initialized) {
|
|
29
|
+
throw new Error(`Skill ${this.name} is already initialized`);
|
|
30
|
+
}
|
|
31
|
+
if (this._initializing) {
|
|
32
|
+
throw new Error(`Skill ${this.name} is already initializing`);
|
|
33
|
+
}
|
|
34
|
+
const initPromise = this._performInit();
|
|
35
|
+
this._initializing = initPromise;
|
|
36
|
+
if (!this.lazyInit) {
|
|
37
|
+
try {
|
|
38
|
+
await initPromise;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
this._initialized = false;
|
|
41
|
+
this._initializing = void 0;
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
isInitialized() {
|
|
47
|
+
return this._initialized;
|
|
48
|
+
}
|
|
49
|
+
async _performInit() {
|
|
50
|
+
await this._doInit();
|
|
51
|
+
this._initialized = true;
|
|
52
|
+
this._initializing = void 0;
|
|
53
|
+
}
|
|
54
|
+
async getToolDefinitions() {
|
|
55
|
+
if (!this.isInitialized() && this._initializing) {
|
|
56
|
+
await this._initializing;
|
|
57
|
+
}
|
|
58
|
+
if (!this.isInitialized()) {
|
|
59
|
+
throw new Error(`Skill ${this.name} is not initialized`);
|
|
60
|
+
}
|
|
61
|
+
return this._filterTools(this._toolDefinitions);
|
|
62
|
+
}
|
|
63
|
+
_filterTools(tools) {
|
|
64
|
+
return tools;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// src/skill-manager/delegate.ts
|
|
69
|
+
var DelegateSkillManager = class extends BaseSkillManager {
|
|
70
|
+
name;
|
|
71
|
+
type = "delegate";
|
|
72
|
+
lazyInit = false;
|
|
73
|
+
expert;
|
|
74
|
+
constructor(expert, jobId, runId, eventListener) {
|
|
75
|
+
super(jobId, runId, eventListener);
|
|
76
|
+
this.name = expert.name;
|
|
77
|
+
this.expert = expert;
|
|
78
|
+
}
|
|
79
|
+
async _doInit() {
|
|
80
|
+
this._toolDefinitions = [
|
|
81
|
+
{
|
|
82
|
+
skillName: this.expert.name,
|
|
83
|
+
name: this.expert.name.split("/").pop() ?? this.expert.name,
|
|
84
|
+
description: this.expert.description,
|
|
85
|
+
inputSchema: {
|
|
86
|
+
type: "object",
|
|
87
|
+
properties: {
|
|
88
|
+
query: { type: "string" }
|
|
89
|
+
},
|
|
90
|
+
required: ["query"]
|
|
91
|
+
},
|
|
92
|
+
interactive: false
|
|
93
|
+
}
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
async close() {
|
|
97
|
+
}
|
|
98
|
+
async callTool(_toolName, _input) {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
function handleToolError(error, toolName, McpErrorClass) {
|
|
103
|
+
if (error instanceof McpErrorClass) {
|
|
104
|
+
return [
|
|
105
|
+
{
|
|
106
|
+
type: "textPart",
|
|
107
|
+
text: `Error calling tool ${toolName}: ${error.message}`,
|
|
108
|
+
id: createId()
|
|
109
|
+
}
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
function convertToolResult(result, toolName, input) {
|
|
115
|
+
if (!result.content || result.content.length === 0) {
|
|
116
|
+
return [
|
|
117
|
+
{
|
|
118
|
+
type: "textPart",
|
|
119
|
+
text: `Tool ${toolName} returned nothing with arguments: ${JSON.stringify(input)}`,
|
|
120
|
+
id: createId()
|
|
121
|
+
}
|
|
122
|
+
];
|
|
123
|
+
}
|
|
124
|
+
return result.content.filter((part) => part.type !== "audio" && part.type !== "resource_link").map((part) => convertPart(part));
|
|
125
|
+
}
|
|
126
|
+
function convertPart(part) {
|
|
127
|
+
switch (part.type) {
|
|
128
|
+
case "text":
|
|
129
|
+
if (!part.text || part.text === "") {
|
|
130
|
+
return { type: "textPart", text: "Error: No content", id: createId() };
|
|
131
|
+
}
|
|
132
|
+
return { type: "textPart", text: part.text, id: createId() };
|
|
133
|
+
case "image":
|
|
134
|
+
if (!part.data || !part.mimeType) {
|
|
135
|
+
throw new Error("Image part must have both data and mimeType");
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
type: "imageInlinePart",
|
|
139
|
+
encodedData: part.data,
|
|
140
|
+
mimeType: part.mimeType,
|
|
141
|
+
id: createId()
|
|
142
|
+
};
|
|
143
|
+
case "resource":
|
|
144
|
+
if (!part.resource) {
|
|
145
|
+
throw new Error("Resource part must have resource content");
|
|
146
|
+
}
|
|
147
|
+
return convertResource(part.resource);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function convertResource(resource) {
|
|
151
|
+
if (!resource.mimeType) {
|
|
152
|
+
throw new Error(`Resource ${JSON.stringify(resource)} has no mimeType`);
|
|
153
|
+
}
|
|
154
|
+
if (resource.text && typeof resource.text === "string") {
|
|
155
|
+
return { type: "textPart", text: resource.text, id: createId() };
|
|
156
|
+
}
|
|
157
|
+
if (resource.blob && typeof resource.blob === "string") {
|
|
158
|
+
return {
|
|
159
|
+
type: "fileInlinePart",
|
|
160
|
+
encodedData: resource.blob,
|
|
161
|
+
mimeType: resource.mimeType,
|
|
162
|
+
id: createId()
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
throw new Error(`Unsupported resource type: ${JSON.stringify(resource)}`);
|
|
166
|
+
}
|
|
167
|
+
var DefaultTransportFactory = class {
|
|
168
|
+
createStdio(options) {
|
|
169
|
+
return new StdioClientTransport({
|
|
170
|
+
command: options.command,
|
|
171
|
+
args: options.args,
|
|
172
|
+
env: options.env,
|
|
173
|
+
stderr: options.stderr
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
createSse(options) {
|
|
177
|
+
return new SSEClientTransport(options.url);
|
|
178
|
+
}
|
|
179
|
+
createInMemoryPair() {
|
|
180
|
+
return InMemoryTransport.createLinkedPair();
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
var defaultTransportFactory = new DefaultTransportFactory();
|
|
184
|
+
|
|
185
|
+
// src/skill-manager/in-memory-base.ts
|
|
186
|
+
var InMemoryBaseSkillManager = class extends BaseSkillManager {
|
|
187
|
+
name = BASE_SKILL_NAME;
|
|
188
|
+
type = "mcp";
|
|
189
|
+
lazyInit = false;
|
|
190
|
+
skill;
|
|
191
|
+
_mcpServer;
|
|
192
|
+
_mcpClient;
|
|
193
|
+
_transportFactory;
|
|
194
|
+
constructor(skill, jobId, runId, eventListener, options) {
|
|
195
|
+
super(jobId, runId, eventListener);
|
|
196
|
+
this.skill = skill;
|
|
197
|
+
this._transportFactory = options?.transportFactory ?? defaultTransportFactory;
|
|
198
|
+
}
|
|
199
|
+
_filterTools(tools) {
|
|
200
|
+
const omit = this.skill.omit ?? [];
|
|
201
|
+
const pick = this.skill.pick ?? [];
|
|
202
|
+
return tools.filter((tool2) => omit.length > 0 ? !omit.includes(tool2.name) : true).filter((tool2) => pick.length > 0 ? pick.includes(tool2.name) : true);
|
|
203
|
+
}
|
|
204
|
+
async _doInit() {
|
|
205
|
+
const startTime = Date.now();
|
|
206
|
+
const [clientTransport, serverTransport] = this._transportFactory.createInMemoryPair();
|
|
207
|
+
this._mcpServer = createBaseServer();
|
|
208
|
+
await this._mcpServer.connect(serverTransport);
|
|
209
|
+
this._mcpClient = new Client({
|
|
210
|
+
name: `${BASE_SKILL_NAME}-in-memory-client`,
|
|
211
|
+
version: "1.0.0"
|
|
212
|
+
});
|
|
213
|
+
const handshakeStartTime = Date.now();
|
|
214
|
+
await this._mcpClient.connect(clientTransport);
|
|
215
|
+
const handshakeDurationMs = Date.now() - handshakeStartTime;
|
|
216
|
+
const toolDiscoveryStartTime = Date.now();
|
|
217
|
+
const { tools } = await this._mcpClient.listTools();
|
|
218
|
+
const toolDiscoveryDurationMs = Date.now() - toolDiscoveryStartTime;
|
|
219
|
+
this._toolDefinitions = tools.map((tool2) => ({
|
|
220
|
+
skillName: BASE_SKILL_NAME,
|
|
221
|
+
name: tool2.name,
|
|
222
|
+
description: tool2.description,
|
|
223
|
+
inputSchema: tool2.inputSchema,
|
|
224
|
+
interactive: false
|
|
225
|
+
}));
|
|
226
|
+
if (this._eventListener) {
|
|
227
|
+
const totalDurationMs = Date.now() - startTime;
|
|
228
|
+
const event = createRuntimeEvent("skillConnected", this._jobId, this._runId, {
|
|
229
|
+
skillName: BASE_SKILL_NAME,
|
|
230
|
+
serverInfo: { name: BASE_SKILL_NAME, version: BASE_SKILL_VERSION },
|
|
231
|
+
spawnDurationMs: 0,
|
|
232
|
+
// No process spawn for in-memory
|
|
233
|
+
handshakeDurationMs,
|
|
234
|
+
toolDiscoveryDurationMs,
|
|
235
|
+
connectDurationMs: handshakeDurationMs,
|
|
236
|
+
totalDurationMs
|
|
237
|
+
});
|
|
238
|
+
this._eventListener(event);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async close() {
|
|
242
|
+
if (this._mcpClient) {
|
|
243
|
+
await this._mcpClient.close();
|
|
244
|
+
}
|
|
245
|
+
if (this._mcpServer) {
|
|
246
|
+
await this._mcpServer.close();
|
|
247
|
+
}
|
|
248
|
+
if (this._eventListener && (this._mcpClient || this._mcpServer)) {
|
|
249
|
+
const event = createRuntimeEvent("skillDisconnected", this._jobId, this._runId, {
|
|
250
|
+
skillName: BASE_SKILL_NAME
|
|
251
|
+
});
|
|
252
|
+
this._eventListener(event);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async callTool(toolName, input) {
|
|
256
|
+
if (!this.isInitialized() || !this._mcpClient) {
|
|
257
|
+
throw new Error(`${this.name} is not initialized`);
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
const result = await this._mcpClient.callTool({
|
|
261
|
+
name: toolName,
|
|
262
|
+
arguments: input
|
|
263
|
+
});
|
|
264
|
+
return convertToolResult(result, toolName, input);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
return handleToolError(error, toolName, McpError);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// src/skill-manager/interactive.ts
|
|
272
|
+
var InteractiveSkillManager = class extends BaseSkillManager {
|
|
273
|
+
name;
|
|
274
|
+
type = "interactive";
|
|
275
|
+
lazyInit = false;
|
|
276
|
+
interactiveSkill;
|
|
277
|
+
constructor(interactiveSkill, jobId, runId, eventListener) {
|
|
278
|
+
super(jobId, runId, eventListener);
|
|
279
|
+
this.name = interactiveSkill.name;
|
|
280
|
+
this.interactiveSkill = interactiveSkill;
|
|
281
|
+
}
|
|
282
|
+
async _doInit() {
|
|
283
|
+
this._toolDefinitions = Object.values(this.interactiveSkill.tools).map((tool2) => ({
|
|
284
|
+
skillName: this.interactiveSkill.name,
|
|
285
|
+
name: tool2.name,
|
|
286
|
+
description: tool2.description,
|
|
287
|
+
inputSchema: JSON.parse(tool2.inputJsonSchema),
|
|
288
|
+
interactive: true
|
|
289
|
+
}));
|
|
290
|
+
}
|
|
291
|
+
async close() {
|
|
292
|
+
}
|
|
293
|
+
async callTool(_toolName, _input) {
|
|
294
|
+
return [];
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
// src/skill-manager/command-args.ts
|
|
299
|
+
function getCommandArgs(skill) {
|
|
300
|
+
const { name, command, packageName, args } = skill;
|
|
301
|
+
if (!packageName && (!args || args.length === 0)) {
|
|
302
|
+
throw new Error(`Skill ${name} has no packageName or args. Please provide one of them.`);
|
|
303
|
+
}
|
|
304
|
+
if (packageName && args && args.length > 0) {
|
|
305
|
+
throw new Error(`Skill ${name} has both packageName and args. Please provide only one of them.`);
|
|
306
|
+
}
|
|
307
|
+
let newArgs = args && args.length > 0 ? args : [packageName];
|
|
308
|
+
if (command === "npx" && !newArgs.includes("-y")) {
|
|
309
|
+
newArgs = ["-y", ...newArgs];
|
|
310
|
+
}
|
|
311
|
+
return { command, args: newArgs };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/skill-manager/ip-validator.ts
|
|
315
|
+
function isPrivateOrLocalIP(hostname) {
|
|
316
|
+
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "0.0.0.0") {
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
const ipv4Match = hostname.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
|
|
320
|
+
if (ipv4Match) {
|
|
321
|
+
const [, a, b] = ipv4Match.map(Number);
|
|
322
|
+
if (a === 10) return true;
|
|
323
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
324
|
+
if (a === 192 && b === 168) return true;
|
|
325
|
+
if (a === 169 && b === 254) return true;
|
|
326
|
+
if (a === 127) return true;
|
|
327
|
+
}
|
|
328
|
+
if (hostname.includes(":")) {
|
|
329
|
+
if (hostname.startsWith("fe80:")) return true;
|
|
330
|
+
if (hostname.startsWith("fc") || hostname.startsWith("fd")) return true;
|
|
331
|
+
}
|
|
332
|
+
if (hostname.startsWith("::ffff:")) {
|
|
333
|
+
const ipv4Part = hostname.slice(7);
|
|
334
|
+
if (isPrivateOrLocalIP(ipv4Part)) {
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/skill-manager/mcp.ts
|
|
342
|
+
var McpSkillManager = class extends BaseSkillManager {
|
|
343
|
+
name;
|
|
344
|
+
type = "mcp";
|
|
345
|
+
lazyInit;
|
|
346
|
+
skill;
|
|
347
|
+
_mcpClient;
|
|
348
|
+
_env;
|
|
349
|
+
_transportFactory;
|
|
350
|
+
constructor(skill, env, jobId, runId, eventListener, options) {
|
|
351
|
+
super(jobId, runId, eventListener);
|
|
352
|
+
this.name = skill.name;
|
|
353
|
+
this.skill = skill;
|
|
354
|
+
this._env = env;
|
|
355
|
+
this._transportFactory = options?.transportFactory ?? defaultTransportFactory;
|
|
356
|
+
this.lazyInit = skill.type === "mcpStdioSkill" && skill.lazyInit && skill.name !== "@perstack/base";
|
|
357
|
+
}
|
|
358
|
+
async _doInit() {
|
|
359
|
+
this._mcpClient = new Client({
|
|
360
|
+
name: `${this.skill.name}-mcp-client`,
|
|
361
|
+
version: "1.0.0"
|
|
362
|
+
});
|
|
363
|
+
let timingInfo;
|
|
364
|
+
if (this.skill.type === "mcpStdioSkill") {
|
|
365
|
+
timingInfo = await this._initStdio(this.skill);
|
|
366
|
+
} else {
|
|
367
|
+
await this._initSse(this.skill);
|
|
368
|
+
}
|
|
369
|
+
const toolDiscoveryStartTime = Date.now();
|
|
370
|
+
const { tools } = await this._mcpClient.listTools();
|
|
371
|
+
const toolDiscoveryDurationMs = Date.now() - toolDiscoveryStartTime;
|
|
372
|
+
this._toolDefinitions = tools.map((tool2) => ({
|
|
373
|
+
skillName: this.skill.name,
|
|
374
|
+
name: tool2.name,
|
|
375
|
+
description: tool2.description,
|
|
376
|
+
inputSchema: tool2.inputSchema,
|
|
377
|
+
interactive: false
|
|
378
|
+
}));
|
|
379
|
+
if (this._eventListener && timingInfo) {
|
|
380
|
+
const totalDurationMs = Date.now() - timingInfo.startTime;
|
|
381
|
+
const event = createRuntimeEvent("skillConnected", this._jobId, this._runId, {
|
|
382
|
+
skillName: this.skill.name,
|
|
383
|
+
serverInfo: timingInfo.serverInfo,
|
|
384
|
+
spawnDurationMs: timingInfo.spawnDurationMs,
|
|
385
|
+
handshakeDurationMs: timingInfo.handshakeDurationMs,
|
|
386
|
+
toolDiscoveryDurationMs,
|
|
387
|
+
connectDurationMs: timingInfo.spawnDurationMs + timingInfo.handshakeDurationMs,
|
|
388
|
+
totalDurationMs
|
|
389
|
+
});
|
|
390
|
+
this._eventListener(event);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
async _initStdio(skill) {
|
|
394
|
+
if (!skill.command) {
|
|
395
|
+
throw new Error(`Skill ${skill.name} has no command`);
|
|
396
|
+
}
|
|
397
|
+
const requiredEnv = {};
|
|
398
|
+
for (const envName of skill.requiredEnv) {
|
|
399
|
+
if (!this._env[envName]) {
|
|
400
|
+
throw new Error(`Skill ${skill.name} requires environment variable ${envName}`);
|
|
401
|
+
}
|
|
402
|
+
requiredEnv[envName] = this._env[envName];
|
|
403
|
+
}
|
|
404
|
+
const env = getFilteredEnv(requiredEnv);
|
|
405
|
+
const startTime = Date.now();
|
|
406
|
+
const { command, args } = getCommandArgs(skill);
|
|
407
|
+
if (this._eventListener) {
|
|
408
|
+
const event = createRuntimeEvent("skillStarting", this._jobId, this._runId, {
|
|
409
|
+
skillName: skill.name,
|
|
410
|
+
command,
|
|
411
|
+
args
|
|
412
|
+
});
|
|
413
|
+
this._eventListener(event);
|
|
414
|
+
}
|
|
415
|
+
const transport = this._transportFactory.createStdio({ command, args, env, stderr: "pipe" });
|
|
416
|
+
const spawnDurationMs = Date.now() - startTime;
|
|
417
|
+
if (transport.stderr) {
|
|
418
|
+
transport.stderr.on("data", (chunk) => {
|
|
419
|
+
if (this._eventListener) {
|
|
420
|
+
const event = createRuntimeEvent("skillStderr", this._jobId, this._runId, {
|
|
421
|
+
skillName: skill.name,
|
|
422
|
+
message: chunk.toString().trim()
|
|
423
|
+
});
|
|
424
|
+
this._eventListener(event);
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
const connectStartTime = Date.now();
|
|
429
|
+
await this._mcpClient.connect(transport);
|
|
430
|
+
const handshakeDurationMs = Date.now() - connectStartTime;
|
|
431
|
+
const serverVersion = this._mcpClient.getServerVersion();
|
|
432
|
+
return {
|
|
433
|
+
startTime,
|
|
434
|
+
spawnDurationMs,
|
|
435
|
+
handshakeDurationMs,
|
|
436
|
+
serverInfo: serverVersion ? { name: serverVersion.name, version: serverVersion.version } : void 0
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
async _initSse(skill) {
|
|
440
|
+
if (!skill.endpoint) {
|
|
441
|
+
throw new Error(`Skill ${skill.name} has no endpoint`);
|
|
442
|
+
}
|
|
443
|
+
const url = new URL(skill.endpoint);
|
|
444
|
+
if (url.protocol !== "https:") {
|
|
445
|
+
throw new Error(`Skill ${skill.name} SSE endpoint must use HTTPS: ${skill.endpoint}`);
|
|
446
|
+
}
|
|
447
|
+
if (isPrivateOrLocalIP(url.hostname)) {
|
|
448
|
+
throw new Error(
|
|
449
|
+
`Skill ${skill.name} SSE endpoint cannot use private/local IP: ${skill.endpoint}`
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
const transport = this._transportFactory.createSse({ url });
|
|
453
|
+
await this._mcpClient.connect(transport);
|
|
454
|
+
}
|
|
455
|
+
async close() {
|
|
456
|
+
if (this._mcpClient) {
|
|
457
|
+
await this._mcpClient.close();
|
|
458
|
+
if (this._eventListener && this.skill) {
|
|
459
|
+
const event = createRuntimeEvent("skillDisconnected", this._jobId, this._runId, {
|
|
460
|
+
skillName: this.skill.name
|
|
461
|
+
});
|
|
462
|
+
this._eventListener(event);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
_filterTools(tools) {
|
|
467
|
+
const omit = this.skill.omit ?? [];
|
|
468
|
+
const pick = this.skill.pick ?? [];
|
|
469
|
+
return tools.filter((tool2) => omit.length > 0 ? !omit.includes(tool2.name) : true).filter((tool2) => pick.length > 0 ? pick.includes(tool2.name) : true);
|
|
470
|
+
}
|
|
471
|
+
async callTool(toolName, input) {
|
|
472
|
+
if (!this.isInitialized() || !this._mcpClient) {
|
|
473
|
+
throw new Error(`${this.name} is not initialized`);
|
|
474
|
+
}
|
|
475
|
+
try {
|
|
476
|
+
const result = await this._mcpClient.callTool({
|
|
477
|
+
name: toolName,
|
|
478
|
+
arguments: input
|
|
479
|
+
});
|
|
480
|
+
return convertToolResult(result, toolName, input);
|
|
481
|
+
} catch (error) {
|
|
482
|
+
return handleToolError(error, toolName, McpError);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
// src/skill-manager/skill-manager-factory.ts
|
|
488
|
+
var DefaultSkillManagerFactory = class {
|
|
489
|
+
createMcp(skill, context) {
|
|
490
|
+
return new McpSkillManager(
|
|
491
|
+
skill,
|
|
492
|
+
context.env,
|
|
493
|
+
context.jobId,
|
|
494
|
+
context.runId,
|
|
495
|
+
context.eventListener,
|
|
496
|
+
context.mcpOptions
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
createInMemoryBase(skill, context) {
|
|
500
|
+
return new InMemoryBaseSkillManager(skill, context.jobId, context.runId, context.eventListener);
|
|
501
|
+
}
|
|
502
|
+
createInteractive(skill, context) {
|
|
503
|
+
return new InteractiveSkillManager(skill, context.jobId, context.runId, context.eventListener);
|
|
504
|
+
}
|
|
505
|
+
createDelegate(expert, context) {
|
|
506
|
+
return new DelegateSkillManager(expert, context.jobId, context.runId, context.eventListener);
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
var defaultSkillManagerFactory = new DefaultSkillManagerFactory();
|
|
510
|
+
|
|
511
|
+
// src/skill-manager/helpers.ts
|
|
512
|
+
async function initSkillManagersWithCleanup(managers, allManagers) {
|
|
513
|
+
const results = await Promise.allSettled(managers.map((m) => m.init()));
|
|
514
|
+
const firstRejected = results.find((r) => r.status === "rejected");
|
|
515
|
+
if (firstRejected) {
|
|
516
|
+
await Promise.all(allManagers.map((m) => m.close().catch(() => {
|
|
517
|
+
})));
|
|
518
|
+
throw firstRejected.reason;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
function hasExplicitBaseVersion(skill) {
|
|
522
|
+
if (skill.packageName) {
|
|
523
|
+
const atSignIndex = skill.packageName.indexOf("@", 1);
|
|
524
|
+
if (atSignIndex > 0) {
|
|
525
|
+
return true;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
if (skill.args) {
|
|
529
|
+
for (const arg of skill.args) {
|
|
530
|
+
if (arg.startsWith("@perstack/base@")) {
|
|
531
|
+
const versionStart = "@perstack/base@".length;
|
|
532
|
+
if (arg.length > versionStart) {
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
function isBaseSkill(skill) {
|
|
541
|
+
if (skill.name === "@perstack/base") {
|
|
542
|
+
return true;
|
|
543
|
+
}
|
|
544
|
+
if (skill.type === "mcpStdioSkill") {
|
|
545
|
+
const stdioSkill = skill;
|
|
546
|
+
if (stdioSkill.packageName?.startsWith("@perstack/base")) {
|
|
547
|
+
return true;
|
|
548
|
+
}
|
|
549
|
+
if (stdioSkill.args?.some((arg) => arg.startsWith("@perstack/base"))) {
|
|
550
|
+
return true;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
function shouldUseBundledBase(baseSkill, perstackBaseSkillCommand) {
|
|
556
|
+
if (perstackBaseSkillCommand && perstackBaseSkillCommand.length > 0) {
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
if (baseSkill.type === "mcpSseSkill") {
|
|
560
|
+
return false;
|
|
561
|
+
}
|
|
562
|
+
if (hasExplicitBaseVersion(baseSkill)) {
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
return true;
|
|
566
|
+
}
|
|
567
|
+
async function getSkillManagers(expert, experts, setting, eventListener, options) {
|
|
568
|
+
const { perstackBaseSkillCommand, env, jobId, runId } = setting;
|
|
569
|
+
const { skills } = expert;
|
|
570
|
+
const factory = options?.factory ?? defaultSkillManagerFactory;
|
|
571
|
+
if (!skills["@perstack/base"]) {
|
|
572
|
+
throw new Error("Base skill is not defined");
|
|
573
|
+
}
|
|
574
|
+
const factoryContext = {
|
|
575
|
+
env,
|
|
576
|
+
jobId,
|
|
577
|
+
runId,
|
|
578
|
+
eventListener
|
|
579
|
+
};
|
|
580
|
+
const allManagers = [];
|
|
581
|
+
const baseSkill = skills["@perstack/base"];
|
|
582
|
+
const useBundledBase = (baseSkill.type === "mcpStdioSkill" || baseSkill.type === "mcpSseSkill") && shouldUseBundledBase(baseSkill, perstackBaseSkillCommand);
|
|
583
|
+
if (useBundledBase && baseSkill.type === "mcpStdioSkill") {
|
|
584
|
+
if (baseSkill.requiredEnv.length > 0) {
|
|
585
|
+
console.warn(
|
|
586
|
+
`[perstack] requiredEnv is ignored for bundled @perstack/base. Pin a version to enable it.`
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
const baseManager = factory.createInMemoryBase(baseSkill, factoryContext);
|
|
590
|
+
allManagers.push(baseManager);
|
|
591
|
+
await initSkillManagersWithCleanup([baseManager], allManagers);
|
|
592
|
+
}
|
|
593
|
+
const mcpSkills = Object.values(skills).filter(
|
|
594
|
+
(skill) => skill.type === "mcpStdioSkill" || skill.type === "mcpSseSkill"
|
|
595
|
+
).filter((skill) => {
|
|
596
|
+
if (useBundledBase && isBaseSkill(skill)) {
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
return true;
|
|
600
|
+
}).map((skill) => {
|
|
601
|
+
if (perstackBaseSkillCommand && skill.type === "mcpStdioSkill") {
|
|
602
|
+
const matchesBaseByPackage = skill.command === "npx" && skill.packageName === "@perstack/base";
|
|
603
|
+
const matchesBaseByArgs = skill.command === "npx" && Array.isArray(skill.args) && skill.args.includes("@perstack/base");
|
|
604
|
+
if (matchesBaseByPackage || matchesBaseByArgs) {
|
|
605
|
+
const [overrideCommand, ...overrideArgs] = perstackBaseSkillCommand;
|
|
606
|
+
if (!overrideCommand) {
|
|
607
|
+
throw new Error("perstackBaseSkillCommand must have at least one element");
|
|
608
|
+
}
|
|
609
|
+
return {
|
|
610
|
+
...skill,
|
|
611
|
+
command: overrideCommand,
|
|
612
|
+
packageName: void 0,
|
|
613
|
+
args: overrideArgs,
|
|
614
|
+
lazyInit: false
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return skill;
|
|
619
|
+
});
|
|
620
|
+
const mcpSkillManagers = mcpSkills.map((skill) => {
|
|
621
|
+
const manager = factory.createMcp(skill, factoryContext);
|
|
622
|
+
allManagers.push(manager);
|
|
623
|
+
return manager;
|
|
624
|
+
});
|
|
625
|
+
await initSkillManagersWithCleanup(mcpSkillManagers, allManagers);
|
|
626
|
+
if (!options?.isDelegatedRun) {
|
|
627
|
+
const interactiveSkills = Object.values(skills).filter(
|
|
628
|
+
(skill) => skill.type === "interactiveSkill"
|
|
629
|
+
);
|
|
630
|
+
const interactiveSkillManagers = interactiveSkills.map((interactiveSkill) => {
|
|
631
|
+
const manager = factory.createInteractive(interactiveSkill, factoryContext);
|
|
632
|
+
allManagers.push(manager);
|
|
633
|
+
return manager;
|
|
634
|
+
});
|
|
635
|
+
await initSkillManagersWithCleanup(interactiveSkillManagers, allManagers);
|
|
636
|
+
}
|
|
637
|
+
const delegateSkillManagers = [];
|
|
638
|
+
for (const delegateExpertName of expert.delegates) {
|
|
639
|
+
const delegate = experts[delegateExpertName];
|
|
640
|
+
if (!delegate) {
|
|
641
|
+
await Promise.all(allManagers.map((m) => m.close().catch(() => {
|
|
642
|
+
})));
|
|
643
|
+
throw new Error(`Delegate expert "${delegateExpertName}" not found in experts`);
|
|
644
|
+
}
|
|
645
|
+
const manager = factory.createDelegate(delegate, factoryContext);
|
|
646
|
+
allManagers.push(manager);
|
|
647
|
+
delegateSkillManagers.push(manager);
|
|
648
|
+
}
|
|
649
|
+
await initSkillManagersWithCleanup(delegateSkillManagers, allManagers);
|
|
650
|
+
const skillManagers = {};
|
|
651
|
+
for (const manager of allManagers) {
|
|
652
|
+
skillManagers[manager.name] = manager;
|
|
653
|
+
}
|
|
654
|
+
return skillManagers;
|
|
655
|
+
}
|
|
656
|
+
async function closeSkillManagers(skillManagers) {
|
|
657
|
+
await Promise.all(Object.values(skillManagers).map((m) => m.close().catch(() => {
|
|
658
|
+
})));
|
|
659
|
+
}
|
|
660
|
+
async function getSkillManagerByToolName(skillManagers, toolName) {
|
|
661
|
+
for (const skillManager of Object.values(skillManagers)) {
|
|
662
|
+
const toolDefinitions = await skillManager.getToolDefinitions();
|
|
663
|
+
for (const toolDefinition of toolDefinitions) {
|
|
664
|
+
if (toolDefinition.name === toolName) {
|
|
665
|
+
return skillManager;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
throw new Error(`Tool ${toolName} not found`);
|
|
670
|
+
}
|
|
671
|
+
async function getToolSet(skillManagers) {
|
|
672
|
+
const tools = {};
|
|
673
|
+
for (const skillManager of Object.values(skillManagers)) {
|
|
674
|
+
const toolDefinitions = await skillManager.getToolDefinitions();
|
|
675
|
+
for (const toolDefinition of toolDefinitions) {
|
|
676
|
+
tools[toolDefinition.name] = tool({
|
|
677
|
+
description: toolDefinition.description,
|
|
678
|
+
inputSchema: jsonSchema(toolDefinition.inputSchema)
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return tools;
|
|
683
|
+
}
|
|
684
|
+
async function collectToolDefinitionsForExpert(expert, options) {
|
|
685
|
+
const { env, perstackBaseSkillCommand, factory = defaultSkillManagerFactory } = options;
|
|
686
|
+
const { skills } = expert;
|
|
687
|
+
if (!skills["@perstack/base"]) {
|
|
688
|
+
throw new Error("Base skill is not defined");
|
|
689
|
+
}
|
|
690
|
+
const factoryContext = {
|
|
691
|
+
env,
|
|
692
|
+
jobId: "lockfile-generation",
|
|
693
|
+
runId: "lockfile-generation",
|
|
694
|
+
eventListener: void 0
|
|
695
|
+
};
|
|
696
|
+
const allManagers = [];
|
|
697
|
+
const baseSkill = skills["@perstack/base"];
|
|
698
|
+
const useBundledBase = (baseSkill.type === "mcpStdioSkill" || baseSkill.type === "mcpSseSkill") && shouldUseBundledBase(baseSkill, perstackBaseSkillCommand);
|
|
699
|
+
try {
|
|
700
|
+
if (useBundledBase && baseSkill.type === "mcpStdioSkill") {
|
|
701
|
+
const baseManager = factory.createInMemoryBase(baseSkill, factoryContext);
|
|
702
|
+
allManagers.push(baseManager);
|
|
703
|
+
await initSkillManagersWithCleanup([baseManager], allManagers);
|
|
704
|
+
}
|
|
705
|
+
const mcpSkills = Object.values(skills).filter(
|
|
706
|
+
(skill) => skill.type === "mcpStdioSkill" || skill.type === "mcpSseSkill"
|
|
707
|
+
).filter((skill) => {
|
|
708
|
+
if (useBundledBase && isBaseSkill(skill)) {
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
return true;
|
|
712
|
+
}).map((skill) => {
|
|
713
|
+
if (perstackBaseSkillCommand && skill.type === "mcpStdioSkill") {
|
|
714
|
+
const matchesBaseByPackage = skill.command === "npx" && skill.packageName === "@perstack/base";
|
|
715
|
+
const matchesBaseByArgs = skill.command === "npx" && Array.isArray(skill.args) && skill.args.includes("@perstack/base");
|
|
716
|
+
if (matchesBaseByPackage || matchesBaseByArgs) {
|
|
717
|
+
const [overrideCommand, ...overrideArgs] = perstackBaseSkillCommand;
|
|
718
|
+
if (!overrideCommand) {
|
|
719
|
+
throw new Error("perstackBaseSkillCommand must have at least one element");
|
|
720
|
+
}
|
|
721
|
+
return {
|
|
722
|
+
...skill,
|
|
723
|
+
command: overrideCommand,
|
|
724
|
+
packageName: void 0,
|
|
725
|
+
args: overrideArgs,
|
|
726
|
+
lazyInit: false
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return skill;
|
|
731
|
+
});
|
|
732
|
+
const mcpSkillManagers = mcpSkills.map((skill) => {
|
|
733
|
+
const manager = factory.createMcp(skill, factoryContext);
|
|
734
|
+
allManagers.push(manager);
|
|
735
|
+
return manager;
|
|
736
|
+
});
|
|
737
|
+
await initSkillManagersWithCleanup(mcpSkillManagers, allManagers);
|
|
738
|
+
const interactiveSkills = Object.values(skills).filter(
|
|
739
|
+
(skill) => skill.type === "interactiveSkill"
|
|
740
|
+
);
|
|
741
|
+
const interactiveSkillManagers = interactiveSkills.map((interactiveSkill) => {
|
|
742
|
+
const manager = factory.createInteractive(interactiveSkill, factoryContext);
|
|
743
|
+
allManagers.push(manager);
|
|
744
|
+
return manager;
|
|
745
|
+
});
|
|
746
|
+
await initSkillManagersWithCleanup(interactiveSkillManagers, allManagers);
|
|
747
|
+
const toolDefinitions = [];
|
|
748
|
+
for (const manager of allManagers) {
|
|
749
|
+
const definitions = await manager.getToolDefinitions();
|
|
750
|
+
for (const def of definitions) {
|
|
751
|
+
toolDefinitions.push({
|
|
752
|
+
skillName: def.skillName,
|
|
753
|
+
name: def.name,
|
|
754
|
+
description: def.description,
|
|
755
|
+
inputSchema: def.inputSchema
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return toolDefinitions;
|
|
760
|
+
} finally {
|
|
761
|
+
await Promise.all(allManagers.map((m) => m.close().catch(() => {
|
|
762
|
+
})));
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
async function getSkillManagersFromLockfile(expert, experts, setting, lockfileToolDefinitions, eventListener, options) {
|
|
766
|
+
const { LockfileSkillManager: LockfileSkillManager2 } = await import('./lockfile-skill-manager-LG2E4KAZ.js');
|
|
767
|
+
const { perstackBaseSkillCommand, env, jobId, runId } = setting;
|
|
768
|
+
const { skills } = expert;
|
|
769
|
+
const factory = options?.factory ?? defaultSkillManagerFactory;
|
|
770
|
+
if (!skills["@perstack/base"]) {
|
|
771
|
+
throw new Error("Base skill is not defined");
|
|
772
|
+
}
|
|
773
|
+
const factoryContext = {
|
|
774
|
+
env,
|
|
775
|
+
jobId,
|
|
776
|
+
runId,
|
|
777
|
+
eventListener
|
|
778
|
+
};
|
|
779
|
+
const allManagers = [];
|
|
780
|
+
const mcpSkills = Object.values(skills).filter(
|
|
781
|
+
(skill) => skill.type === "mcpStdioSkill" || skill.type === "mcpSseSkill"
|
|
782
|
+
);
|
|
783
|
+
for (const skill of mcpSkills) {
|
|
784
|
+
const skillToolDefs = lockfileToolDefinitions[skill.name] ?? [];
|
|
785
|
+
const manager = new LockfileSkillManager2({
|
|
786
|
+
skill,
|
|
787
|
+
toolDefinitions: skillToolDefs,
|
|
788
|
+
env,
|
|
789
|
+
jobId,
|
|
790
|
+
runId,
|
|
791
|
+
eventListener,
|
|
792
|
+
perstackBaseSkillCommand
|
|
793
|
+
});
|
|
794
|
+
await manager.init();
|
|
795
|
+
allManagers.push(manager);
|
|
796
|
+
}
|
|
797
|
+
if (!options?.isDelegatedRun) {
|
|
798
|
+
const interactiveSkills = Object.values(skills).filter(
|
|
799
|
+
(skill) => skill.type === "interactiveSkill"
|
|
800
|
+
);
|
|
801
|
+
const interactiveSkillManagers = interactiveSkills.map((interactiveSkill) => {
|
|
802
|
+
const manager = factory.createInteractive(interactiveSkill, factoryContext);
|
|
803
|
+
allManagers.push(manager);
|
|
804
|
+
return manager;
|
|
805
|
+
});
|
|
806
|
+
await initSkillManagersWithCleanup(interactiveSkillManagers, allManagers);
|
|
807
|
+
}
|
|
808
|
+
const delegateSkillManagers = [];
|
|
809
|
+
for (const delegateExpertName of expert.delegates) {
|
|
810
|
+
const delegate = experts[delegateExpertName];
|
|
811
|
+
if (!delegate) {
|
|
812
|
+
await Promise.all(allManagers.map((m) => m.close().catch(() => {
|
|
813
|
+
})));
|
|
814
|
+
throw new Error(`Delegate expert "${delegateExpertName}" not found in experts`);
|
|
815
|
+
}
|
|
816
|
+
const manager = factory.createDelegate(delegate, factoryContext);
|
|
817
|
+
allManagers.push(manager);
|
|
818
|
+
delegateSkillManagers.push(manager);
|
|
819
|
+
}
|
|
820
|
+
await initSkillManagersWithCleanup(delegateSkillManagers, allManagers);
|
|
821
|
+
const skillManagers = {};
|
|
822
|
+
for (const manager of allManagers) {
|
|
823
|
+
skillManagers[manager.name] = manager;
|
|
824
|
+
}
|
|
825
|
+
return skillManagers;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// src/skill-manager/lockfile-skill-manager.ts
|
|
829
|
+
var LockfileSkillManager = class extends BaseSkillManager {
|
|
830
|
+
name;
|
|
831
|
+
type = "mcp";
|
|
832
|
+
lazyInit = true;
|
|
833
|
+
skill;
|
|
834
|
+
_cachedToolDefinitions;
|
|
835
|
+
_realManager;
|
|
836
|
+
_pendingInit;
|
|
837
|
+
_env;
|
|
838
|
+
_perstackBaseSkillCommand;
|
|
839
|
+
constructor(options) {
|
|
840
|
+
super(options.jobId, options.runId, options.eventListener);
|
|
841
|
+
this.name = options.skill.name;
|
|
842
|
+
this.skill = options.skill;
|
|
843
|
+
this._env = options.env;
|
|
844
|
+
this._perstackBaseSkillCommand = options.perstackBaseSkillCommand;
|
|
845
|
+
this._cachedToolDefinitions = options.toolDefinitions.map((def) => ({
|
|
846
|
+
skillName: def.skillName,
|
|
847
|
+
name: def.name,
|
|
848
|
+
description: def.description,
|
|
849
|
+
inputSchema: def.inputSchema,
|
|
850
|
+
interactive: false
|
|
851
|
+
}));
|
|
852
|
+
}
|
|
853
|
+
async _doInit() {
|
|
854
|
+
}
|
|
855
|
+
async getToolDefinitions() {
|
|
856
|
+
return this._filterTools(this._cachedToolDefinitions);
|
|
857
|
+
}
|
|
858
|
+
_filterTools(tools) {
|
|
859
|
+
const omit = this.skill.omit ?? [];
|
|
860
|
+
const pick = this.skill.pick ?? [];
|
|
861
|
+
return tools.filter((tool2) => omit.length > 0 ? !omit.includes(tool2.name) : true).filter((tool2) => pick.length > 0 ? pick.includes(tool2.name) : true);
|
|
862
|
+
}
|
|
863
|
+
async _ensureRealManager() {
|
|
864
|
+
if (this._realManager) {
|
|
865
|
+
return this._realManager;
|
|
866
|
+
}
|
|
867
|
+
if (this._pendingInit) {
|
|
868
|
+
return this._pendingInit;
|
|
869
|
+
}
|
|
870
|
+
this._pendingInit = this._initRealManager();
|
|
871
|
+
try {
|
|
872
|
+
this._realManager = await this._pendingInit;
|
|
873
|
+
return this._realManager;
|
|
874
|
+
} finally {
|
|
875
|
+
this._pendingInit = void 0;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
async _initRealManager() {
|
|
879
|
+
const useBundledBase = this.skill.type === "mcpStdioSkill" && isBaseSkill(this.skill) && shouldUseBundledBase(this.skill, this._perstackBaseSkillCommand);
|
|
880
|
+
let manager;
|
|
881
|
+
if (useBundledBase && this.skill.type === "mcpStdioSkill") {
|
|
882
|
+
manager = new InMemoryBaseSkillManager(
|
|
883
|
+
this.skill,
|
|
884
|
+
this._jobId,
|
|
885
|
+
this._runId,
|
|
886
|
+
this._eventListener
|
|
887
|
+
);
|
|
888
|
+
} else {
|
|
889
|
+
const skillToUse = this._applyBaseSkillCommandOverride(this.skill);
|
|
890
|
+
manager = new McpSkillManager(
|
|
891
|
+
skillToUse,
|
|
892
|
+
this._env,
|
|
893
|
+
this._jobId,
|
|
894
|
+
this._runId,
|
|
895
|
+
this._eventListener
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
await manager.init();
|
|
899
|
+
return manager;
|
|
900
|
+
}
|
|
901
|
+
_applyBaseSkillCommandOverride(skill) {
|
|
902
|
+
if (!this._perstackBaseSkillCommand || skill.type !== "mcpStdioSkill") {
|
|
903
|
+
return skill;
|
|
904
|
+
}
|
|
905
|
+
const matchesBaseByPackage = skill.command === "npx" && skill.packageName === "@perstack/base";
|
|
906
|
+
const matchesBaseByArgs = skill.command === "npx" && Array.isArray(skill.args) && skill.args.includes("@perstack/base");
|
|
907
|
+
if (matchesBaseByPackage || matchesBaseByArgs) {
|
|
908
|
+
const [overrideCommand, ...overrideArgs] = this._perstackBaseSkillCommand;
|
|
909
|
+
if (!overrideCommand) {
|
|
910
|
+
return skill;
|
|
911
|
+
}
|
|
912
|
+
return {
|
|
913
|
+
...skill,
|
|
914
|
+
command: overrideCommand,
|
|
915
|
+
packageName: void 0,
|
|
916
|
+
args: overrideArgs,
|
|
917
|
+
lazyInit: false
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
return skill;
|
|
921
|
+
}
|
|
922
|
+
async callTool(toolName, input) {
|
|
923
|
+
const realManager = await this._ensureRealManager();
|
|
924
|
+
return realManager.callTool(toolName, input);
|
|
925
|
+
}
|
|
926
|
+
async close() {
|
|
927
|
+
if (this._realManager) {
|
|
928
|
+
await this._realManager.close();
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
export { LockfileSkillManager, closeSkillManagers, collectToolDefinitionsForExpert, getSkillManagerByToolName, getSkillManagers, getSkillManagersFromLockfile, getToolSet };
|
|
934
|
+
//# sourceMappingURL=chunk-RG4QHAGG.js.map
|
|
935
|
+
//# sourceMappingURL=chunk-RG4QHAGG.js.map
|