palpal-core 0.1.0
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/LICENSE +21 -0
- package/README.ja.md +114 -0
- package/README.md +118 -0
- package/dist/src/agent.d.ts +16 -0
- package/dist/src/agent.js +21 -0
- package/dist/src/approval.d.ts +14 -0
- package/dist/src/approval.js +67 -0
- package/dist/src/errors.d.ts +9 -0
- package/dist/src/errors.js +52 -0
- package/dist/src/guardrails.d.ts +11 -0
- package/dist/src/guardrails.js +29 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.js +27 -0
- package/dist/src/mcp.d.ts +15 -0
- package/dist/src/mcp.js +34 -0
- package/dist/src/providers.d.ts +2 -0
- package/dist/src/providers.js +413 -0
- package/dist/src/runner.d.ts +31 -0
- package/dist/src/runner.js +389 -0
- package/dist/src/safety.d.ts +37 -0
- package/dist/src/safety.js +348 -0
- package/dist/src/skills.d.ts +19 -0
- package/dist/src/skills.js +269 -0
- package/dist/src/tools.d.ts +21 -0
- package/dist/src/tools.js +53 -0
- package/dist/src/types.d.ts +227 -0
- package/dist/src/types.js +2 -0
- package/package.json +36 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SafetyGate = exports.InMemoryPolicyStore = exports.ModelSafetyAgent = exports.SafetyAgent = void 0;
|
|
4
|
+
exports.deriveAgentCapabilitySnapshot = deriveAgentCapabilitySnapshot;
|
|
5
|
+
const errors_1 = require("./errors");
|
|
6
|
+
class SafetyAgent {
|
|
7
|
+
evaluator;
|
|
8
|
+
constructor(evaluator) {
|
|
9
|
+
this.evaluator =
|
|
10
|
+
evaluator ??
|
|
11
|
+
(() => ({
|
|
12
|
+
decision: "allow",
|
|
13
|
+
reason: "default-allow",
|
|
14
|
+
risk_level: 1
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
17
|
+
static allowAll() {
|
|
18
|
+
return new SafetyAgent();
|
|
19
|
+
}
|
|
20
|
+
async evaluate(agent, request, policy) {
|
|
21
|
+
try {
|
|
22
|
+
const raw = await this.evaluator(agent, request, policy);
|
|
23
|
+
return normalizeDecision(raw, policy.name);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
throw new errors_1.AgentsError("AGENTS-E-GATE-EVAL", "SafetyAgent evaluation failed.", error);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.SafetyAgent = SafetyAgent;
|
|
31
|
+
class ModelSafetyAgent {
|
|
32
|
+
model;
|
|
33
|
+
rubricLines;
|
|
34
|
+
name;
|
|
35
|
+
includeUserIntent;
|
|
36
|
+
constructor(options) {
|
|
37
|
+
(0, errors_1.ensure)(options?.model, "AGENTS-E-GATE-EVAL", "ModelSafetyAgent.model is required.");
|
|
38
|
+
const rubricLines = normalizeRubric(options.rubric);
|
|
39
|
+
(0, errors_1.ensure)(rubricLines.length > 0, "AGENTS-E-GATE-EVAL", "ModelSafetyAgent.rubric must include at least one rule.");
|
|
40
|
+
this.model = options.model;
|
|
41
|
+
this.rubricLines = rubricLines;
|
|
42
|
+
this.name = options.name?.trim() || "model-safety-agent";
|
|
43
|
+
this.includeUserIntent = options.includeUserIntent === true;
|
|
44
|
+
}
|
|
45
|
+
async evaluate(agent, request, policy) {
|
|
46
|
+
const capabilitySnapshot = request.capability_snapshot ?? deriveAgentCapabilitySnapshot(agent);
|
|
47
|
+
const toolCatalog = request.tool_catalog ?? capabilitySnapshot.tool_catalog;
|
|
48
|
+
const targetTool = request.target_tool ??
|
|
49
|
+
toolCatalog.find((tool) => tool.name === request.tool_name);
|
|
50
|
+
const prompt = buildModelSafetyPrompt({
|
|
51
|
+
rubricLines: this.rubricLines,
|
|
52
|
+
includeUserIntent: this.includeUserIntent,
|
|
53
|
+
policy,
|
|
54
|
+
request: {
|
|
55
|
+
...request,
|
|
56
|
+
capability_snapshot: capabilitySnapshot,
|
|
57
|
+
tool_catalog: toolCatalog,
|
|
58
|
+
target_tool: targetTool
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
const safetyAgentLike = {
|
|
62
|
+
name: this.name,
|
|
63
|
+
instructions: [
|
|
64
|
+
"You are a safety evaluator for tool execution.",
|
|
65
|
+
"Return JSON only. Do not include markdown."
|
|
66
|
+
].join(" "),
|
|
67
|
+
tools: []
|
|
68
|
+
};
|
|
69
|
+
const result = await this.model.generate({
|
|
70
|
+
agent: safetyAgentLike,
|
|
71
|
+
inputText: prompt,
|
|
72
|
+
toolCalls: [],
|
|
73
|
+
stream: false
|
|
74
|
+
});
|
|
75
|
+
const outputText = result.outputText?.trim() ?? "";
|
|
76
|
+
(0, errors_1.ensure)(outputText.length > 0, "AGENTS-E-GATE-EVAL", "ModelSafetyAgent returned empty output.");
|
|
77
|
+
const parsed = parseModelSafetyDecision(outputText);
|
|
78
|
+
return normalizeDecision(parsed, policy.name);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
exports.ModelSafetyAgent = ModelSafetyAgent;
|
|
82
|
+
class InMemoryPolicyStore {
|
|
83
|
+
profiles = new Map();
|
|
84
|
+
constructor() {
|
|
85
|
+
this.setProfile({
|
|
86
|
+
name: "strict",
|
|
87
|
+
approval_mode: "always",
|
|
88
|
+
allowed_tool_scopes: ["function", "skill", "mcp"]
|
|
89
|
+
});
|
|
90
|
+
this.setProfile({
|
|
91
|
+
name: "balanced",
|
|
92
|
+
approval_mode: "risk_based",
|
|
93
|
+
allowed_tool_scopes: ["function", "skill", "mcp"]
|
|
94
|
+
});
|
|
95
|
+
this.setProfile({
|
|
96
|
+
name: "fast",
|
|
97
|
+
approval_mode: "never",
|
|
98
|
+
allowed_tool_scopes: ["function", "skill", "mcp"]
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
getProfile(name) {
|
|
102
|
+
const profile = this.profiles.get(name);
|
|
103
|
+
(0, errors_1.ensure)(profile, "AGENTS-E-POLICY-INVALID", `Unknown policy profile: ${name}`);
|
|
104
|
+
return profile;
|
|
105
|
+
}
|
|
106
|
+
setProfile(profile) {
|
|
107
|
+
this.profiles.set(profile.name, profile);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
exports.InMemoryPolicyStore = InMemoryPolicyStore;
|
|
111
|
+
class SafetyGate {
|
|
112
|
+
safetyAgent;
|
|
113
|
+
policyStore;
|
|
114
|
+
constructor(safetyAgent, policyStore) {
|
|
115
|
+
this.safetyAgent = safetyAgent;
|
|
116
|
+
this.policyStore = policyStore ?? new InMemoryPolicyStore();
|
|
117
|
+
}
|
|
118
|
+
async evaluate(agent, request, context) {
|
|
119
|
+
const profile = this.policyStore.getProfile(context.policyProfile);
|
|
120
|
+
const capabilitySnapshot = deriveAgentCapabilitySnapshot(agent);
|
|
121
|
+
const targetTool = capabilitySnapshot.tool_catalog.find((tool) => tool.name === request.tool_name);
|
|
122
|
+
const enrichedRequest = {
|
|
123
|
+
...request,
|
|
124
|
+
capability_snapshot: capabilitySnapshot,
|
|
125
|
+
tool_catalog: capabilitySnapshot.tool_catalog,
|
|
126
|
+
target_tool: targetTool
|
|
127
|
+
};
|
|
128
|
+
const rawDecision = await this.safetyAgent.evaluate(agent, enrichedRequest, profile);
|
|
129
|
+
validateSafetyDecision(rawDecision);
|
|
130
|
+
return {
|
|
131
|
+
decision: rawDecision.decision,
|
|
132
|
+
reason: rawDecision.reason,
|
|
133
|
+
risk_level: rawDecision.risk_level
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
exports.SafetyGate = SafetyGate;
|
|
138
|
+
function deriveAgentCapabilitySnapshot(agent) {
|
|
139
|
+
(0, errors_1.ensure)(agent, "AGENTS-E-AGENT-CAPABILITY-RESOLVE", "Agent is required.");
|
|
140
|
+
(0, errors_1.ensure)(Array.isArray(agent.tools), "AGENTS-E-AGENT-CAPABILITY-RESOLVE", "Agent.tools must be an array.");
|
|
141
|
+
const toolNames = agent.tools.map((tool) => tool.name);
|
|
142
|
+
const skillIds = new Set();
|
|
143
|
+
const mcpCapabilities = [];
|
|
144
|
+
const toolCatalog = [];
|
|
145
|
+
for (const tool of agent.tools) {
|
|
146
|
+
if (!tool.name) {
|
|
147
|
+
throw new errors_1.AgentsError("AGENTS-E-AGENT-CAPABILITY-RESOLVE", "Agent has a tool without a name.");
|
|
148
|
+
}
|
|
149
|
+
if (tool.kind === "skill") {
|
|
150
|
+
const skillId = readString(tool, "skill_id") ?? readString(tool, "skillId");
|
|
151
|
+
if (skillId) {
|
|
152
|
+
skillIds.add(skillId);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (tool.kind === "mcp") {
|
|
156
|
+
const capabilities = readCapabilities(tool);
|
|
157
|
+
if (capabilities.length === 0) {
|
|
158
|
+
mcpCapabilities.push({
|
|
159
|
+
name: tool.name,
|
|
160
|
+
description: tool.description,
|
|
161
|
+
risk_level: 3
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
mcpCapabilities.push(...capabilities);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
toolCatalog.push(buildToolCapability(tool));
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
agent_name: agent.name,
|
|
172
|
+
tool_names: toolNames,
|
|
173
|
+
skill_ids: [...skillIds],
|
|
174
|
+
mcp_capabilities: mcpCapabilities,
|
|
175
|
+
tool_catalog: toolCatalog
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function validateSafetyDecision(decision) {
|
|
179
|
+
const allowed = new Set(["allow", "deny", "needs_human"]);
|
|
180
|
+
(0, errors_1.ensure)(decision, "AGENTS-E-GATE-EVAL", "SafetyAgent decision is missing.");
|
|
181
|
+
(0, errors_1.ensure)(allowed.has(decision.decision), "AGENTS-E-GATE-EVAL", "SafetyAgent decision.decision is invalid.", decision);
|
|
182
|
+
(0, errors_1.ensure)(Number.isInteger(decision.risk_level) && decision.risk_level >= 1 && decision.risk_level <= 5, "AGENTS-E-GATE-EVAL", "SafetyAgent decision.risk_level must be an integer between 1 and 5.", decision);
|
|
183
|
+
(0, errors_1.ensure)(typeof decision.reason === "string" && decision.reason.length > 0, "AGENTS-E-GATE-EVAL", "SafetyAgent decision.reason is required.", decision);
|
|
184
|
+
(0, errors_1.ensure)(typeof decision.policy_ref === "string" && decision.policy_ref.length > 0, "AGENTS-E-GATE-EVAL", "SafetyAgent decision.policy_ref is required.", decision);
|
|
185
|
+
}
|
|
186
|
+
function readString(tool, key) {
|
|
187
|
+
const value = tool.metadata?.[key];
|
|
188
|
+
if (typeof value === "string" && value.length > 0) {
|
|
189
|
+
return value;
|
|
190
|
+
}
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
function readCapabilities(tool) {
|
|
194
|
+
const value = tool.metadata?.capabilities;
|
|
195
|
+
if (!Array.isArray(value)) {
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
const list = [];
|
|
199
|
+
for (const item of value) {
|
|
200
|
+
if (!item || typeof item !== "object") {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const record = item;
|
|
204
|
+
if (typeof record.name === "string" &&
|
|
205
|
+
typeof record.description === "string" &&
|
|
206
|
+
typeof record.risk_level === "number") {
|
|
207
|
+
list.push({
|
|
208
|
+
name: record.name,
|
|
209
|
+
description: record.description,
|
|
210
|
+
risk_level: Math.max(1, Math.min(5, Math.floor(record.risk_level)))
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return list;
|
|
215
|
+
}
|
|
216
|
+
function buildToolCapability(tool) {
|
|
217
|
+
const capability = {
|
|
218
|
+
name: tool.name,
|
|
219
|
+
kind: tool.kind,
|
|
220
|
+
description: tool.description,
|
|
221
|
+
parameters_schema: tool.parameters
|
|
222
|
+
};
|
|
223
|
+
if (tool.kind === "skill") {
|
|
224
|
+
const skillId = readString(tool, "skill_id") ?? readString(tool, "skillId");
|
|
225
|
+
const overview = readString(tool, "skill_overview");
|
|
226
|
+
const constraints = readStringArray(tool, "skill_constraints");
|
|
227
|
+
const tags = readStringArray(tool, "skill_tags");
|
|
228
|
+
capability.skill = {
|
|
229
|
+
skill_id: skillId,
|
|
230
|
+
overview,
|
|
231
|
+
constraints: constraints.length > 0 ? constraints : undefined,
|
|
232
|
+
tags: tags.length > 0 ? tags : undefined
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
if (tool.kind === "mcp") {
|
|
236
|
+
capability.mcp_capabilities = readCapabilities(tool);
|
|
237
|
+
}
|
|
238
|
+
return capability;
|
|
239
|
+
}
|
|
240
|
+
function readStringArray(tool, key) {
|
|
241
|
+
const value = tool.metadata?.[key];
|
|
242
|
+
if (!Array.isArray(value)) {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
return value.filter((item) => typeof item === "string");
|
|
246
|
+
}
|
|
247
|
+
function normalizeDecision(decision, policyRefFallback) {
|
|
248
|
+
return {
|
|
249
|
+
decision: decision.decision,
|
|
250
|
+
reason: decision.reason,
|
|
251
|
+
risk_level: decision.risk_level,
|
|
252
|
+
policy_ref: decision.policy_ref ?? policyRefFallback
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function normalizeRubric(rubric) {
|
|
256
|
+
if (typeof rubric === "string") {
|
|
257
|
+
return rubric
|
|
258
|
+
.split(/\r?\n/)
|
|
259
|
+
.map((line) => line.trim())
|
|
260
|
+
.filter((line) => line.length > 0);
|
|
261
|
+
}
|
|
262
|
+
if (!Array.isArray(rubric)) {
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
return rubric
|
|
266
|
+
.map((line) => (typeof line === "string" ? line.trim() : ""))
|
|
267
|
+
.filter((line) => line.length > 0);
|
|
268
|
+
}
|
|
269
|
+
function buildModelSafetyPrompt(params) {
|
|
270
|
+
const rubric = params.rubricLines.map((line, index) => `${index + 1}. ${line}`).join("\n");
|
|
271
|
+
const requestPayload = {
|
|
272
|
+
tool_name: params.request.tool_name,
|
|
273
|
+
tool_kind: params.request.tool_kind,
|
|
274
|
+
args: params.request.args
|
|
275
|
+
};
|
|
276
|
+
if (params.includeUserIntent) {
|
|
277
|
+
requestPayload.user_intent = params.request.user_intent;
|
|
278
|
+
}
|
|
279
|
+
const payload = {
|
|
280
|
+
policy: {
|
|
281
|
+
name: params.policy.name,
|
|
282
|
+
approval_mode: params.policy.approval_mode,
|
|
283
|
+
allowed_tool_scopes: params.policy.allowed_tool_scopes
|
|
284
|
+
},
|
|
285
|
+
request: requestPayload,
|
|
286
|
+
target_tool: params.request.target_tool ?? null,
|
|
287
|
+
tool_catalog: params.request.tool_catalog ?? [],
|
|
288
|
+
capability_snapshot: params.request.capability_snapshot ?? null
|
|
289
|
+
};
|
|
290
|
+
return [
|
|
291
|
+
"Evaluate whether the requested tool call should be allowed.",
|
|
292
|
+
"",
|
|
293
|
+
"Rubric:",
|
|
294
|
+
rubric,
|
|
295
|
+
"",
|
|
296
|
+
"Decision rules:",
|
|
297
|
+
"1) Return exactly one JSON object.",
|
|
298
|
+
"2) decision must be one of: allow, deny, needs_human.",
|
|
299
|
+
"3) risk_level must be integer 1..5.",
|
|
300
|
+
"4) reason must be concise and specific.",
|
|
301
|
+
"5) policy_ref must reference current policy name.",
|
|
302
|
+
"",
|
|
303
|
+
"Input:",
|
|
304
|
+
safeJson(payload),
|
|
305
|
+
"",
|
|
306
|
+
"Output schema:",
|
|
307
|
+
"{\"decision\":\"allow|deny|needs_human\",\"reason\":\"...\",\"risk_level\":1,\"policy_ref\":\"...\"}"
|
|
308
|
+
].join("\n");
|
|
309
|
+
}
|
|
310
|
+
function parseModelSafetyDecision(outputText) {
|
|
311
|
+
const jsonText = extractJsonObject(outputText);
|
|
312
|
+
(0, errors_1.ensure)(jsonText.length > 0, "AGENTS-E-GATE-EVAL", "ModelSafetyAgent output does not contain a JSON object.");
|
|
313
|
+
let parsed;
|
|
314
|
+
try {
|
|
315
|
+
parsed = JSON.parse(jsonText);
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
throw new errors_1.AgentsError("AGENTS-E-GATE-EVAL", "ModelSafetyAgent returned invalid JSON.", error);
|
|
319
|
+
}
|
|
320
|
+
(0, errors_1.ensure)(parsed && typeof parsed === "object" && !Array.isArray(parsed), "AGENTS-E-GATE-EVAL", "ModelSafetyAgent output must be a JSON object.");
|
|
321
|
+
const record = parsed;
|
|
322
|
+
(0, errors_1.ensure)(typeof record.decision === "string", "AGENTS-E-GATE-EVAL", "ModelSafetyAgent decision.decision is required.");
|
|
323
|
+
(0, errors_1.ensure)(typeof record.reason === "string" && record.reason.length > 0, "AGENTS-E-GATE-EVAL", "ModelSafetyAgent decision.reason is required.");
|
|
324
|
+
(0, errors_1.ensure)(typeof record.risk_level === "number", "AGENTS-E-GATE-EVAL", "ModelSafetyAgent decision.risk_level is required.");
|
|
325
|
+
const policyRef = typeof record.policy_ref === "string" ? record.policy_ref : undefined;
|
|
326
|
+
return {
|
|
327
|
+
decision: record.decision,
|
|
328
|
+
reason: record.reason,
|
|
329
|
+
risk_level: Math.floor(record.risk_level),
|
|
330
|
+
policy_ref: policyRef
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
function extractJsonObject(text) {
|
|
334
|
+
const start = text.indexOf("{");
|
|
335
|
+
const end = text.lastIndexOf("}");
|
|
336
|
+
if (start < 0 || end < start) {
|
|
337
|
+
return "";
|
|
338
|
+
}
|
|
339
|
+
return text.slice(start, end + 1).trim();
|
|
340
|
+
}
|
|
341
|
+
function safeJson(value) {
|
|
342
|
+
try {
|
|
343
|
+
return JSON.stringify(value);
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
return "{\"error\":\"json_serialization_failed\"}";
|
|
347
|
+
}
|
|
348
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Skill, SkillManifest, SkillMode, SkillSummary, Tool } from "./types";
|
|
2
|
+
export interface LoadSkillsOptions {
|
|
3
|
+
dir: string;
|
|
4
|
+
mode?: SkillMode;
|
|
5
|
+
}
|
|
6
|
+
export declare class SkillRegistry {
|
|
7
|
+
loadSkills(options: LoadSkillsOptions): Promise<Skill[]>;
|
|
8
|
+
toTools(skills: Skill[]): Tool[];
|
|
9
|
+
}
|
|
10
|
+
export declare class SkillMetadataExtractor {
|
|
11
|
+
listSkills(skills: Skill[]): Promise<SkillSummary[]>;
|
|
12
|
+
describeSkill(skills: Skill[], skillId: string, detailLevel?: "summary" | "full"): Promise<SkillManifest>;
|
|
13
|
+
toIntrospectionTools(skills: Skill[]): Tool[];
|
|
14
|
+
}
|
|
15
|
+
export declare function loadSkills(options: LoadSkillsOptions): Promise<Skill[]>;
|
|
16
|
+
export declare function toTools(skills: Skill[]): Tool[];
|
|
17
|
+
export declare function listSkills(skills: Skill[]): Promise<SkillSummary[]>;
|
|
18
|
+
export declare function describeSkill(skills: Skill[], skillId: string, detailLevel?: "summary" | "full"): Promise<SkillManifest>;
|
|
19
|
+
export declare function toIntrospectionTools(skills: Skill[]): Tool[];
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SkillMetadataExtractor = exports.SkillRegistry = void 0;
|
|
7
|
+
exports.loadSkills = loadSkills;
|
|
8
|
+
exports.toTools = toTools;
|
|
9
|
+
exports.listSkills = listSkills;
|
|
10
|
+
exports.describeSkill = describeSkill;
|
|
11
|
+
exports.toIntrospectionTools = toIntrospectionTools;
|
|
12
|
+
const node_fs_1 = require("node:fs");
|
|
13
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
14
|
+
const errors_1 = require("./errors");
|
|
15
|
+
const tools_1 = require("./tools");
|
|
16
|
+
class SkillRegistry {
|
|
17
|
+
async loadSkills(options) {
|
|
18
|
+
(0, errors_1.ensure)(options?.dir, "AGENTS-E-SKILL-NOT-LOADED", "loadSkills requires dir.");
|
|
19
|
+
const mode = options.mode ?? "function_tool";
|
|
20
|
+
const stats = await node_fs_1.promises.stat(options.dir).catch(() => null);
|
|
21
|
+
(0, errors_1.ensure)(stats?.isDirectory(), "AGENTS-E-SKILL-NOT-LOADED", `Directory not found: ${options.dir}`);
|
|
22
|
+
const files = await findSkillFiles(options.dir);
|
|
23
|
+
const skills = [];
|
|
24
|
+
for (const filePath of files) {
|
|
25
|
+
const source = await node_fs_1.promises.readFile(filePath, "utf8");
|
|
26
|
+
skills.push(parseSkillFile(source, filePath, mode));
|
|
27
|
+
}
|
|
28
|
+
return skills;
|
|
29
|
+
}
|
|
30
|
+
toTools(skills) {
|
|
31
|
+
validateSkills(skills);
|
|
32
|
+
return skills.map((skill) => {
|
|
33
|
+
const name = `skill.${skill.descriptor.skill_id}`;
|
|
34
|
+
return {
|
|
35
|
+
name,
|
|
36
|
+
description: skill.manifest.overview,
|
|
37
|
+
kind: "skill",
|
|
38
|
+
parameters: skill.descriptor.input_schema,
|
|
39
|
+
metadata: {
|
|
40
|
+
skill_id: skill.descriptor.skill_id,
|
|
41
|
+
mode: skill.descriptor.mode,
|
|
42
|
+
source_path: skill.source_path,
|
|
43
|
+
skill_overview: skill.manifest.overview,
|
|
44
|
+
skill_constraints: skill.manifest.constraints,
|
|
45
|
+
skill_tags: skill.manifest.tags
|
|
46
|
+
},
|
|
47
|
+
execute: async (args) => ({
|
|
48
|
+
skill_id: skill.descriptor.skill_id,
|
|
49
|
+
mode: skill.descriptor.mode,
|
|
50
|
+
args
|
|
51
|
+
})
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.SkillRegistry = SkillRegistry;
|
|
57
|
+
class SkillMetadataExtractor {
|
|
58
|
+
async listSkills(skills) {
|
|
59
|
+
validateSkills(skills);
|
|
60
|
+
return skills.map((skill) => ({
|
|
61
|
+
skill_id: skill.descriptor.skill_id,
|
|
62
|
+
name: skill.manifest.name,
|
|
63
|
+
overview: skill.manifest.overview,
|
|
64
|
+
tags: skill.manifest.tags
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
async describeSkill(skills, skillId, detailLevel = "summary") {
|
|
68
|
+
validateSkills(skills);
|
|
69
|
+
const skill = skills.find((item) => item.descriptor.skill_id === skillId);
|
|
70
|
+
(0, errors_1.ensure)(skill, "AGENTS-E-SKILL-NOT-FOUND", `Skill not found: ${skillId}`);
|
|
71
|
+
if (detailLevel === "full") {
|
|
72
|
+
return skill.manifest;
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
...skill.manifest,
|
|
76
|
+
usage_examples: skill.manifest.usage_examples.slice(0, 1)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
toIntrospectionTools(skills) {
|
|
80
|
+
validateSkills(skills);
|
|
81
|
+
return [
|
|
82
|
+
(0, tools_1.tool)({
|
|
83
|
+
name: "skill.list",
|
|
84
|
+
description: "List loaded skills with summary.",
|
|
85
|
+
execute: async () => this.listSkills(skills)
|
|
86
|
+
}),
|
|
87
|
+
(0, tools_1.tool)({
|
|
88
|
+
name: "skill.describe",
|
|
89
|
+
description: "Describe a skill in summary or full detail.",
|
|
90
|
+
parameters: {
|
|
91
|
+
type: "object",
|
|
92
|
+
properties: {
|
|
93
|
+
skill_id: { type: "string" },
|
|
94
|
+
detail_level: { type: "string", enum: ["summary", "full"] }
|
|
95
|
+
},
|
|
96
|
+
required: ["skill_id"]
|
|
97
|
+
},
|
|
98
|
+
execute: async (args) => {
|
|
99
|
+
const skillId = typeof args.skill_id === "string" ? args.skill_id : "";
|
|
100
|
+
const detail = args.detail_level === "full" || args.detail_level === "summary"
|
|
101
|
+
? args.detail_level
|
|
102
|
+
: "summary";
|
|
103
|
+
return this.describeSkill(skills, skillId, detail);
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
exports.SkillMetadataExtractor = SkillMetadataExtractor;
|
|
110
|
+
const defaultRegistry = new SkillRegistry();
|
|
111
|
+
const defaultExtractor = new SkillMetadataExtractor();
|
|
112
|
+
async function loadSkills(options) {
|
|
113
|
+
return defaultRegistry.loadSkills(options);
|
|
114
|
+
}
|
|
115
|
+
function toTools(skills) {
|
|
116
|
+
return defaultRegistry.toTools(skills);
|
|
117
|
+
}
|
|
118
|
+
async function listSkills(skills) {
|
|
119
|
+
return defaultExtractor.listSkills(skills);
|
|
120
|
+
}
|
|
121
|
+
async function describeSkill(skills, skillId, detailLevel = "summary") {
|
|
122
|
+
return defaultExtractor.describeSkill(skills, skillId, detailLevel);
|
|
123
|
+
}
|
|
124
|
+
function toIntrospectionTools(skills) {
|
|
125
|
+
return defaultExtractor.toIntrospectionTools(skills);
|
|
126
|
+
}
|
|
127
|
+
async function findSkillFiles(dir) {
|
|
128
|
+
const entries = await node_fs_1.promises.readdir(dir, { withFileTypes: true });
|
|
129
|
+
const files = [];
|
|
130
|
+
for (const entry of entries) {
|
|
131
|
+
const fullPath = node_path_1.default.join(dir, entry.name);
|
|
132
|
+
if (entry.isDirectory()) {
|
|
133
|
+
const nested = await findSkillFiles(fullPath);
|
|
134
|
+
files.push(...nested);
|
|
135
|
+
}
|
|
136
|
+
else if (entry.isFile() && entry.name.toUpperCase() === "SKILL.MD") {
|
|
137
|
+
files.push(fullPath);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return files;
|
|
141
|
+
}
|
|
142
|
+
function parseSkillFile(source, sourcePath, mode) {
|
|
143
|
+
const heading = matchGroup(source, /^#\s+(.+)$/m) ?? node_path_1.default.basename(node_path_1.default.dirname(sourcePath));
|
|
144
|
+
const skillId = slugify(heading);
|
|
145
|
+
const tags = parseTags(source);
|
|
146
|
+
const overview = parseOverview(source) ?? `Skill loaded from ${sourcePath}`;
|
|
147
|
+
const constraints = parseConstraints(source);
|
|
148
|
+
const inputSchema = parseInputSchema(source);
|
|
149
|
+
const examples = parseExamples(source);
|
|
150
|
+
const descriptor = {
|
|
151
|
+
skill_id: skillId,
|
|
152
|
+
mode,
|
|
153
|
+
input_schema: inputSchema
|
|
154
|
+
};
|
|
155
|
+
const manifest = {
|
|
156
|
+
skill_id: skillId,
|
|
157
|
+
name: heading,
|
|
158
|
+
overview,
|
|
159
|
+
usage_examples: examples,
|
|
160
|
+
constraints,
|
|
161
|
+
tags,
|
|
162
|
+
input_schema: inputSchema
|
|
163
|
+
};
|
|
164
|
+
return {
|
|
165
|
+
descriptor,
|
|
166
|
+
manifest,
|
|
167
|
+
source_path: sourcePath
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function parseOverview(source) {
|
|
171
|
+
const lines = source
|
|
172
|
+
.split(/\r?\n/)
|
|
173
|
+
.map((line) => line.trim())
|
|
174
|
+
.filter((line) => line.length > 0);
|
|
175
|
+
for (const line of lines) {
|
|
176
|
+
if (line.startsWith("#") || line.startsWith("- ") || line.startsWith("```")) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
return line;
|
|
180
|
+
}
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
function parseTags(source) {
|
|
184
|
+
const raw = matchGroup(source, /tags?\s*:\s*(.+)$/im);
|
|
185
|
+
if (!raw) {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
return raw
|
|
189
|
+
.split(",")
|
|
190
|
+
.map((item) => item.trim())
|
|
191
|
+
.filter((item) => item.length > 0);
|
|
192
|
+
}
|
|
193
|
+
function parseConstraints(source) {
|
|
194
|
+
const lines = source.split(/\r?\n/);
|
|
195
|
+
const constraints = [];
|
|
196
|
+
let inConstraintSection = false;
|
|
197
|
+
for (const lineRaw of lines) {
|
|
198
|
+
const line = lineRaw.trim();
|
|
199
|
+
if (/^#+\s*(constraints?|rules?)\b/i.test(line)) {
|
|
200
|
+
inConstraintSection = true;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (inConstraintSection && line.startsWith("#")) {
|
|
204
|
+
inConstraintSection = false;
|
|
205
|
+
}
|
|
206
|
+
if (inConstraintSection && line.startsWith("- ")) {
|
|
207
|
+
constraints.push(line.slice(2).trim());
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return constraints;
|
|
211
|
+
}
|
|
212
|
+
function parseExamples(source) {
|
|
213
|
+
const lines = source.split(/\r?\n/);
|
|
214
|
+
const examples = [];
|
|
215
|
+
for (const lineRaw of lines) {
|
|
216
|
+
const line = lineRaw.trim();
|
|
217
|
+
if (!line.startsWith("- `")) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const title = line.replace(/^- `/, "").replace(/`.*$/, "").trim() || "example";
|
|
221
|
+
examples.push({
|
|
222
|
+
title,
|
|
223
|
+
input: {}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return examples;
|
|
227
|
+
}
|
|
228
|
+
function parseInputSchema(source) {
|
|
229
|
+
const fence = matchGroup(source, /```json\s*([\s\S]*?)```/im);
|
|
230
|
+
if (!fence) {
|
|
231
|
+
return { type: "object", additionalProperties: true };
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
const parsed = JSON.parse(fence);
|
|
235
|
+
if (isObject(parsed)) {
|
|
236
|
+
if (isObject(parsed.input_schema)) {
|
|
237
|
+
return parsed.input_schema;
|
|
238
|
+
}
|
|
239
|
+
return parsed;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return { type: "object", additionalProperties: true };
|
|
244
|
+
}
|
|
245
|
+
return { type: "object", additionalProperties: true };
|
|
246
|
+
}
|
|
247
|
+
function slugify(value) {
|
|
248
|
+
return value
|
|
249
|
+
.toLowerCase()
|
|
250
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
251
|
+
.replace(/^-+|-+$/g, "")
|
|
252
|
+
.slice(0, 64);
|
|
253
|
+
}
|
|
254
|
+
function matchGroup(source, regex) {
|
|
255
|
+
const matched = source.match(regex);
|
|
256
|
+
if (!matched || typeof matched[1] !== "string") {
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
return matched[1].trim();
|
|
260
|
+
}
|
|
261
|
+
function isObject(value) {
|
|
262
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
function validateSkills(skills) {
|
|
268
|
+
(0, errors_1.ensure)(Array.isArray(skills), "AGENTS-E-SKILL-NOT-LOADED", "skills must be an array.");
|
|
269
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { JsonObject, McpCapabilitySummary, Tool } from "./types";
|
|
2
|
+
export interface FunctionToolDefinition {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
parameters?: JsonObject;
|
|
6
|
+
execute(args: JsonObject): Promise<unknown> | unknown;
|
|
7
|
+
}
|
|
8
|
+
export interface HostedMcpServer {
|
|
9
|
+
id?: string;
|
|
10
|
+
url: string;
|
|
11
|
+
listTools?: () => Promise<McpCapabilitySummary[]>;
|
|
12
|
+
callTool(toolName: string, args: JsonObject): Promise<unknown> | unknown;
|
|
13
|
+
}
|
|
14
|
+
export interface HostedMcpToolOptions {
|
|
15
|
+
name?: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
requireApproval?: boolean;
|
|
18
|
+
capabilities?: McpCapabilitySummary[];
|
|
19
|
+
}
|
|
20
|
+
export declare function tool(definition: FunctionToolDefinition): Tool;
|
|
21
|
+
export declare function hostedMcpTool(server: HostedMcpServer, options?: HostedMcpToolOptions): Tool;
|