@aipack-ai/skills 1.0.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/README.md +65 -0
- package/dist/index.d.ts +181 -0
- package/dist/index.js +354 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# @aipack-ai/skills
|
|
2
|
+
|
|
3
|
+
aipack Agent Skills 插件:对齐 Agent Skills 开放规范(`SKILL.md` + YAML frontmatter),通过 `@aipack-ai/agent` 的 **Extension 插件机制**零侵入接入 Runtime。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- **开放规范格式**:`SKILL.md` + frontmatter(`name` / `description` / `disable-model-invocation`),与 pi / Claude Code 生态 skill 互通
|
|
8
|
+
- **渐进式披露**:system prompt 只注入 `<available_skills>` 目录(name + description),全文由模型经内置 `skill` 工具按需获取,避免 token 浪费
|
|
9
|
+
- **运行时零 fs 依赖**:loader 加载时把正文读入 `content` 字段,工具直接返回内容
|
|
10
|
+
- **多源加载**:user(`~/.aipack/skills`)→ project(`<cwd>/.aipack/skills`)→ `extraPaths`,同名先注册者胜,冲突产出诊断不中断
|
|
11
|
+
- **零开销向后兼容**:无 skill 时不注册工具、不注入 prompt
|
|
12
|
+
|
|
13
|
+
## 快速接入
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createRuntime } from '@aipack-ai/agent';
|
|
17
|
+
import { createSkillsPlugin } from '@aipack-ai/skills';
|
|
18
|
+
|
|
19
|
+
// 一站式(推荐):加载 + 装配
|
|
20
|
+
const plugin = createSkillsPlugin({ load: { cwd: process.cwd() } });
|
|
21
|
+
const runtime = createRuntime({ ..., extensions: plugin.extensions });
|
|
22
|
+
|
|
23
|
+
// DIY:程序化注册
|
|
24
|
+
const ext = new SkillsExtension([{ name: 'demo', description: '...', content: '...' }]);
|
|
25
|
+
createRuntime({ ..., extensions: [ext] });
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 目录结构约定
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
~/.aipack/skills/ # 用户级(优先)
|
|
32
|
+
<cwd>/.aipack/skills/ # 项目级
|
|
33
|
+
pdf-export/
|
|
34
|
+
SKILL.md # 含 SKILL.md 的目录视为 skill 根(不递归)
|
|
35
|
+
loose-skill.md # 直接 .md 文件也可(name 缺省取文件名)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## SKILL.md 格式
|
|
39
|
+
|
|
40
|
+
```markdown
|
|
41
|
+
---
|
|
42
|
+
name: pdf-export
|
|
43
|
+
description: 导出 PDF 的专项指引(模型据此判断是否调用)
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
# PDF Export
|
|
47
|
+
|
|
48
|
+
1. 步骤一
|
|
49
|
+
2. 步骤二 …
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 设计说明
|
|
53
|
+
|
|
54
|
+
插件通过两个 agent 扩展点接入(均为通用能力):
|
|
55
|
+
|
|
56
|
+
| 扩展点 | 用途 |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `ExtensionContext.runtime` | apply 阶段注册内置 `skill` 工具(执行走 Runtime 统一工具通道,含权限/审批) |
|
|
59
|
+
| `hooks.beforeModelCall` | 每次模型调用前把目录段附加到 system prompt(基准 prompt 每次重建,不累积) |
|
|
60
|
+
|
|
61
|
+
上层显式触发可使用 `expandSkillCommand(text, skills)`(`/skill:name args` → XML 块展开)。
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { BaseExtension, RuntimeHooks, ExtensionContext, Extension, Tool } from '@aipack-ai/agent';
|
|
2
|
+
export { Extension, Tool, ToolResult } from '@aipack-ai/agent';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Skills - 核心类型定义
|
|
6
|
+
*
|
|
7
|
+
* 对齐 Agent Skills 开放规范(SKILL.md + YAML frontmatter),
|
|
8
|
+
* 与 pi / Claude Code 生态的 skill 格式互通。
|
|
9
|
+
*/
|
|
10
|
+
interface Skill {
|
|
11
|
+
/**
|
|
12
|
+
* 唯一名称。规则:^[a-z0-9]+(-[a-z0-9]+)*$,≤64 字符。
|
|
13
|
+
* 文件加载时缺省取 skill 根目录名。
|
|
14
|
+
*/
|
|
15
|
+
name: string;
|
|
16
|
+
/** 必填,≤1024 字符。模型据此判断是否调用(system prompt 目录唯一可见字段)。 */
|
|
17
|
+
description: string;
|
|
18
|
+
/** SKILL.md 正文(不含 frontmatter)。加载器读盘时填入,运行时零 fs 依赖。 */
|
|
19
|
+
content: string;
|
|
20
|
+
/** 来源文件路径(文件加载时填充,供提示中标注资源基准目录) */
|
|
21
|
+
filePath?: string;
|
|
22
|
+
/** 资源解析基准目录(默认 filePath 的 dirname) */
|
|
23
|
+
baseDir?: string;
|
|
24
|
+
/**
|
|
25
|
+
* true 时不进 system prompt 目录、skill 工具拒绝调用,
|
|
26
|
+
* 仅支持显式展开(expandSkillCommand / 手动触发)。
|
|
27
|
+
*/
|
|
28
|
+
disableModelInvocation?: boolean;
|
|
29
|
+
/** 来源标识:'user' | 'project' | 'path' | 'inline'(诊断用) */
|
|
30
|
+
source?: string;
|
|
31
|
+
}
|
|
32
|
+
interface SkillDiagnostic {
|
|
33
|
+
/** error: 校验失败被跳过;warning: 非致命问题;collision: 同名冲突(先注册者胜) */
|
|
34
|
+
type: 'error' | 'warning' | 'collision';
|
|
35
|
+
message: string;
|
|
36
|
+
path?: string;
|
|
37
|
+
}
|
|
38
|
+
interface LoadSkillsOptions {
|
|
39
|
+
/** 工作目录(默认 process.cwd()),项目级 skill 的解析基准 */
|
|
40
|
+
cwd?: string;
|
|
41
|
+
/** 用户级 skill 目录(默认 ~/.aipack/skills),不存在时静默跳过 */
|
|
42
|
+
userDir?: string;
|
|
43
|
+
/** 项目级 skill 目录名(默认 '.aipack/skills',相对 cwd) */
|
|
44
|
+
projectDirName?: string;
|
|
45
|
+
/** 显式补充路径(文件或目录,按声明顺序加载) */
|
|
46
|
+
extraPaths?: string[];
|
|
47
|
+
/** 是否加载默认来源(user + project,默认 true) */
|
|
48
|
+
includeDefaults?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Skills - Runtime 插件(Extension 接入)
|
|
53
|
+
*
|
|
54
|
+
* 通过 @aipack-ai/agent 的 Extension 机制零侵入接入 Runtime:
|
|
55
|
+
* 1. apply 阶段:经 ExtensionContext.runtime 注册内置 skill 工具
|
|
56
|
+
* (模型可调用获取 skill 全文;执行走 Runtime 统一的工具通道)
|
|
57
|
+
* 2. beforeModelCall 钩子:把 <available_skills> 目录段附加到 system prompt
|
|
58
|
+
* (渐进式披露:目录先行,全文按需拉取;基准 prompt 每次重建,不累积)
|
|
59
|
+
*
|
|
60
|
+
* 用法:
|
|
61
|
+
* const runtime = createRuntime({ ..., extensions: [new SkillsExtension(skills)] });
|
|
62
|
+
* // 或一站式(含文件加载):
|
|
63
|
+
* const plugin = createSkillsPlugin({ load: { cwd } });
|
|
64
|
+
* const runtime = createRuntime({ ..., extensions: plugin.extensions });
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
interface SkillsExtensionOptions {
|
|
68
|
+
/** skill 工具名(默认 'skill') */
|
|
69
|
+
toolName?: string;
|
|
70
|
+
/** 是否注册内置 skill 工具(默认 true;关闭后仅注入目录段,全文获取由上层自理) */
|
|
71
|
+
registerTool?: boolean;
|
|
72
|
+
/** 是否注入 <available_skills> 目录段到 system prompt(默认 true) */
|
|
73
|
+
injectPrompt?: boolean;
|
|
74
|
+
}
|
|
75
|
+
declare class SkillsExtension extends BaseExtension {
|
|
76
|
+
readonly name = "skills";
|
|
77
|
+
private skills;
|
|
78
|
+
private options;
|
|
79
|
+
constructor(skills: Skill[], options?: SkillsExtensionOptions);
|
|
80
|
+
protected setup(hooks: RuntimeHooks, context: ExtensionContext): void;
|
|
81
|
+
}
|
|
82
|
+
interface SkillsPluginOptions extends SkillsExtensionOptions {
|
|
83
|
+
/** 程序化提供的 skills(与 load 合并时 inline 优先) */
|
|
84
|
+
skills?: Skill[];
|
|
85
|
+
/** 文件加载选项(提供时调用 loadSkills) */
|
|
86
|
+
load?: LoadSkillsOptions;
|
|
87
|
+
}
|
|
88
|
+
interface SkillsPlugin {
|
|
89
|
+
/** 合并后的全部 skills(含 disableModelInvocation) */
|
|
90
|
+
skills: Skill[];
|
|
91
|
+
/** 加载诊断(error / collision 等),供上层展示 */
|
|
92
|
+
diagnostics: SkillDiagnostic[];
|
|
93
|
+
/** 接入 Runtime 的扩展列表(无可用 skill 时为空数组,零开销) */
|
|
94
|
+
extensions: Extension[];
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 一站式创建 skills 插件:文件加载(可选)+ inline 合并 + Extension 装配。
|
|
98
|
+
*
|
|
99
|
+
* 用法(aipack.config.js / CLI):
|
|
100
|
+
* import { createSkillsPlugin } from '@aipack-ai/skills';
|
|
101
|
+
* const plugin = createSkillsPlugin({ load: { cwd } });
|
|
102
|
+
* export default { ..., extensions: plugin.extensions };
|
|
103
|
+
*/
|
|
104
|
+
declare function createSkillsPlugin(options?: SkillsPluginOptions): SkillsPlugin;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Skills - 契约层(纯函数,零 fs 依赖)
|
|
108
|
+
*
|
|
109
|
+
* 职责:
|
|
110
|
+
* - validateSkill name/description/content 规则校验
|
|
111
|
+
* - formatSkillsSection <available_skills> 目录段渲染(system prompt 注入用)
|
|
112
|
+
* - createSkillTool 内置 skill 工具工厂(模型按需获取 skill 全文)
|
|
113
|
+
* - expandSkillCommand /skill:name 显式展开(CLI 等上层消费)
|
|
114
|
+
*
|
|
115
|
+
* 关键设计:loader 在加载时把正文读入 content 字段,skill 工具直接返回内容,
|
|
116
|
+
* 运行时零 fs 依赖(agent 作为通用框架不假设消费者有 read 工具 / fs 权限)。
|
|
117
|
+
*/
|
|
118
|
+
|
|
119
|
+
/** 校验 skill,返回错误列表(空数组 = 合法) */
|
|
120
|
+
declare function validateSkill(skill: Skill): string[];
|
|
121
|
+
/**
|
|
122
|
+
* 渲染 <available_skills> 目录段(渐进式披露:只注入 name + description)。
|
|
123
|
+
* 无可见 skill 时返回空串(零 token 开销)。
|
|
124
|
+
*/
|
|
125
|
+
declare function formatSkillsSection(skills: Skill[]): string;
|
|
126
|
+
/**
|
|
127
|
+
* 创建内置 skill 工具(工具名固定 'skill')。
|
|
128
|
+
*
|
|
129
|
+
* execute:查注册表 → 命中返回 content(含 baseDir 提示);
|
|
130
|
+
* 未命中 / disableModelInvocation → 错误结果(details.error)。
|
|
131
|
+
* permissions: [](只读注册表,安全工具,不走审批)。
|
|
132
|
+
*/
|
|
133
|
+
declare function createSkillTool(registry: Skill[] | Map<string, Skill>, options?: {
|
|
134
|
+
name?: string;
|
|
135
|
+
}): Tool;
|
|
136
|
+
/**
|
|
137
|
+
* 将文本中的 /skill:name [args] 展开为完整 skill XML 块 + 参数。
|
|
138
|
+
* 命中即替换为 `<skill name="…" location="…">…全文…</skill>`,行内其余参数
|
|
139
|
+
* 以空行分隔保留其后;未命中保持原样(由上层决定是否报错)。
|
|
140
|
+
*/
|
|
141
|
+
declare function expandSkillCommand(text: string, skills: Skill[]): string;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Skills - 文件加载器(Node only)
|
|
145
|
+
*
|
|
146
|
+
* 发现规则(对齐 Agent Skills 开放规范 / pi):
|
|
147
|
+
* - 目录含 SKILL.md → 视为 skill 根,不再递归;name 缺省取目录名
|
|
148
|
+
* - 否则扫描根下直接 .md 子文件;再递归子目录寻找 SKILL.md
|
|
149
|
+
* - 跳过 '.' 开头目录与 node_modules
|
|
150
|
+
* 多源优先级:user → project → extraPaths,同名先注册者胜,败者产出 collision 诊断。
|
|
151
|
+
* 加载失败 / 校验失败只产诊断不中断(健壮性优先)。
|
|
152
|
+
*/
|
|
153
|
+
|
|
154
|
+
interface ParseSkillMarkdownOptions {
|
|
155
|
+
/** frontmatter name 缺省时的回退名(目录名 / 文件名) */
|
|
156
|
+
fallbackName?: string;
|
|
157
|
+
/** 来源文件路径(填入 skill.filePath,baseDir 取 dirname) */
|
|
158
|
+
filePath?: string;
|
|
159
|
+
/** 来源标识(诊断用) */
|
|
160
|
+
source?: string;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 解析 SKILL.md 文本为 Skill。
|
|
164
|
+
* frontmatter 支持顶层 key: value(name / description / disable-model-invocation),
|
|
165
|
+
* 值可带单双引号;其余 key 忽略。缺失 name 用 fallbackName;缺失 description 报错。
|
|
166
|
+
*/
|
|
167
|
+
declare function parseSkillMarkdown(text: string, options?: ParseSkillMarkdownOptions): {
|
|
168
|
+
skill?: Skill;
|
|
169
|
+
errors: string[];
|
|
170
|
+
};
|
|
171
|
+
/**
|
|
172
|
+
* 加载 skills:user → project → extraPaths,同名先注册者胜(user 优先)。
|
|
173
|
+
* 缺省来源:userDir = ~/.aipack/skills,project = <cwd>/.aipack/skills。
|
|
174
|
+
* 目录不存在静默跳过;校验失败 / 同名冲突产出诊断,不中断。
|
|
175
|
+
*/
|
|
176
|
+
declare function loadSkills(options?: LoadSkillsOptions): {
|
|
177
|
+
skills: Skill[];
|
|
178
|
+
diagnostics: SkillDiagnostic[];
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export { type LoadSkillsOptions, type ParseSkillMarkdownOptions, type Skill, type SkillDiagnostic, SkillsExtension, type SkillsExtensionOptions, type SkillsPlugin, type SkillsPluginOptions, createSkillTool, createSkillsPlugin, expandSkillCommand, formatSkillsSection, loadSkills, parseSkillMarkdown, validateSkill };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
// ../agent/core/types.ts
|
|
2
|
+
function createTextContent(text) {
|
|
3
|
+
return { type: "text", text };
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// ../agent/core/extension.ts
|
|
7
|
+
var BaseExtension = class {
|
|
8
|
+
apply(hooks, context) {
|
|
9
|
+
this.setup(hooks, context);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/core.ts
|
|
14
|
+
var SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
15
|
+
function validateSkill(skill) {
|
|
16
|
+
const errors = [];
|
|
17
|
+
if (!skill.name || typeof skill.name !== "string") {
|
|
18
|
+
errors.push("name \u7F3A\u5931");
|
|
19
|
+
} else {
|
|
20
|
+
if (skill.name.length > 64) errors.push(`name \u8D85\u8FC7 64 \u5B57\u7B26\uFF08${skill.name.length}\uFF09`);
|
|
21
|
+
if (!SKILL_NAME_RE.test(skill.name)) {
|
|
22
|
+
errors.push(`name "${skill.name}" \u4E0D\u5408\u6CD5\uFF08\u9700\u6EE1\u8DB3 ^[a-z0-9]+(-[a-z0-9]+)*$\uFF09`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (!skill.description || typeof skill.description !== "string") {
|
|
26
|
+
errors.push("description \u7F3A\u5931");
|
|
27
|
+
} else if (skill.description.length > 1024) {
|
|
28
|
+
errors.push(`description \u8D85\u8FC7 1024 \u5B57\u7B26\uFF08${skill.description.length}\uFF09`);
|
|
29
|
+
}
|
|
30
|
+
if (typeof skill.content !== "string" || !skill.content.trim()) {
|
|
31
|
+
errors.push("content \u7F3A\u5931\uFF08SKILL.md \u6B63\u6587\uFF09");
|
|
32
|
+
}
|
|
33
|
+
return errors;
|
|
34
|
+
}
|
|
35
|
+
function formatSkillsSection(skills) {
|
|
36
|
+
const visible = skills.filter((s) => !s.disableModelInvocation);
|
|
37
|
+
if (visible.length === 0) return "";
|
|
38
|
+
const entries = visible.map(
|
|
39
|
+
(s) => ` <skill>
|
|
40
|
+
<name>${s.name}</name>
|
|
41
|
+
<description>${s.description}</description>
|
|
42
|
+
</skill>`
|
|
43
|
+
).join("\n");
|
|
44
|
+
return [
|
|
45
|
+
"The following skills provide specialized instructions for specific tasks.",
|
|
46
|
+
"Use the skill tool to load a skill when the task matches its description.",
|
|
47
|
+
"When a skill references relative paths, resolve them against its baseDir.",
|
|
48
|
+
"",
|
|
49
|
+
"<available_skills>",
|
|
50
|
+
entries,
|
|
51
|
+
"</available_skills>"
|
|
52
|
+
].join("\n");
|
|
53
|
+
}
|
|
54
|
+
function toRegistry(skills) {
|
|
55
|
+
if (skills instanceof Map) return skills;
|
|
56
|
+
const map = /* @__PURE__ */ new Map();
|
|
57
|
+
for (const s of skills) {
|
|
58
|
+
if (!map.has(s.name)) map.set(s.name, s);
|
|
59
|
+
}
|
|
60
|
+
return map;
|
|
61
|
+
}
|
|
62
|
+
function createSkillTool(registry, options) {
|
|
63
|
+
const map = toRegistry(registry);
|
|
64
|
+
return {
|
|
65
|
+
name: options?.name ?? "skill",
|
|
66
|
+
description: "Load the full instructions of a skill by name. Use when the task matches a skill listed in <available_skills>.",
|
|
67
|
+
parameters: {
|
|
68
|
+
type: "object",
|
|
69
|
+
properties: {
|
|
70
|
+
name: { type: "string", description: "\u8981\u52A0\u8F7D\u7684 skill \u540D\u79F0" }
|
|
71
|
+
},
|
|
72
|
+
required: ["name"]
|
|
73
|
+
},
|
|
74
|
+
permissions: [],
|
|
75
|
+
async execute(_toolCallId, args) {
|
|
76
|
+
const name = typeof args?.name === "string" ? args.name : "";
|
|
77
|
+
if (!name) {
|
|
78
|
+
return errorResult("skill \u5DE5\u5177\u7F3A\u5C11\u53C2\u6570 name");
|
|
79
|
+
}
|
|
80
|
+
const skill = map.get(name);
|
|
81
|
+
if (!skill) {
|
|
82
|
+
const known = Array.from(map.keys()).join(", ");
|
|
83
|
+
return errorResult(
|
|
84
|
+
known ? `\u672A\u627E\u5230 skill "${name}"\uFF08\u53EF\u7528: ${known}\uFF09` : `\u672A\u627E\u5230 skill "${name}"`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
if (skill.disableModelInvocation) {
|
|
88
|
+
return errorResult(`skill "${name}" \u7981\u6B62\u6A21\u578B\u8C03\u7528\uFF08disable-model-invocation\uFF09`);
|
|
89
|
+
}
|
|
90
|
+
const parts = [skill.content];
|
|
91
|
+
if (skill.baseDir) {
|
|
92
|
+
parts.push(`References are relative to: ${skill.baseDir}`);
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
content: [createTextContent(parts.join("\n\n"))],
|
|
96
|
+
details: { skill: skill.name }
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function errorResult(message) {
|
|
102
|
+
return {
|
|
103
|
+
content: [createTextContent(message)],
|
|
104
|
+
details: { error: message }
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function expandSkillCommand(text, skills) {
|
|
108
|
+
const map = toRegistry(skills);
|
|
109
|
+
return text.replace(
|
|
110
|
+
/\/skill:([a-z0-9-]+)(?:[ \t]+([^\n]*))?/g,
|
|
111
|
+
(match, name, args) => {
|
|
112
|
+
const skill = map.get(name);
|
|
113
|
+
if (!skill) return match;
|
|
114
|
+
const location = skill.filePath ? ` location="${skill.filePath}"` : "";
|
|
115
|
+
const xml = `<skill name="${name}"${location}>
|
|
116
|
+
${skill.content}
|
|
117
|
+
</skill>`;
|
|
118
|
+
return args ? `${xml}
|
|
119
|
+
|
|
120
|
+
${args}` : xml;
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/loader.ts
|
|
126
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
|
127
|
+
import { homedir } from "os";
|
|
128
|
+
import { basename, join } from "path";
|
|
129
|
+
function parseSkillMarkdown(text, options = {}) {
|
|
130
|
+
const errors = [];
|
|
131
|
+
const normalized = text.replace(/^\uFEFF/, "");
|
|
132
|
+
const lines = normalized.split(/\r?\n/);
|
|
133
|
+
const frontmatter = extractFrontmatter(lines);
|
|
134
|
+
const content = frontmatter ? frontmatter.rest.join("\n").trim() : normalized.trim();
|
|
135
|
+
const name = frontmatter?.fields.get("name") ?? options.fallbackName;
|
|
136
|
+
const description = frontmatter?.fields.get("description");
|
|
137
|
+
const disableRaw = frontmatter?.fields.get("disable-model-invocation");
|
|
138
|
+
if (!name) errors.push("name \u7F3A\u5931\uFF08frontmatter \u6216\u76EE\u5F55\u540D\u5747\u672A\u63D0\u4F9B\uFF09");
|
|
139
|
+
if (!description) errors.push("description \u7F3A\u5931\uFF08frontmatter\uFF09");
|
|
140
|
+
const skill = {
|
|
141
|
+
name: name ?? "",
|
|
142
|
+
description: description ?? "",
|
|
143
|
+
content,
|
|
144
|
+
disableModelInvocation: disableRaw === "true",
|
|
145
|
+
source: options.source ?? "path"
|
|
146
|
+
};
|
|
147
|
+
if (options.filePath) {
|
|
148
|
+
skill.filePath = options.filePath;
|
|
149
|
+
skill.baseDir = dirnameOf(options.filePath);
|
|
150
|
+
}
|
|
151
|
+
return { skill, errors };
|
|
152
|
+
}
|
|
153
|
+
function extractFrontmatter(lines) {
|
|
154
|
+
if ((lines[0] ?? "").trim() !== "---") return null;
|
|
155
|
+
const fields = /* @__PURE__ */ new Map();
|
|
156
|
+
let i = 1;
|
|
157
|
+
for (; i < lines.length; i++) {
|
|
158
|
+
const line = lines[i];
|
|
159
|
+
if (line.trim() === "---") break;
|
|
160
|
+
const idx = line.indexOf(":");
|
|
161
|
+
if (idx <= 0) continue;
|
|
162
|
+
const key = line.slice(0, idx).trim();
|
|
163
|
+
const value = stripQuotes(line.slice(idx + 1).trim());
|
|
164
|
+
if (key) fields.set(key, value);
|
|
165
|
+
}
|
|
166
|
+
if (i >= lines.length) return null;
|
|
167
|
+
return { fields, rest: lines.slice(i + 1) };
|
|
168
|
+
}
|
|
169
|
+
function stripQuotes(value) {
|
|
170
|
+
if (value.length >= 2) {
|
|
171
|
+
const first = value[0];
|
|
172
|
+
if ((first === '"' || first === "'") && value[value.length - 1] === first) {
|
|
173
|
+
return value.slice(1, -1);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return value;
|
|
177
|
+
}
|
|
178
|
+
function dirnameOf(filePath) {
|
|
179
|
+
const idx = Math.max(filePath.lastIndexOf("/"), filePath.lastIndexOf("\\"));
|
|
180
|
+
return idx > 0 ? filePath.slice(0, idx) : filePath;
|
|
181
|
+
}
|
|
182
|
+
function collectFromDir(dir, source, diagnostics) {
|
|
183
|
+
const skillMdPath = join(dir, "SKILL.md");
|
|
184
|
+
if (existsSync(skillMdPath)) {
|
|
185
|
+
const { skill, errors } = parseSkillMarkdown(
|
|
186
|
+
readText(skillMdPath),
|
|
187
|
+
{ fallbackName: basename(dir), filePath: skillMdPath, source }
|
|
188
|
+
);
|
|
189
|
+
if (skill) return [skill];
|
|
190
|
+
diagnostics.push({ type: "error", message: errors.join("; "), path: skillMdPath });
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
const skills = [];
|
|
194
|
+
let entries;
|
|
195
|
+
try {
|
|
196
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
197
|
+
} catch (err) {
|
|
198
|
+
diagnostics.push({
|
|
199
|
+
type: "warning",
|
|
200
|
+
message: `\u76EE\u5F55\u8BFB\u53D6\u5931\u8D25: ${err?.message ?? err}`,
|
|
201
|
+
path: dir
|
|
202
|
+
});
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
for (const entry of entries) {
|
|
206
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
207
|
+
const full = join(dir, entry.name);
|
|
208
|
+
if (entry.isDirectory()) {
|
|
209
|
+
skills.push(...collectFromDir(full, source, diagnostics));
|
|
210
|
+
} else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md") && entry.name !== "SKILL.md") {
|
|
211
|
+
const { skill, errors } = parseSkillMarkdown(
|
|
212
|
+
readText(full),
|
|
213
|
+
{ fallbackName: basename(entry.name, ".md"), filePath: full, source }
|
|
214
|
+
);
|
|
215
|
+
if (skill) skills.push(skill);
|
|
216
|
+
else diagnostics.push({ type: "error", message: errors.join("; "), path: full });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return skills;
|
|
220
|
+
}
|
|
221
|
+
function readText(path) {
|
|
222
|
+
try {
|
|
223
|
+
return readFileSync(path, "utf8");
|
|
224
|
+
} catch {
|
|
225
|
+
return "";
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function loadSkills(options = {}) {
|
|
229
|
+
const cwd = options.cwd ?? process.cwd();
|
|
230
|
+
const diagnostics = [];
|
|
231
|
+
const byName = /* @__PURE__ */ new Map();
|
|
232
|
+
const skills = [];
|
|
233
|
+
const register = (skill, path) => {
|
|
234
|
+
if (!skill) return;
|
|
235
|
+
const errors = validateSkill(skill);
|
|
236
|
+
if (errors.length > 0) {
|
|
237
|
+
diagnostics.push({ type: "error", message: errors.join("; "), path });
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const existing = byName.get(skill.name);
|
|
241
|
+
if (existing) {
|
|
242
|
+
diagnostics.push({
|
|
243
|
+
type: "collision",
|
|
244
|
+
message: `skill "${skill.name}" \u5DF2\u6CE8\u518C\uFF08${existing.filePath ?? "inline"}\uFF09\uFF0C\u5FFD\u7565\u540E\u6CE8\u518C\u8005`,
|
|
245
|
+
path
|
|
246
|
+
});
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
byName.set(skill.name, skill);
|
|
250
|
+
skills.push(skill);
|
|
251
|
+
};
|
|
252
|
+
const loadDir = (dir, source) => {
|
|
253
|
+
if (!dir || !existsSync(dir)) return;
|
|
254
|
+
for (const skill of collectFromDir(dir, source, diagnostics)) {
|
|
255
|
+
register(skill, skill.filePath ?? dir);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
const loadFile = (file, source) => {
|
|
259
|
+
if (!file || !existsSync(file)) return;
|
|
260
|
+
const { skill, errors } = parseSkillMarkdown(readText(file), {
|
|
261
|
+
fallbackName: basename(file, ".md"),
|
|
262
|
+
filePath: file,
|
|
263
|
+
source
|
|
264
|
+
});
|
|
265
|
+
register(skill, file);
|
|
266
|
+
if (errors.length > 0) {
|
|
267
|
+
diagnostics.push({ type: "error", message: errors.join("; "), path: file });
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
const userDir = options.userDir ?? join(homedir(), ".aipack", "skills");
|
|
271
|
+
const projectDir = join(cwd, options.projectDirName ?? join(".aipack", "skills"));
|
|
272
|
+
if (options.includeDefaults !== false) {
|
|
273
|
+
loadDir(userDir, "user");
|
|
274
|
+
loadDir(projectDir, "project");
|
|
275
|
+
} else {
|
|
276
|
+
if (options.userDir) loadDir(userDir, "user");
|
|
277
|
+
if (options.projectDirName) loadDir(projectDir, "project");
|
|
278
|
+
}
|
|
279
|
+
for (const p of options.extraPaths ?? []) {
|
|
280
|
+
if (!p || !existsSync(p)) {
|
|
281
|
+
diagnostics.push({ type: "warning", message: `extraPath \u4E0D\u5B58\u5728\uFF0C\u5DF2\u8DF3\u8FC7`, path: p });
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
let isDir = false;
|
|
285
|
+
try {
|
|
286
|
+
isDir = statSync(p).isDirectory();
|
|
287
|
+
} catch {
|
|
288
|
+
isDir = false;
|
|
289
|
+
}
|
|
290
|
+
if (isDir) loadDir(p, "path");
|
|
291
|
+
else loadFile(p, "path");
|
|
292
|
+
}
|
|
293
|
+
return { skills, diagnostics };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/extension.ts
|
|
297
|
+
var SkillsExtension = class extends BaseExtension {
|
|
298
|
+
name = "skills";
|
|
299
|
+
skills;
|
|
300
|
+
options;
|
|
301
|
+
constructor(skills, options = {}) {
|
|
302
|
+
super();
|
|
303
|
+
this.skills = skills;
|
|
304
|
+
this.options = options;
|
|
305
|
+
}
|
|
306
|
+
setup(hooks, context) {
|
|
307
|
+
const valid = [];
|
|
308
|
+
for (const skill of this.skills) {
|
|
309
|
+
const errors = validateSkill(skill);
|
|
310
|
+
if (errors.length > 0) {
|
|
311
|
+
console.warn(`[skills] "${skill.name}" \u6821\u9A8C\u5931\u8D25\uFF0C\u5DF2\u5FFD\u7565: ${errors.join("; ")}`);
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
valid.push(skill);
|
|
315
|
+
}
|
|
316
|
+
if (this.options.registerTool !== false && valid.length > 0) {
|
|
317
|
+
if (!context.runtime) {
|
|
318
|
+
console.warn("[skills] ExtensionContext.runtime \u4E0D\u53EF\u7528\uFF0C\u8DF3\u8FC7 skill \u5DE5\u5177\u6CE8\u518C");
|
|
319
|
+
} else {
|
|
320
|
+
context.runtime.registerTool(
|
|
321
|
+
createSkillTool(valid, { name: this.options.toolName })
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const visible = valid.filter((s) => !s.disableModelInvocation);
|
|
326
|
+
if (this.options.injectPrompt !== false && visible.length > 0) {
|
|
327
|
+
const section = formatSkillsSection(visible);
|
|
328
|
+
hooks.beforeModelCall.tapPromise("skills", async (ctx) => ({
|
|
329
|
+
...ctx,
|
|
330
|
+
systemPrompt: ctx.systemPrompt ? `${ctx.systemPrompt}
|
|
331
|
+
|
|
332
|
+
${section}` : section
|
|
333
|
+
}));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
function createSkillsPlugin(options = {}) {
|
|
338
|
+
const loaded = options.load ? loadSkills(options.load) : { skills: [], diagnostics: [] };
|
|
339
|
+
const skills = [...options.skills ?? [], ...loaded.skills];
|
|
340
|
+
const diagnostics = [...loaded.diagnostics];
|
|
341
|
+
const extensions = skills.length > 0 ? [new SkillsExtension(skills, options)] : [];
|
|
342
|
+
return { skills, diagnostics, extensions };
|
|
343
|
+
}
|
|
344
|
+
export {
|
|
345
|
+
SkillsExtension,
|
|
346
|
+
createSkillTool,
|
|
347
|
+
createSkillsPlugin,
|
|
348
|
+
expandSkillCommand,
|
|
349
|
+
formatSkillsSection,
|
|
350
|
+
loadSkills,
|
|
351
|
+
parseSkillMarkdown,
|
|
352
|
+
validateSkill
|
|
353
|
+
};
|
|
354
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../agent/core/types.ts","../../agent/core/extension.ts","../src/core.ts","../src/loader.ts","../src/extension.ts"],"sourcesContent":["/**\n * packages/core/types.ts - 核心类型定义\n *\n * 这是整个框架的类型基础,不依赖任何外部实现。\n * 所有消息、内容块、模型、工具等类型都在此定义。\n */\n\n// ─── 内容块类型 ───────────────────────────────────────────────────\n\nexport interface TextContent {\n type: 'text';\n text: string;\n}\n\nexport interface ImageContent {\n type: 'image';\n data: string; // base64 或 URL\n mimeType: string;\n}\n\nexport interface ToolCallContent {\n type: 'toolCall';\n id: string;\n name: string;\n arguments: unknown;\n}\n\nexport interface ThinkingContent {\n type: 'thinking';\n text: string;\n}\n\nexport type ContentBlock =\n | TextContent\n | ImageContent\n | ToolCallContent\n | ThinkingContent;\n\n// ─── 消息类型 ─────────────────────────────────────────────────────\n\nexport interface BaseMessage {\n role: string;\n content: string | ContentBlock[];\n timestamp: number;\n [key: string]: unknown;\n}\n\nexport interface UserMessage extends BaseMessage {\n role: 'user';\n}\n\nexport interface AssistantMessage extends BaseMessage {\n role: 'assistant';\n model?: string;\n provider?: string;\n stopReason?: string;\n usage?: Usage;\n errorMessage?: string;\n responseId?: string;\n}\n\nexport interface ToolResultMessage extends BaseMessage {\n role: 'toolResult';\n toolCallId: string;\n toolName: string;\n isError: boolean;\n}\n\nexport interface SystemMessage extends BaseMessage {\n role: 'system';\n}\n\nexport type Message = UserMessage | AssistantMessage | ToolResultMessage | SystemMessage;\n\n// ─── Token 用量 ───────────────────────────────────────────────────\n\n/**\n * 用量与费用。与 aipack/ai 的 Usage 结构对齐:\n * cacheRead/cacheWrite/reasoning 为可选字段,旧消费者无需感知。\n */\nexport interface Usage {\n input: number;\n output: number;\n total: number;\n /** 命中缓存读取的 token 数(不计入 input) */\n cacheRead?: number;\n /** 写入缓存的 token 数 */\n cacheWrite?: number;\n /** 推理 token 数(completion 的子集) */\n reasoning?: number;\n /** 还原后的原始 token 总数(input + output + cacheRead + cacheWrite) */\n totalTokens?: number;\n}\n\n// ─── 模型定义 ─────────────────────────────────────────────────────\n\nexport interface Model {\n id: string;\n name: string;\n provider: string;\n contextWindow: number;\n maxTokens: number;\n reasoning: boolean;\n [key: string]: unknown;\n}\n\n// ─── 工具定义 ─────────────────────────────────────────────────────\n\nexport interface ToolResult {\n content: ContentBlock[];\n details: unknown;\n usage?: Usage;\n}\n\nexport interface Tool {\n name: string;\n description: string;\n parameters: unknown; // JSON Schema 或 TypeBox schema\n /**\n * 所需权限能力声明(如 'shell:exec' / 'fs:write' / 'memory:write' / 'network:fetch')。\n * 供框架级 PermissionPolicy 裁决使用;未声明视为安全工具。\n */\n permissions?: string[];\n execute: (\n toolCallId: string,\n args: unknown,\n signal?: AbortSignal,\n ) => Promise<ToolResult>;\n prepareArguments?: (args: unknown) => unknown;\n}\n\n// ─── 上下文 ───────────────────────────────────────────────────────\n\nexport interface Context {\n systemPrompt: string;\n messages: Message[];\n tools?: Tool[];\n}\n\n// ─── 流式事件 ─────────────────────────────────────────────────────\n\nexport interface StreamOptions {\n signal?: AbortSignal;\n reasoning?: string;\n sessionId?: string;\n /** 重试回调:provider 内 retry() 真正退避重试时调用(可观测/埋点用) */\n onRetryAttempt?: (info: { attempt: number; error: unknown; delayMs: number }) => void;\n}\n\nexport type StreamEvent =\n | { type: 'start'; partial: { content: ContentBlock[] } }\n | { type: 'text_delta'; delta: string }\n | { type: 'thinking_delta'; delta: string }\n | { type: 'tool_call_start'; id: string; name: string }\n | { type: 'tool_call_delta'; id: string; delta: string }\n | { type: 'tool_call_end'; id: string }\n | { type: 'done'; message: AssistantMessage }\n | { type: 'error'; message: AssistantMessage };\n\nexport type StreamResult = AsyncIterable<StreamEvent>;\n\n/**\n * 流式函数类型 - 模型提供者需实现此接口\n *\n * 是一个可替换的核心能力。\n * 用户通过提供不同的 streamFn 来接入不同的 LLM 提供商。\n */\nexport type StreamFn = (\n model: Model,\n context: Context,\n options?: StreamOptions,\n) => StreamResult;\n\n// ─── Agent 状态 ───────────────────────────────────────────────────\n\nexport type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'max';\n\nexport interface AgentState {\n systemPrompt: string;\n model: Model;\n thinkingLevel: ThinkingLevel;\n tools: Tool[];\n messages: Message[];\n isStreaming: boolean;\n streamingMessage?: Message;\n pendingToolCalls: Set<string>;\n errorMessage?: string;\n}\n\n// ─── 工具函数 ─────────────────────────────────────────────────────\n\nexport function extractText(content: string | ContentBlock[]): string {\n if (typeof content === 'string') return content;\n return content\n .filter((c): c is TextContent => c.type === 'text')\n .map(c => c.text)\n .join('');\n}\n\nexport function extractToolCalls(content: string | ContentBlock[]): ToolCallContent[] {\n if (typeof content === 'string') return [];\n return content.filter((c): c is ToolCallContent => c.type === 'toolCall');\n}\n\nexport function createTextContent(text: string): TextContent {\n return { type: 'text', text };\n}\n\nexport function createEmptyUsage(): Usage {\n return { input: 0, output: 0, total: 0 };\n}\n","/**\n * Extension - 扩展插件系统\n *\n * Extension 通过 Tapable 钩子在 Runtime 生命周期的关键节点注入逻辑,\n * 无需修改 Runtime 核心,实现开闭原则。\n */\n\nimport { AsyncSeriesHook, AsyncSeriesWaterfallHook } from './tapable';\nimport type { Request } from './request';\nimport type { Result } from './result';\nimport type { Context } from './types';\nimport type { ContextResource } from './context-resource';\nimport type { BeforeToolCallDecision, AfterToolCallDecision } from './tool-hooks';\nimport type { Runtime } from './runtime';\n\n// ─── Runtime 钩子集合 ─────────────────────────────────────────────\n\nexport interface RuntimeHooks {\n /** Runtime 初始化前 */\n beforeInitialize: AsyncSeriesHook<[Request]>;\n /** Runtime 初始化后 */\n afterInitialize: AsyncSeriesHook<[Request]>;\n /** 请求处理前(可修改请求) */\n beforeRun: AsyncSeriesWaterfallHook<Request>;\n /** 资源构建后、转换前(可修改资源) */\n beforeTransform: AsyncSeriesWaterfallHook<ContextResource[]>;\n /**\n * 资源转换后、模型调用前(可修改资源)\n */\n afterTransform: AsyncSeriesWaterfallHook<ContextResource[]>;\n /**\n * 模型调用前(waterfall,可修改最终发给模型的 Context:\n * systemPrompt / messages / tools)。\n * 典型用途:插件注入 system prompt 片段或动态附加工具(如 skills 目录注入)。\n * 每次模型调用触发一次;systemPrompt 以 Runtime 持有的基准值为起点,改动不累积。\n */\n beforeModelCall: AsyncSeriesWaterfallHook<Context>;\n /** 模型调用后、结果构建前 */\n beforeEmit: AsyncSeriesHook<[Result]>;\n /** 结果构建后 */\n afterEmit: AsyncSeriesHook<[Result]>;\n /**\n * 运行结束。\n * 第二参数为最终 Request(可选,向后兼容)。\n * 单会话模式下会话标识由 Runtime 持有;多会话场景请创建独立 Runtime 实例。\n */\n done: AsyncSeriesHook<[Result, Request?]>;\n /** 运行失败 */\n failed: AsyncSeriesHook<[Error, Request]>;\n /**\n * 工具执行前(参数校验后)。流转 BeforeToolCallDecision:\n * 任一 tap 设 block 则该工具不执行(生成拒绝结果);\n * 设 terminate 则终止整个 run(本轮工具结束后停止 runLoop);\n * 可改写 args。第二参数为 ToolCallContext。\n */\n beforeToolCall: AsyncSeriesWaterfallHook<BeforeToolCallDecision>;\n /**\n * 工具执行后、最终事件发出前。流转 AfterToolCallDecision:\n * 可改写 result(waterfall),设 terminate 终止整个 run。\n * 第二参数为 ToolCallContext。\n */\n afterToolCall: AsyncSeriesWaterfallHook<AfterToolCallDecision>;\n}\n\n// ─── Extension 接口 ───────────────────────────────────────────────\n\nexport interface Extension {\n /** 扩展名称 */\n readonly name: string;\n /**\n * 应用扩展到 Runtime\n * 通过 hooks.tap 注册回调\n */\n apply(hooks: RuntimeHooks, context: ExtensionContext): void;\n}\n\n// ─── 扩展上下文 ───────────────────────────────────────────────────\n\nexport interface ExtensionContext {\n /** Runtime 配置 */\n readonly config: Record<string, unknown>;\n /** 工作区路径 */\n readonly workspace: string;\n /** 会话标识 */\n readonly sessionKey: string;\n /** 共享状态(Extension 间通信) */\n readonly shared: Map<string, unknown>;\n /**\n * 所属 Runtime 引用(可选)。\n * 供插件在 apply 阶段注册工具等(如 SkillsExtension 注册内置 skill 工具)。\n * 由 AgentRuntime.create 注入;自建 ExtensionContext 时可缺省。\n */\n readonly runtime?: Runtime;\n}\n\n// ─── Extension 管理器 ─────────────────────────────────────────────\n\nexport class ExtensionManager {\n private extensions: Extension[] = [];\n private hooks: RuntimeHooks;\n\n constructor() {\n this.hooks = this.createHooks();\n }\n\n private createHooks(): RuntimeHooks {\n return {\n beforeInitialize: new AsyncSeriesHook('beforeInitialize'),\n afterInitialize: new AsyncSeriesHook('afterInitialize'),\n beforeRun: new AsyncSeriesWaterfallHook('beforeRun'),\n beforeTransform: new AsyncSeriesWaterfallHook('beforeTransform'),\n afterTransform: new AsyncSeriesWaterfallHook('afterTransform'),\n beforeModelCall: new AsyncSeriesWaterfallHook('beforeModelCall'),\n beforeEmit: new AsyncSeriesHook('beforeEmit'),\n afterEmit: new AsyncSeriesHook('afterEmit'),\n done: new AsyncSeriesHook('done'),\n failed: new AsyncSeriesHook('failed'),\n beforeToolCall: new AsyncSeriesWaterfallHook('beforeToolCall'),\n afterToolCall: new AsyncSeriesWaterfallHook('afterToolCall'),\n };\n }\n\n /** 注册扩展 */\n register(extension: Extension): this {\n this.extensions.push(extension);\n return this;\n }\n\n /** 批量注册扩展 */\n registerAll(extensions: Extension[]): this {\n for (const ext of extensions) {\n this.register(ext);\n }\n return this;\n }\n\n /** 应用所有扩展到钩子 */\n applyAll(context: ExtensionContext): void {\n for (const ext of this.extensions) {\n try {\n ext.apply(this.hooks, context);\n } catch (err) {\n // 单个 Extension 失败不影响其他 Extension,但需可观测\n console.warn(`[Extension] \"${ext.name}\" apply 失败:`, (err as Error)?.message ?? err);\n }\n }\n }\n\n /** 获取钩子集合 */\n getHooks(): RuntimeHooks {\n return this.hooks;\n }\n\n /** 移除扩展 */\n unregister(name: string): boolean {\n const idx = this.extensions.findIndex(e => e.name === name);\n if (idx === -1) return false;\n this.extensions.splice(idx, 1);\n return true;\n }\n\n /** 获取所有已注册的扩展 */\n getExtensions(): Extension[] {\n return [...this.extensions];\n }\n\n /** 清空所有扩展 */\n clear(): void {\n this.extensions = [];\n }\n}\n\n// ─── 抽象基类 ─────────────────────────────────────────────────────\n\nexport abstract class BaseExtension implements Extension {\n abstract readonly name: string;\n\n apply(hooks: RuntimeHooks, context: ExtensionContext): void {\n this.setup(hooks, context);\n }\n\n protected abstract setup(hooks: RuntimeHooks, context: ExtensionContext): void;\n}\n","/**\n * Skills - 契约层(纯函数,零 fs 依赖)\n *\n * 职责:\n * - validateSkill name/description/content 规则校验\n * - formatSkillsSection <available_skills> 目录段渲染(system prompt 注入用)\n * - createSkillTool 内置 skill 工具工厂(模型按需获取 skill 全文)\n * - expandSkillCommand /skill:name 显式展开(CLI 等上层消费)\n *\n * 关键设计:loader 在加载时把正文读入 content 字段,skill 工具直接返回内容,\n * 运行时零 fs 依赖(agent 作为通用框架不假设消费者有 read 工具 / fs 权限)。\n */\n\nimport type { Tool, ToolResult } from '@aipack-ai/agent';\nimport { createTextContent } from '@aipack-ai/agent';\nimport type { Skill } from './types';\n\n// ─── 校验 ─────────────────────────────────────────────────────────\n\n/** name 规则:小写字母/数字,单连字符分隔,无首尾/连续连字符 */\nconst SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\n/** 校验 skill,返回错误列表(空数组 = 合法) */\nexport function validateSkill(skill: Skill): string[] {\n const errors: string[] = [];\n\n if (!skill.name || typeof skill.name !== 'string') {\n errors.push('name 缺失');\n } else {\n if (skill.name.length > 64) errors.push(`name 超过 64 字符(${skill.name.length})`);\n if (!SKILL_NAME_RE.test(skill.name)) {\n errors.push(`name \"${skill.name}\" 不合法(需满足 ^[a-z0-9]+(-[a-z0-9]+)*$)`);\n }\n }\n\n if (!skill.description || typeof skill.description !== 'string') {\n errors.push('description 缺失');\n } else if (skill.description.length > 1024) {\n errors.push(`description 超过 1024 字符(${skill.description.length})`);\n }\n\n if (typeof skill.content !== 'string' || !skill.content.trim()) {\n errors.push('content 缺失(SKILL.md 正文)');\n }\n\n return errors;\n}\n\n// ─── system prompt 目录段 ──────────────────────────────────────────\n\n/**\n * 渲染 <available_skills> 目录段(渐进式披露:只注入 name + description)。\n * 无可见 skill 时返回空串(零 token 开销)。\n */\nexport function formatSkillsSection(skills: Skill[]): string {\n const visible = skills.filter(s => !s.disableModelInvocation);\n if (visible.length === 0) return '';\n\n const entries = visible\n .map(\n s =>\n ` <skill>\\n <name>${s.name}</name>\\n <description>${s.description}</description>\\n </skill>`,\n )\n .join('\\n');\n\n return [\n 'The following skills provide specialized instructions for specific tasks.',\n 'Use the skill tool to load a skill when the task matches its description.',\n 'When a skill references relative paths, resolve them against its baseDir.',\n '',\n '<available_skills>',\n entries,\n '</available_skills>',\n ].join('\\n');\n}\n\n// ─── 内置 skill 工具 ───────────────────────────────────────────────\n\n/** 归一化注册表:数组转 Map,同名先注册者胜(对齐 loader 多源优先级语义) */\nfunction toRegistry(skills: Skill[] | Map<string, Skill>): Map<string, Skill> {\n if (skills instanceof Map) return skills;\n const map = new Map<string, Skill>();\n for (const s of skills) {\n if (!map.has(s.name)) map.set(s.name, s);\n }\n return map;\n}\n\n/**\n * 创建内置 skill 工具(工具名固定 'skill')。\n *\n * execute:查注册表 → 命中返回 content(含 baseDir 提示);\n * 未命中 / disableModelInvocation → 错误结果(details.error)。\n * permissions: [](只读注册表,安全工具,不走审批)。\n */\nexport function createSkillTool(\n registry: Skill[] | Map<string, Skill>,\n options?: { name?: string },\n): Tool {\n const map = toRegistry(registry);\n return {\n name: options?.name ?? 'skill',\n description:\n 'Load the full instructions of a skill by name. ' +\n 'Use when the task matches a skill listed in <available_skills>.',\n parameters: {\n type: 'object',\n properties: {\n name: { type: 'string', description: '要加载的 skill 名称' },\n },\n required: ['name'],\n },\n permissions: [],\n async execute(_toolCallId, args): Promise<ToolResult> {\n const name =\n typeof (args as { name?: unknown })?.name === 'string'\n ? (args as { name: string }).name\n : '';\n if (!name) {\n return errorResult('skill 工具缺少参数 name');\n }\n const skill = map.get(name);\n if (!skill) {\n const known = Array.from(map.keys()).join(', ');\n return errorResult(\n known ? `未找到 skill \"${name}\"(可用: ${known})` : `未找到 skill \"${name}\"`,\n );\n }\n if (skill.disableModelInvocation) {\n return errorResult(`skill \"${name}\" 禁止模型调用(disable-model-invocation)`);\n }\n const parts = [skill.content];\n if (skill.baseDir) {\n parts.push(`References are relative to: ${skill.baseDir}`);\n }\n return {\n content: [createTextContent(parts.join('\\n\\n'))],\n details: { skill: skill.name },\n };\n },\n };\n}\n\nfunction errorResult(message: string): ToolResult {\n return {\n content: [createTextContent(message)],\n details: { error: message },\n };\n}\n\n// ─── 显式展开(CLI /skill:name 用)────────────────────────────────\n\n/**\n * 将文本中的 /skill:name [args] 展开为完整 skill XML 块 + 参数。\n * 命中即替换为 `<skill name=\"…\" location=\"…\">…全文…</skill>`,行内其余参数\n * 以空行分隔保留其后;未命中保持原样(由上层决定是否报错)。\n */\nexport function expandSkillCommand(text: string, skills: Skill[]): string {\n const map = toRegistry(skills);\n return text.replace(\n /\\/skill:([a-z0-9-]+)(?:[ \\t]+([^\\n]*))?/g,\n (match, name: string, args?: string) => {\n const skill = map.get(name);\n if (!skill) return match;\n const location = skill.filePath ? ` location=\"${skill.filePath}\"` : '';\n const xml = `<skill name=\"${name}\"${location}>\\n${skill.content}\\n</skill>`;\n return args ? `${xml}\\n\\n${args}` : xml;\n },\n );\n}\n","/**\n * Skills - 文件加载器(Node only)\n *\n * 发现规则(对齐 Agent Skills 开放规范 / pi):\n * - 目录含 SKILL.md → 视为 skill 根,不再递归;name 缺省取目录名\n * - 否则扫描根下直接 .md 子文件;再递归子目录寻找 SKILL.md\n * - 跳过 '.' 开头目录与 node_modules\n * 多源优先级:user → project → extraPaths,同名先注册者胜,败者产出 collision 诊断。\n * 加载失败 / 校验失败只产诊断不中断(健壮性优先)。\n */\n\nimport { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';\nimport type { Dirent } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { basename, join } from 'node:path';\nimport type { Skill, SkillDiagnostic, LoadSkillsOptions } from './types';\nimport { validateSkill } from './core';\n\n// ─── frontmatter 解析(手写 YAML-lite,零新依赖)──────────────────\n\nexport interface ParseSkillMarkdownOptions {\n /** frontmatter name 缺省时的回退名(目录名 / 文件名) */\n fallbackName?: string;\n /** 来源文件路径(填入 skill.filePath,baseDir 取 dirname) */\n filePath?: string;\n /** 来源标识(诊断用) */\n source?: string;\n}\n\n/**\n * 解析 SKILL.md 文本为 Skill。\n * frontmatter 支持顶层 key: value(name / description / disable-model-invocation),\n * 值可带单双引号;其余 key 忽略。缺失 name 用 fallbackName;缺失 description 报错。\n */\nexport function parseSkillMarkdown(\n text: string,\n options: ParseSkillMarkdownOptions = {},\n): { skill?: Skill; errors: string[] } {\n const errors: string[] = [];\n const normalized = text.replace(/^\\uFEFF/, '');\n const lines = normalized.split(/\\r?\\n/);\n\n const frontmatter = extractFrontmatter(lines);\n const content = frontmatter ? frontmatter.rest.join('\\n').trim() : normalized.trim();\n\n const name = frontmatter?.fields.get('name') ?? options.fallbackName;\n const description = frontmatter?.fields.get('description');\n const disableRaw = frontmatter?.fields.get('disable-model-invocation');\n\n if (!name) errors.push('name 缺失(frontmatter 或目录名均未提供)');\n if (!description) errors.push('description 缺失(frontmatter)');\n\n const skill: Skill = {\n name: name ?? '',\n description: description ?? '',\n content,\n disableModelInvocation: disableRaw === 'true',\n source: options.source ?? 'path',\n };\n if (options.filePath) {\n skill.filePath = options.filePath;\n skill.baseDir = dirnameOf(options.filePath);\n }\n\n // 无论是否有错误都返回 skill:调用方以 errors 为准决定是否注册,\n // 同时保留字段供上层诊断展示\n return { skill, errors };\n}\n\n/** 提取文件顶部 --- 包裹的 frontmatter;无则返回 null */\nfunction extractFrontmatter(lines: string[]): { fields: Map<string, string>; rest: string[] } | null {\n if ((lines[0] ?? '').trim() !== '---') return null;\n const fields = new Map<string, string>();\n let i = 1;\n for (; i < lines.length; i++) {\n const line = lines[i];\n if (line.trim() === '---') break;\n const idx = line.indexOf(':');\n if (idx <= 0) continue; // 忽略非 key: value 行(嵌套结构不支持)\n const key = line.slice(0, idx).trim();\n const value = stripQuotes(line.slice(idx + 1).trim());\n if (key) fields.set(key, value);\n }\n // 未闭合(无结束 ---):视为无 frontmatter 的普通文档\n if (i >= lines.length) return null;\n return { fields, rest: lines.slice(i + 1) };\n}\n\nfunction stripQuotes(value: string): string {\n if (value.length >= 2) {\n const first = value[0];\n if ((first === '\"' || first === \"'\") && value[value.length - 1] === first) {\n return value.slice(1, -1);\n }\n }\n return value;\n}\n\nfunction dirnameOf(filePath: string): string {\n const idx = Math.max(filePath.lastIndexOf('/'), filePath.lastIndexOf('\\\\'));\n return idx > 0 ? filePath.slice(0, idx) : filePath;\n}\n\n// ─── 目录扫描 ──────────────────────────────────────────────────────\n\n/** 目录内收集 skill(SKILL.md 根不递归;否则扫 .md 文件 + 递归子目录) */\nfunction collectFromDir(\n dir: string,\n source: string,\n diagnostics: SkillDiagnostic[],\n): Skill[] {\n const skillMdPath = join(dir, 'SKILL.md');\n if (existsSync(skillMdPath)) {\n const { skill, errors } = parseSkillMarkdown(\n readText(skillMdPath),\n { fallbackName: basename(dir), filePath: skillMdPath, source },\n );\n if (skill) return [skill];\n diagnostics.push({ type: 'error', message: errors.join('; '), path: skillMdPath });\n return [];\n }\n\n const skills: Skill[] = [];\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch (err) {\n diagnostics.push({\n type: 'warning',\n message: `目录读取失败: ${(err as Error)?.message ?? err}`,\n path: dir,\n });\n return [];\n }\n\n for (const entry of entries) {\n if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n skills.push(...collectFromDir(full, source, diagnostics));\n } else if (entry.isFile() && entry.name.toLowerCase().endsWith('.md') && entry.name !== 'SKILL.md') {\n const { skill, errors } = parseSkillMarkdown(\n readText(full),\n { fallbackName: basename(entry.name, '.md'), filePath: full, source },\n );\n if (skill) skills.push(skill);\n else diagnostics.push({ type: 'error', message: errors.join('; '), path: full });\n }\n }\n return skills;\n}\n\nfunction readText(path: string): string {\n try {\n return readFileSync(path, 'utf8');\n } catch {\n return '';\n }\n}\n\n// ─── 多源加载 ──────────────────────────────────────────────────────\n\n/**\n * 加载 skills:user → project → extraPaths,同名先注册者胜(user 优先)。\n * 缺省来源:userDir = ~/.aipack/skills,project = <cwd>/.aipack/skills。\n * 目录不存在静默跳过;校验失败 / 同名冲突产出诊断,不中断。\n */\nexport function loadSkills(\n options: LoadSkillsOptions = {},\n): { skills: Skill[]; diagnostics: SkillDiagnostic[] } {\n const cwd = options.cwd ?? process.cwd();\n const diagnostics: SkillDiagnostic[] = [];\n const byName = new Map<string, Skill>();\n const skills: Skill[] = [];\n\n const register = (skill: Skill | undefined, path: string): void => {\n if (!skill) return;\n const errors = validateSkill(skill);\n if (errors.length > 0) {\n diagnostics.push({ type: 'error', message: errors.join('; '), path });\n return;\n }\n const existing = byName.get(skill.name);\n if (existing) {\n diagnostics.push({\n type: 'collision',\n message: `skill \"${skill.name}\" 已注册(${existing.filePath ?? 'inline'}),忽略后注册者`,\n path,\n });\n return;\n }\n byName.set(skill.name, skill);\n skills.push(skill);\n };\n\n const loadDir = (dir: string, source: string): void => {\n if (!dir || !existsSync(dir)) return;\n for (const skill of collectFromDir(dir, source, diagnostics)) {\n register(skill, skill.filePath ?? dir);\n }\n };\n const loadFile = (file: string, source: string): void => {\n if (!file || !existsSync(file)) return;\n const { skill, errors } = parseSkillMarkdown(readText(file), {\n fallbackName: basename(file, '.md'),\n filePath: file,\n source,\n });\n register(skill, file);\n if (errors.length > 0) {\n diagnostics.push({ type: 'error', message: errors.join('; '), path: file });\n }\n };\n\n // 多源优先级:user → project → extraPaths。\n // includeDefaults=false 仅跳过\"默认目录\";显式指定的 userDir / projectDirName 仍生效。\n const userDir = options.userDir ?? join(homedir(), '.aipack', 'skills');\n const projectDir = join(cwd, options.projectDirName ?? join('.aipack', 'skills'));\n if (options.includeDefaults !== false) {\n loadDir(userDir, 'user');\n loadDir(projectDir, 'project');\n } else {\n if (options.userDir) loadDir(userDir, 'user');\n if (options.projectDirName) loadDir(projectDir, 'project');\n }\n\n for (const p of options.extraPaths ?? []) {\n if (!p || !existsSync(p)) {\n diagnostics.push({ type: 'warning', message: `extraPath 不存在,已跳过`, path: p });\n continue;\n }\n let isDir = false;\n try {\n isDir = statSync(p).isDirectory();\n } catch {\n isDir = false;\n }\n if (isDir) loadDir(p, 'path');\n else loadFile(p, 'path');\n }\n\n return { skills, diagnostics };\n}\n","/**\n * Skills - Runtime 插件(Extension 接入)\n *\n * 通过 @aipack-ai/agent 的 Extension 机制零侵入接入 Runtime:\n * 1. apply 阶段:经 ExtensionContext.runtime 注册内置 skill 工具\n * (模型可调用获取 skill 全文;执行走 Runtime 统一的工具通道)\n * 2. beforeModelCall 钩子:把 <available_skills> 目录段附加到 system prompt\n * (渐进式披露:目录先行,全文按需拉取;基准 prompt 每次重建,不累积)\n *\n * 用法:\n * const runtime = createRuntime({ ..., extensions: [new SkillsExtension(skills)] });\n * // 或一站式(含文件加载):\n * const plugin = createSkillsPlugin({ load: { cwd } });\n * const runtime = createRuntime({ ..., extensions: plugin.extensions });\n */\n\nimport { BaseExtension } from '@aipack-ai/agent';\nimport type { Extension, RuntimeHooks, ExtensionContext, Context } from '@aipack-ai/agent';\nimport type { Skill, SkillDiagnostic, LoadSkillsOptions } from './types';\nimport { validateSkill, formatSkillsSection, createSkillTool } from './core';\nimport { loadSkills } from './loader';\n\n// ─── 插件选项 ──────────────────────────────────────────────────────\n\nexport interface SkillsExtensionOptions {\n /** skill 工具名(默认 'skill') */\n toolName?: string;\n /** 是否注册内置 skill 工具(默认 true;关闭后仅注入目录段,全文获取由上层自理) */\n registerTool?: boolean;\n /** 是否注入 <available_skills> 目录段到 system prompt(默认 true) */\n injectPrompt?: boolean;\n}\n\n// ─── SkillsExtension ───────────────────────────────────────────────\n\nexport class SkillsExtension extends BaseExtension {\n readonly name = 'skills';\n\n private skills: Skill[];\n private options: SkillsExtensionOptions;\n\n constructor(skills: Skill[], options: SkillsExtensionOptions = {}) {\n super();\n this.skills = skills;\n this.options = options;\n }\n\n protected setup(hooks: RuntimeHooks, context: ExtensionContext): void {\n // 1. 过滤非法 skill(校验失败只告警不中断)\n const valid: Skill[] = [];\n for (const skill of this.skills) {\n const errors = validateSkill(skill);\n if (errors.length > 0) {\n console.warn(`[skills] \"${skill.name}\" 校验失败,已忽略: ${errors.join('; ')}`);\n continue;\n }\n valid.push(skill);\n }\n\n // 2. 注册内置 skill 工具(模型按需获取全文;执行走 Runtime 工具通道)\n if (this.options.registerTool !== false && valid.length > 0) {\n if (!context.runtime) {\n console.warn('[skills] ExtensionContext.runtime 不可用,跳过 skill 工具注册');\n } else {\n context.runtime.registerTool(\n createSkillTool(valid, { name: this.options.toolName }),\n );\n }\n }\n\n // 3. 注入 <available_skills> 目录段(渐进式披露;无可见 skill 时零开销)\n const visible = valid.filter(s => !s.disableModelInvocation);\n if (this.options.injectPrompt !== false && visible.length > 0) {\n const section = formatSkillsSection(visible);\n hooks.beforeModelCall.tapPromise('skills', async (ctx: Context): Promise<Context> => ({\n ...ctx,\n systemPrompt: ctx.systemPrompt ? `${ctx.systemPrompt}\\n\\n${section}` : section,\n }));\n }\n }\n}\n\n// ─── 一站式插件工厂 ────────────────────────────────────────────────\n\nexport interface SkillsPluginOptions extends SkillsExtensionOptions {\n /** 程序化提供的 skills(与 load 合并时 inline 优先) */\n skills?: Skill[];\n /** 文件加载选项(提供时调用 loadSkills) */\n load?: LoadSkillsOptions;\n}\n\nexport interface SkillsPlugin {\n /** 合并后的全部 skills(含 disableModelInvocation) */\n skills: Skill[];\n /** 加载诊断(error / collision 等),供上层展示 */\n diagnostics: SkillDiagnostic[];\n /** 接入 Runtime 的扩展列表(无可用 skill 时为空数组,零开销) */\n extensions: Extension[];\n}\n\n/**\n * 一站式创建 skills 插件:文件加载(可选)+ inline 合并 + Extension 装配。\n *\n * 用法(aipack.config.js / CLI):\n * import { createSkillsPlugin } from '@aipack-ai/skills';\n * const plugin = createSkillsPlugin({ load: { cwd } });\n * export default { ..., extensions: plugin.extensions };\n */\nexport function createSkillsPlugin(options: SkillsPluginOptions = {}): SkillsPlugin {\n const loaded = options.load\n ? loadSkills(options.load)\n : { skills: [] as Skill[], diagnostics: [] as SkillDiagnostic[] };\n\n const skills = [...(options.skills ?? []), ...loaded.skills];\n const diagnostics: SkillDiagnostic[] = [...loaded.diagnostics];\n\n const extensions: Extension[] =\n skills.length > 0 ? [new SkillsExtension(skills, options)] : [];\n\n return { skills, diagnostics, extensions };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aipack-ai/skills",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "aipack Agent Skills 插件:SKILL.md 加载 + 渐进式披露 + skill 工具,Extension 机制接入",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"keywords": [
|
|
20
|
+
"aipack",
|
|
21
|
+
"skills",
|
|
22
|
+
"agent",
|
|
23
|
+
"plugin",
|
|
24
|
+
"skill",
|
|
25
|
+
"context"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18.0.0"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@aipack-ai/agent": "1.0.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^20.12.0",
|
|
36
|
+
"tsup": "^8.5.1",
|
|
37
|
+
"typescript": "^5.5.0",
|
|
38
|
+
"tsx": "^4.16.0",
|
|
39
|
+
"@aipack-ai/agent": "1.0.0"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsup",
|
|
43
|
+
"prebuild": "[ -f ../agent/dist/index.d.ts ] || pnpm --filter @aipack-ai/agent build",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"test": "node --import tsx --test tests/*.test.ts"
|
|
46
|
+
}
|
|
47
|
+
}
|