@agenticforge/skills 1.1.1 → 1.4.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.zh_CN.md CHANGED
@@ -1,217 +1,227 @@
1
- # @agenticforge/skills
2
-
3
- [![npm](https://img.shields.io/npm/v/@agenticforge/skills)](https://www.npmjs.com/package/@agenticforge/skills)
4
- [![license](https://img.shields.io/github/license/LittleBlacky/AgenticFORGE)](https://github.com/LittleBlacky/AgenticFORGE/blob/main/LICENSE)
5
-
6
- <p><strong>中文</strong> | <a href="./README.md">English</a></p>
7
-
8
- AgenticFORGE 的可组合、可路由 Agent Skills 系统 —— 用 **Markdown 文件**或 **TypeScript 类**定义能力单元,让 Agent 自动路由到最合适的 Skill。
9
-
10
- ## 安装
11
-
12
- ```bash
13
- npm install @agenticforge/skills
14
- ```
15
-
16
- ## 什么是 Skill?
17
-
18
- **Skill** 是一个具名、自包含的 Agent 能力单元,类似于 Semantic Kernel 的 Plugin 或 Copilot Studio 的 Skill。每个 Skill 封装了:
19
-
20
- - 明确的业务语义(`name` + `description`)
21
- - 专属的系统提示词(角色定义、规则、约束)
22
- - 专属的工具集(只有该 Skill 可使用)
23
- - 独立的 `execute()` 执行逻辑
24
-
25
- Skill 支持两种定义方式:
26
-
27
- | 方式 | 适用场景 |
28
- |------|----------|
29
- | **Markdown 文件**(`.md`) | 非开发者、快速迭代、兼容 Cursor/Claude skills 目录规范 |
30
- | **TypeScript 类** | 复杂逻辑、自定义工具编排、程序化控制 |
31
-
32
- ---
33
-
34
- ## Markdown Skill(推荐)
35
-
36
- 创建一个带 YAML frontmatter 的 `SKILL.md` 文件:
37
-
38
- ```markdown
39
- ---
40
- name: weather-assistant
41
- description: 获取城市实时天气,回答温度、降雨、风速相关问题
42
- triggerHint: 当用户询问天气、温度、是否下雨、风速时
43
- ---
44
-
45
- # 天气助理
46
-
47
- ## 角色
48
- 你是一个简洁的天气助理,只回答天气相关问题,用简洁中文回答。
49
-
50
- ## 规则
51
- - 回答必须包含城市名和日期。
52
- - 天气数据不可用时明确告知用户。
53
- - 不回答与天气无关的问题。
54
- ```
55
-
56
- 从目录加载 Skills:
57
-
58
- ```ts
59
- import { SkillLoader, SkillRunner } from "@agenticforge/skills";
60
-
61
- const registry = await SkillLoader.registryFromDirectory(".cursor/skills");
62
- const runner = new SkillRunner({ llm, skills: registry.all() });
63
- const result = await runner.run("东京今天下雨吗?");
64
- console.log(result.output);
65
- ```
66
-
67
- ---
68
-
69
- ## TypeScript Skill
70
-
71
- ### 方式 A — 直接实例化
72
-
73
- ```ts
74
- import { AgentSkill, SkillRunner } from "@agenticforge/skills";
75
-
76
- const weatherSkill = new AgentSkill({
77
- name: "weather",
78
- description: "获取城市实时天气,回答温度、降雨、风速问题",
79
- triggerHint: "当用户询问天气、温度、是否下雨时",
80
- systemPrompt: "你是简洁的天气助理,只回答天气相关问题,用中文回答。",
81
- tools: [weatherApiTool],
82
- });
83
-
84
- const runner = new SkillRunner({ llm, skills: [weatherSkill] });
85
- const result = await runner.run("巴黎今天天气怎么样?");
86
- ```
87
-
88
- ### 方式 B — 继承扩展
89
-
90
- ```ts
91
- import { AgentSkill } from "@agenticforge/skills";
92
- import type { SkillContext, SkillResult } from "@agenticforge/skills";
93
- import type { LLMClient } from "@agenticforge/core";
94
-
95
- class StockSkill extends AgentSkill {
96
- constructor() {
97
- super({
98
- name: "stock-query",
99
- description: "查询实时股票价格和财务数据",
100
- triggerHint: "当用户询问股票价格、市值、财报时",
101
- });
102
- }
103
-
104
- override async execute(ctx: SkillContext, llm: LLMClient): Promise<SkillResult> {
105
- const price = await fetchStockPrice(ctx.query);
106
- return { output: `当前股价:${price}` };
107
- }
108
- }
109
- ```
110
-
111
- ---
112
-
113
- ## 多 Skill 自动路由
114
-
115
- ```ts
116
- import { SkillLoader, SkillRunner } from "@agenticforge/skills";
117
-
118
- const mdSkills = await SkillLoader.fromDirectory(".cursor/skills");
119
- const codeSkills = [new StockSkill(), new EmailSkill()];
120
-
121
- const runner = new SkillRunner({ llm, skills: [...mdSkills, ...codeSkills] });
122
-
123
- // 自动路由
124
- await runner.run("苹果股票现在多少?"); // => StockSkill
125
- await runner.run("东京今天下雨吗?"); // => weather SKILL.md
126
- await runner.run("帮我起草一封会议邀请"); // => EmailSkill
127
-
128
- // 直接调用指定 Skill(跳过路由)
129
- await runner.runSkill("stock-query", "AAPL 股价");
130
- ```
131
-
132
- ---
133
-
134
- ## API 参考
135
-
136
- ### `MarkdownSkill`
137
-
138
- | 方法 | 说明 |
139
- |------|------|
140
- | `MarkdownSkill.fromFile(path)` | 从 `.md` 文件加载 Skill |
141
- | `MarkdownSkill.fromSource(text)` | 从原始 Markdown 字符串解析 Skill |
142
- | `skill.execute(ctx, llm)` | 执行 Skill(将正文注入为 system prompt) |
143
-
144
- ### `AgentSkill`
145
-
146
- | 属性 / 方法 | 说明 |
147
- |-------------|------|
148
- | `name` | Skill 唯一标识 |
149
- | `description` | 一句话描述,用于路由匹配 |
150
- | `triggerHint` | 描述触发条件,辅助 LLM 路由决策 |
151
- | `systemPrompt` | Skill 执行时的系统提示词 |
152
- | `tools` | Skill 专属工具集 |
153
- | `execute(ctx, llm)` | 默认:LLM + 工具调用循环。可 override 自定义逻辑 |
154
-
155
- ### `SkillRegistry`
156
-
157
- | 方法 | 说明 |
158
- |------|------|
159
- | `register(skill)` | 注册一个 Skill |
160
- | `get(name)` | 按名称查找 |
161
- | `list()` | 所有已注册 Skill 名称 |
162
- | `visible()` | LLM 路由器可见的 Skills |
163
- | `describeAll()` | 生成用于路由 prompt 的 Markdown 列表 |
164
-
165
- ### `SkillRunner`
166
-
167
- | 方法 | 说明 |
168
- |------|------|
169
- | `run(query, options?)` | 自动路由并执行 |
170
- | `runSkill(name, query)` | 直接执行指定 Skill |
171
- | `addSkill(skill)` | 运行时动态注册 Skill |
172
-
173
- ### `SkillLoader`
174
-
175
- | 方法 | 说明 |
176
- |------|------|
177
- | `fromDirectory(dir)` | 扫描目录,加载所有 `SKILL.md` |
178
- | `fromFiles(paths[])` | 从指定路径列表加载 |
179
- | `fromSources(sources[])` | 从原始字符串加载(测试/浏览器环境) |
180
- | `toRegistry(skills[])` | 将 Skills 封装为 `SkillRegistry` |
181
- | `registryFromDirectory(dir)` | `fromDirectory` + `toRegistry` 一步完成 |
182
-
183
- ---
184
-
185
- ## Skill 文件命名规范
186
-
187
- `SkillLoader` 识别以下文件名:
188
- - `SKILL.md`(推荐,兼容 Cursor / Claude skills 目录布局)
189
- - `*.skill.md`
190
-
191
- 其他 `.md` 文件(如 `examples.md`、`README.md`)会被忽略。
192
-
193
- ---
194
-
195
- ## SkillAgent 配合使用
196
-
197
- 如需完整的 Agent 体验(对话历史、`runStructured`),使用 `@agenticforge/agents` 中的 `SkillAgent`:
198
-
199
- ```ts
200
- import { SkillAgent } from "@agenticforge/agents";
201
- import { SkillLoader } from "@agenticforge/skills";
202
-
203
- const skills = await SkillLoader.fromDirectory(".cursor/skills");
204
-
205
- const agent = new SkillAgent({ name: "assistant", llm, skills });
206
-
207
- const reply = await agent.run("东京今天下雨吗?");
208
- const result = await agent.runSkill("weather", "明天东京天气如何?");
209
- ```
210
-
211
- ---
212
-
213
- ## 链接
214
-
215
- - [GitHub](https://github.com/LittleBlacky/AgenticFORGE/tree/main/packages/skills)
216
- - [npm](https://www.npmjs.com/package/@agenticforge/skills)
217
- - [主项目 README](https://github.com/LittleBlacky/AgenticFORGE)
1
+ # @agenticforge/skills
2
+
3
+ [![npm](https://img.shields.io/npm/v/@agenticforge/skills)](https://www.npmjs.com/package/@agenticforge/skills)
4
+ [![License: CC BY-NC-SA 4.0](https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-lightgrey.svg)](https://creativecommons.org/licenses/by-nc-sa/4.0/)
5
+
6
+ <p><strong>中文</strong> | <a href="./README.md">English</a></p>
7
+
8
+ AgenticFORGE 的可组合、可路由 Agent 能力系统。将每种能力定义为一个聚焦的 **Skill** —— 用 Markdown TypeScript —— 让框架自动把用户请求路由到最合适的那个。
9
+
10
+ ## 安装
11
+
12
+ ```bash
13
+ npm install @agenticforge/skills
14
+ ```
15
+
16
+ ---
17
+
18
+ ## 什么是 Skill
19
+
20
+ Skill 是一个具名、自包含的能力单元。把它想象成一个专家,你的 Agent 可以把具体问题委托给他:
21
+
22
+ - 一个**天气 Skill**,只处理天气相关问题
23
+ - 一个**代码审查 Skill**,专门评审 TypeScript 代码
24
+ - 一个**股票查询 Skill**,查询实时行情数据
25
+
26
+ 每个 Skill 拥有自己的系统提示词、工具集和执行逻辑。用户请求到来时,框架自动路由到最匹配的 Skill。
27
+
28
+ ---
29
+
30
+ ## 定义 Skill
31
+
32
+ ### 方式 1 — Markdown(大多数场景推荐)
33
+
34
+ 创建一个 `SKILL.md` 文件。frontmatter 定义路由元数据,正文成为系统提示词。
35
+
36
+ ```markdown
37
+ ---
38
+ name: code-reviewer
39
+ description: 审查 TypeScript 和 JavaScript 代码,发现 Bug、类型安全问题和性能隐患。
40
+ triggerHint: 当用户要求审查、检查或改善代码质量时
41
+ ---
42
+
43
+ # 代码审查员
44
+
45
+ 你是一位资深 TypeScript 工程师,正在进行严格的代码审查。
46
+ 优先关注正确性,其次是性能,最后是代码风格。
47
+
48
+ ## 审查清单
49
+ - 类型安全:无隐式 `any`,返回值类型明确
50
+ - 错误处理:无未处理的 Promise rejection
51
+ - 边界条件:null/undefined、空数组
52
+
53
+ ## 输出格式
54
+ 1. **总体评价** — 一句话总结
55
+ 2. **问题列表** — 每条包含严重程度和修复建议
56
+ 3. **改进后的代码** — 重写有问题的部分
57
+ ```
58
+
59
+ 加载并运行:
60
+
61
+ ```ts
62
+ import { SkillLoader, SkillRunner } from "@agenticforge/skills";
63
+
64
+ const skills = await SkillLoader.fromDirectory("./skills");
65
+ const runner = new SkillRunner({ llm, skills });
66
+
67
+ const result = await runner.run(
68
+ "帮我审查这个函数:async function fetchUser(id) { return fetch('/api/' + id).then(r => r.json()); }"
69
+ );
70
+ console.log(result.output);
71
+ ```
72
+
73
+ ### 方式 2 — TypeScript 类
74
+
75
+ ```ts
76
+ import { AgentSkill } from "@agenticforge/skills";
77
+ import type { SkillContext, SkillResult } from "@agenticforge/skills";
78
+ import type { LLMClient } from "@agenticforge/core";
79
+
80
+ class StockSkill extends AgentSkill {
81
+ constructor() {
82
+ super({
83
+ name: "stock-query",
84
+ description: "查询任意股票代码的实时价格和市场数据。",
85
+ triggerHint: "当用户询问股票价格、市值、代码或交易数据时",
86
+ });
87
+ }
88
+
89
+ override async execute(ctx: SkillContext, llm: LLMClient): Promise<SkillResult> {
90
+ const price = await fetchStockPrice(extractTicker(ctx.query));
91
+ const output = await llm.think([
92
+ { role: "system", content: "用简洁友好的语言格式化股票数据。" },
93
+ { role: "user", content: `行情数据:${JSON.stringify(price)}\n问题:${ctx.query}` },
94
+ ]);
95
+ return { output };
96
+ }
97
+ }
98
+ ```
99
+
100
+ 简单场景直接实例化,无需继承:
101
+
102
+ ```ts
103
+ const translatorSkill = new AgentSkill({
104
+ name: "translator",
105
+ description: "在任意两种语言之间翻译文本。",
106
+ triggerHint: "当用户想翻译文本,或问某个词在另一种语言中怎么说时",
107
+ systemPrompt: "你是专业翻译。只输出翻译结果,不附加任何解释。",
108
+ });
109
+ ```
110
+
111
+ ---
112
+
113
+ ## 多 Skill 自动路由
114
+
115
+ `SkillRunner`(和 `SkillAgent`)使用**两级路由策略**:
116
+
117
+ | 级别 | 工作方式 | LLM 调用次数 |
118
+ |------|---------|-------------|
119
+ | **规则路由**(优先) | `triggerHint` 关键词与 query 匹配 | 0 |
120
+ | **LLM 路由**(兜底) | 把所有 Skill 描述发给 LLM 做意图分类 | 1 |
121
+
122
+ ```ts
123
+ const runner = new SkillRunner({
124
+ llm,
125
+ skills: [...skills, new StockSkill(), new CalendarSkill()],
126
+ fallbackPrompt: "你是一个通用助理。",
127
+ });
128
+
129
+ await runner.run("明天柏林天气如何?"); // => 天气 Skill(规则路由命中)
130
+ await runner.run("帮我审查一下这段 TS 代码。"); // => code-reviewer Skill
131
+ await runner.run("特斯拉现在股价多少?"); // => StockSkill
132
+ await runner.run("帮我约一个周五下午三点的会。"); // => CalendarSkill
133
+
134
+ // 跳过路由,直接调用指定 Skill
135
+ await runner.runSkill("stock-query", "AAPL 和 MSFT 本周走势对比");
136
+ ```
137
+
138
+ ---
139
+
140
+ ## 进阶:SkillDispatcher
141
+
142
+ ```ts
143
+ import { SkillDispatcher, SkillRegistry } from "@agenticforge/skills";
144
+
145
+ const registry = new SkillRegistry();
146
+ registry.register(weatherSkill);
147
+ registry.register(stockSkill);
148
+
149
+ const dispatcher = new SkillDispatcher(registry, llm);
150
+ const skill = await dispatcher.dispatch("东京明天下雨吗?");
151
+ if (skill) {
152
+ const result = await skill.execute({ query }, llm);
153
+ }
154
+ ```
155
+
156
+ ---
157
+
158
+ ## API 参考
159
+
160
+ ### `SkillRunner`
161
+
162
+ | 方法 | 说明 |
163
+ |------|------|
164
+ | `run(query, options?)` | 路由并执行最匹配的 Skill |
165
+ | `runSkill(name, query, options?)` | 直接执行指定 Skill(跳过路由)|
166
+ | `addSkill(skill)` | 运行时注册 Skill |
167
+ | `removeSkill(name)` | 注销 Skill |
168
+ | `listSkills()` | 列出所有已注册 Skill 名称 |
169
+
170
+ ### `AgentSkill`
171
+
172
+ | 成员 | 说明 |
173
+ |------|------|
174
+ | `name` | 唯一标识 |
175
+ | `description` | 一句话描述,LLM 路由读取此字段 |
176
+ | `triggerHint` | 规则路由关键词(逗号分隔)|
177
+ | `systemPrompt` | 执行时注入的系统提示词 |
178
+ | `tools` | Skill 专属工具集 |
179
+ | `execute(ctx, llm)` | Override 实现自定义执行逻辑 |
180
+
181
+ ### `SkillLoader`
182
+
183
+ | 方法 | 说明 |
184
+ |------|------|
185
+ | `fromDirectory(dir)` | 递归扫描 `SKILL.md` 和 `*.skill.md` |
186
+ | `fromFiles(paths[])` | 从指定路径列表加载 |
187
+ | `fromSources(sources[])` | 从原始字符串解析 |
188
+ | `registryFromDirectory(dir)` | 一步完成扫描 + 封装为 `SkillRegistry` |
189
+
190
+ ### `SkillRegistry`
191
+
192
+ | 方法 | 说明 |
193
+ |------|------|
194
+ | `register(skill)` | 注册 Skill |
195
+ | `get(name)` | 按名称查找 |
196
+ | `list()` | 所有已注册名称 |
197
+ | `visible()` | LLM 路由器可见的 Skills |
198
+ | `describeAll()` | 生成路由 prompt 用的格式化列表 |
199
+
200
+ ---
201
+
202
+ ## 与 SkillAgent 配合使用
203
+
204
+ 如需有状态的 Agent 体验(对话历史、多轮),使用 `@agenticforge/agents` 中的 `SkillAgent`:
205
+
206
+ ```ts
207
+ import { SkillAgent } from "@agenticforge/agents";
208
+ import { SkillLoader } from "@agenticforge/skills";
209
+
210
+ const skills = await SkillLoader.fromDirectory("./skills");
211
+ const agent = new SkillAgent({ name: "assistant", llm, skills });
212
+
213
+ // 对话历史自动维护
214
+ await agent.run("伦敦今天天气怎么样?");
215
+ await agent.run("那东京呢?"); // 知道上一轮在问天气
216
+ await agent.run("哪个城市更暖和?"); // 继续路由到天气 Skill
217
+
218
+ agent.clearHistory(); // 会话结束时重置
219
+ ```
220
+
221
+ ---
222
+
223
+ ## 链接
224
+
225
+ - [GitHub](https://github.com/LittleBlacky/AgenticFORGE/tree/main/packages/skills)
226
+ - [npm](https://www.npmjs.com/package/@agenticforge/skills)
227
+ - [主项目 README](https://github.com/LittleBlacky/AgenticFORGE)
@@ -1 +1 @@
1
- "use strict";var t=require("@agenticforge/tools");class e{name;description;triggerHint;systemPrompt;tools;visible;constructor(t){this.name=t.name,this.description=t.description,this.triggerHint=t.triggerHint,this.systemPrompt=t.systemPrompt,this.tools=t.tools??[],this.visible=t.visible??!0}_registry;get toolRegistry(){if(!this._registry){this._registry=new t.ToolRegistry;for(const e of this.tools)e instanceof t.Tool?this._registry.registerTool(e):this._registry.registerFunction(e.name,e.description,e.func,e.schema)}return this._registry}async execute(t,e){const s=[{role:"system",content:this.systemPrompt??`你是专门负责"${this.description}"的助理。`},...t.history??[],{role:"user",content:t.query}];if(0===this.tools.length)return{output:await e.think(s)};const i=[],r=s.map(t=>({...t})),o=this.toolRegistry.getOpenAISchemas(),n=e.client,l=e.model;if(!n||!l)return{output:await e.think(s)};let a="";for(let t=0;t<3;t++){const t=await n.chat.completions.create({model:l,messages:r,tools:o,tool_choice:"auto",stream:!1}),e=t.choices?.[0]?.message,s=e?.content??"",c=e?.tool_calls??[];if(0===c.length){a=s;break}r.push({role:"assistant",content:s,tool_calls:c});for(const t of c){i.push(t.function.name);let e={};try{e=JSON.parse(t.function.arguments)}catch{}let s="";try{s=await this.toolRegistry.execute(t.function.name,e)}catch(t){s=`Error: ${t instanceof Error?t.message:String(t)}`}r.push({role:"tool",tool_call_id:t.id,name:t.function.name,content:s})}}if(!a){const t=await n.chat.completions.create({model:l,messages:r,tools:o,tool_choice:"none",stream:!1});a=t.choices?.[0]?.message?.content??""}return{output:a,toolsUsed:i}}describe(){const t=[`- **${this.name}**: ${this.description}`];return this.triggerHint&&t.push(` 触发条件:${this.triggerHint}`),t.join("\n")}}class s{skills=new Map;register(t){this.skills.set(t.name,t)}unregister(t){return this.skills.delete(t)}get(t){return this.skills.get(t)}has(t){return this.skills.has(t)}list(){return Array.from(this.skills.keys())}all(){return Array.from(this.skills.values())}visible(){return this.all().filter(t=>!1!==t.visible)}size(){return this.skills.size}describeAll(){const t=this.visible();return 0===t.length?"(暂无可用 Skill)":t.map(t=>t instanceof e?t.describe():`- **${t.name}**: ${t.description}${t.triggerHint?`\n 触发条件:${t.triggerHint}`:""}`).join("\n")}}function i(t){const e=t.trim().match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);if(!e){const e=t.match(/^#\s+(.+)$/m);return{frontmatter:{name:e?r(e[1]):"unknown-skill",description:e?e[1]:"Unnamed skill"},body:t}}const[,s,i]=e,o={};for(const t of s.split(/\r?\n/)){const e=t.match(/^([\w-]+):\s*(.*)$/);if(!e)continue;const[,s,i]=e;o[s]="true"===i||"false"!==i&&i.replace(/^"|"$/g,"").replace(/^'|'$/g,"").trim()}if(!o.name){const t=i.match(/^#\s+(.+)$/m);o.name=t?r(t[1]):"unknown-skill"}return o.description||(o.description=o.name),{frontmatter:o,body:i.trim()}}function r(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}class o{name;description;triggerHint;visible;tools=[];systemPrompt;frontmatter;filePath;constructor(t,e,s){this.frontmatter=t,this.name=t.name,this.description=t.description,this.triggerHint=t.triggerHint,this.visible=!1!==t.visible,this.systemPrompt=e,this.filePath=s}static fromSource(t,e){const{frontmatter:s,body:r}=i(t);return new o(s,r,e)}static async fromFile(t){const{readFile:e}=await import("node:fs/promises"),s=await e(t,"utf8");return o.fromSource(s,t)}async execute(t,e){const s=[{role:"system",content:this.systemPrompt},...t.history??[],{role:"user",content:t.query}];return{output:await e.think(s)}}describe(){const t=[`- **${this.name}**: ${this.description}`];return this.triggerHint&&t.push(` 触发条件:${this.triggerHint}`),t.join("\n")}}class n{static async fromDirectory(t,e={}){const{readdir:s,stat:i}=await import("node:fs/promises"),{join:r,resolve:n}=await import("node:path"),a=e.recursive??!0,c=[],m=n(t);return await async function t(e){let n;try{n=await s(e)}catch{return}for(const s of n){const n=r(e,s);let m;try{m=await i(n)}catch{continue}if(m.isDirectory())a&&await t(n);else if(l(s))try{c.push(await o.fromFile(n))}catch(t){console.warn(`[SkillLoader] Failed to load ${n}: ${String(t)}`)}}}(m),c}static async fromFiles(t){const{resolve:e}=await import("node:path"),s=[];for(const i of t)try{s.push(await o.fromFile(e(i)))}catch(t){console.warn(`[SkillLoader] Failed to load ${i}: ${String(t)}`)}return s}static fromSources(t){return t.map(({source:t,filePath:e})=>o.fromSource(t,e))}static toRegistry(t,e){const i=e??new s;for(const e of t)i.register(e);return i}static async registryFromDirectory(t,e){const s=await n.fromDirectory(t,e);return n.toRegistry(s)}}function l(t){const e=t.toLowerCase();return"skill.md"===e||e.endsWith(".skill.md")}exports.AgentSkill=e,exports.MarkdownSkill=o,exports.SkillLoader=n,exports.SkillRegistry=s,exports.SkillRunner=class{skillRegistry;llm;fallbackPrompt;routerPromptTemplate;constructor(t){this.llm=t.llm,this.fallbackPrompt=t.fallbackPrompt??"你是一个通用AI助理,请回答用户的问题。",this.routerPromptTemplate=t.routerPromptTemplate??["你是一个意图路由器。根据用户输入,从下面的 Skill 列表中选出最合适的一个。","只回答 Skill 的名称(name 字段),不要包含任何解释或标点。","","## 可用 Skills","{skills}","","## 用户输入","{query}","","## 你的选择(只填 Skill name):"].join("\n"),this.skillRegistry=new s;for(const e of t.skills??[])this.skillRegistry.register(e)}addSkill(t){this.skillRegistry.register(t)}removeSkill(t){return this.skillRegistry.unregister(t)}listSkills(){return this.skillRegistry.list()}async routeToSkill(t){const e=this.skillRegistry.visible();if(0===e.length)return;if(1===e.length)return e[0];const s=this.routerPromptTemplate.replace("{skills}",this.skillRegistry.describeAll()).replace("{query}",t),i=(await this.llm.think([{role:"user",content:s}])).trim().toLowerCase();return this.skillRegistry.get(i)??e.find(t=>t.name.toLowerCase().startsWith(i))??e.find(t=>i.includes(t.name.toLowerCase()))}async run(t,e){let s;if(e?.skillName){if(s=this.skillRegistry.get(e.skillName),!s)throw new Error(`Skill "${e.skillName}" not found. Available: ${this.skillRegistry.list().join(", ")}`)}else s=await this.routeToSkill(t);if(!s)return{output:await this.llm.think([{role:"system",content:this.fallbackPrompt},...e?.history??[],{role:"user",content:t}])};const i={query:t,metadata:e?.metadata,history:e?.history};return s.execute(i,this.llm)}async runSkill(t,e,s){return this.run(e,{skillName:t,...s})}},exports.parseFrontmatter=i;
1
+ "use strict";var t=require("@agenticforge/tools"),e=require("@agenticforge/core");class s{name;description;triggerHint;systemPrompt;tools;visible;constructor(t){this.name=t.name,this.description=t.description,this.triggerHint=t.triggerHint,this.systemPrompt=t.systemPrompt,this.tools=t.tools??[],this.visible=t.visible??!0}_registry;get toolRegistry(){if(!this._registry){this._registry=new t.ToolRegistry;for(const e of this.tools)e instanceof t.Tool?this._registry.registerTool(e):this._registry.registerFunction(e.name,e.description,e.func,e.schema)}return this._registry}async execute(t,s){const r=[{role:"system",content:this.systemPrompt??`你是专门负责"${this.description}"的助理。`},...t.history??[],{role:"user",content:t.query}],i=new e.ToolCallExecutor({llm:s,maxIterations:3}),n=this.tools.length>0?this.toolRegistry.getOpenAISchemas():[];try{const t=await i.run({messages:r,tools:n,executor:(t,e)=>this.toolRegistry.execute(t,e)});return{output:t.output,...t.toolsUsed.length>0?{toolsUsed:t.toolsUsed}:{}}}catch(t){const e=t instanceof Error?t.message:String(t);if(e.includes("未暴露底层 OpenAI 客户端")||e.includes("does not expose"))return{output:await s.think(r)};throw t}}describe(){const t=[`- **${this.name}**: ${this.description}`];return this.triggerHint&&t.push(` 触发条件:${this.triggerHint}`),t.join("\n")}}class r{skills=new Map;register(t){this.skills.set(t.name,t)}unregister(t){return this.skills.delete(t)}get(t){return this.skills.get(t)}has(t){return this.skills.has(t)}list(){return Array.from(this.skills.keys())}all(){return Array.from(this.skills.values())}visible(){return this.all().filter(t=>!1!==t.visible)}size(){return this.skills.size}describeAll(){const t=this.visible();return 0===t.length?"(暂无可用 Skill)":t.map(t=>t instanceof s?t.describe():`- **${t.name}**: ${t.description}${t.triggerHint?`\n 触发条件:${t.triggerHint}`:""}`).join("\n")}}const i=["你是一个意图路由器。根据用户输入,从下面的 Skill 列表中选出最合适的一个。","只回答 Skill 的名称(name 字段),不要包含任何解释或标点。","如果没有合适的 Skill,回答 __none__。","","## 可用 Skills","{skills}","","## 用户输入","{query}","","## 你的选择(只填 Skill name 或 __none__):"].join("\n");class n{registry;llm;routerPromptTemplate;triggerHintSeparator;disableRuleRouting;constructor(t,e,s={}){this.registry=t,this.llm=e,this.routerPromptTemplate=s.routerPromptTemplate??i,this.triggerHintSeparator=s.triggerHintSeparator??/[,,、]/,this.disableRuleRouting=s.disableRuleRouting??!1}async dispatch(t){const e=this.registry.visible();if(0!==e.length){if(1===e.length)return e[0];if(!this.disableRuleRouting){const s=this.ruleRoute(t,e);if(s)return s}return this.llmRoute(t,e)}}ruleRoute(t,e){const s=t.toLowerCase();for(const t of e)if(t.triggerHint&&t.triggerHint.toLowerCase().split(this.triggerHintSeparator).map(t=>t.trim()).filter(Boolean).some(t=>s.includes(t)))return t}async llmRoute(t,e){const s=this.routerPromptTemplate.replace("{skills}",this.registry.describeAll()).replace("{query}",t),r=(await this.llm.think([{role:"user",content:s}])).trim().toLowerCase();if("__none__"!==r)return this.registry.get(r)??e.find(t=>t.name.toLowerCase().startsWith(r))??e.find(t=>r.includes(t.name.toLowerCase()))}}function o(t){const e=t.trim().match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);if(!e){const e=t.match(/^#\s+(.+)$/m);return{frontmatter:{name:e?l(e[1]):"unknown-skill",description:e?e[1]:"Unnamed skill"},body:t}}const[,s,r]=e,i={};for(const t of s.split(/\r?\n/)){const e=t.match(/^([\w-]+):\s*(.*)$/);if(!e)continue;const[,s,r]=e;i[s]="true"===r||"false"!==r&&r.replace(/^"|"$/g,"").replace(/^'|'$/g,"").trim()}if(!i.name){const t=r.match(/^#\s+(.+)$/m);i.name=t?l(t[1]):"unknown-skill"}return i.description||(i.description=i.name),{frontmatter:i,body:r.trim()}}function l(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}class a{name;description;triggerHint;visible;tools=[];systemPrompt;frontmatter;filePath;constructor(t,e,s){this.frontmatter=t,this.name=t.name,this.description=t.description,this.triggerHint=t.triggerHint,this.visible=!1!==t.visible,this.systemPrompt=e,this.filePath=s}static fromSource(t,e){const{frontmatter:s,body:r}=o(t);return new a(s,r,e)}static async fromFile(t){const{readFile:e}=await import("node:fs/promises"),s=await e(t,"utf8");return a.fromSource(s,t)}async execute(t,e){const s=[{role:"system",content:this.systemPrompt},...t.history??[],{role:"user",content:t.query}];return{output:await e.think(s)}}describe(){const t=[`- **${this.name}**: ${this.description}`];return this.triggerHint&&t.push(` 触发条件:${this.triggerHint}`),t.join("\n")}}class c{static async fromDirectory(t,e={}){const{readdir:s,stat:r}=await import("node:fs/promises"),{join:i,resolve:n}=await import("node:path"),o=e.recursive??!0,l=[],c=n(t);return await async function t(e){let n;try{n=await s(e)}catch{return}for(const s of n){const n=i(e,s);let c;try{c=await r(n)}catch{continue}if(c.isDirectory())o&&await t(n);else if(h(s))try{l.push(await a.fromFile(n))}catch(t){console.warn(`[SkillLoader] Failed to load ${n}: ${String(t)}`)}}}(c),l}static async fromFiles(t){const{resolve:e}=await import("node:path"),s=[];for(const r of t)try{s.push(await a.fromFile(e(r)))}catch(t){console.warn(`[SkillLoader] Failed to load ${r}: ${String(t)}`)}return s}static fromSources(t){return t.map(({source:t,filePath:e})=>a.fromSource(t,e))}static toRegistry(t,e){const s=e??new r;for(const e of t)s.register(e);return s}static async registryFromDirectory(t,e){const s=await c.fromDirectory(t,e);return c.toRegistry(s)}}function h(t){const e=t.toLowerCase();return"skill.md"===e||e.endsWith(".skill.md")}class u{maxSize;cache=new Map;constructor(t=100){this.maxSize=Math.max(1,t)}get size(){return this.cache.size}has(t){return this.cache.has(t)}get(t){const e=this.cache.get(t);if(e)return this.cache.delete(t),this.cache.set(t,e),e.value}set(t,e){if(this.cache.has(t)&&this.cache.delete(t),this.cache.set(t,{value:e,timestamp:Date.now()}),this.cache.size>this.maxSize){const t=this.cache.keys().next().value;t&&this.cache.delete(t)}}delete(t){return this.cache.delete(t)}clear(){this.cache.clear()}keys(){return[...this.cache.keys()]}values(){return[...this.cache.values()].map(t=>t.value)}entries(){return[...this.cache.entries()].map(([t,e])=>[t,e.value])}}class m extends t.Tool{skill;llm;cache;constructor(t,e,s){const r=t.triggerHint?`${t.description}\n触发条件:${t.triggerHint}`:t.description;super(t.name,r),this.skill=t,this.llm=e,s?.cacheSize&&s.cacheSize>0&&(this.cache=new u(s.cacheSize))}getParameters(){return[{name:"query",type:"string",description:"用户的完整问题或指令,原文传入即可",required:!0,default:null}]}async run(t){const e=String(t.query??"").trim();if(!e)return"错误:query 不能为空";if(this.cache){const t=this.cache.get(e);if(void 0!==t)return t}const s={query:e};try{const t=await this.skill.execute(s,this.llm),r=this.formatOutput(t);return this.cache&&this.cache.set(e,r),r}catch(t){return`Error: ${t instanceof Error?t.message:String(t)}`}}async runSkill(t){const e=t.trim();if(!e)throw new Error("query 不能为空");const s={query:e};return this.skill.execute(s,this.llm)}getSkill(){return this.skill}getCacheStats(){return this.cache?{size:this.cache.size,keys:this.cache.keys()}:null}clearCache(){this.cache?.clear()}formatOutput(t){return t.toolsUsed&&t.toolsUsed.length>0?`${t.output}\n\n[tools_used: ${t.toolsUsed.join(", ")}]`:t.output}}exports.AgentSkill=s,exports.MarkdownSkill=a,exports.SkillDispatcher=n,exports.SkillLoader=c,exports.SkillRegistry=r,exports.SkillRunner=class{skillRegistry;llm;fallbackPrompt;dispatcher;constructor(t){this.llm=t.llm,this.fallbackPrompt=t.fallbackPrompt??"你是一个通用AI助理,请回答用户的问题。",this.skillRegistry=new r;for(const e of t.skills??[])this.skillRegistry.register(e);this.dispatcher=new n(this.skillRegistry,this.llm,t.dispatcher)}addSkill(t){this.skillRegistry.register(t)}removeSkill(t){return this.skillRegistry.unregister(t)}listSkills(){return this.skillRegistry.list()}async run(t,e){const s=await this.resolveSkill(t,e?.skillName);if(!s)return{output:await this.llm.think([{role:"system",content:this.fallbackPrompt},...e?.history??[],{role:"user",content:t}])};const r={query:t,metadata:e?.metadata,history:e?.history?.filter(t=>"user"===t.role||"assistant"===t.role)};return s.execute(r,this.llm)}async runSkill(t,e,s){return this.run(e,{skillName:t,...s})}async resolveSkill(t,e){if(e){const t=this.skillRegistry.get(e);if(!t)throw new Error(`Skill "${e}" not found. Available: ${this.skillRegistry.list().join(", ")}`);return t}return this.dispatcher.dispatch(t)}},exports.SkillTool=m,exports.parseFrontmatter=o,exports.skillsToTools=function(t,e,s){return t.map(t=>new m(t,e,s))};
package/dist/esm/index.js CHANGED
@@ -1 +1 @@
1
- import{ToolRegistry as t,Tool as e}from"@agenticforge/tools";class s{name;description;triggerHint;systemPrompt;tools;visible;constructor(t){this.name=t.name,this.description=t.description,this.triggerHint=t.triggerHint,this.systemPrompt=t.systemPrompt,this.tools=t.tools??[],this.visible=t.visible??!0}_registry;get toolRegistry(){if(!this._registry){this._registry=new t;for(const t of this.tools)t instanceof e?this._registry.registerTool(t):this._registry.registerFunction(t.name,t.description,t.func,t.schema)}return this._registry}async execute(t,e){const s=[{role:"system",content:this.systemPrompt??`你是专门负责"${this.description}"的助理。`},...t.history??[],{role:"user",content:t.query}];if(0===this.tools.length)return{output:await e.think(s)};const i=[],r=s.map(t=>({...t})),o=this.toolRegistry.getOpenAISchemas(),n=e.client,l=e.model;if(!n||!l)return{output:await e.think(s)};let a="";for(let t=0;t<3;t++){const t=await n.chat.completions.create({model:l,messages:r,tools:o,tool_choice:"auto",stream:!1}),e=t.choices?.[0]?.message,s=e?.content??"",c=e?.tool_calls??[];if(0===c.length){a=s;break}r.push({role:"assistant",content:s,tool_calls:c});for(const t of c){i.push(t.function.name);let e={};try{e=JSON.parse(t.function.arguments)}catch{}let s="";try{s=await this.toolRegistry.execute(t.function.name,e)}catch(t){s=`Error: ${t instanceof Error?t.message:String(t)}`}r.push({role:"tool",tool_call_id:t.id,name:t.function.name,content:s})}}if(!a){const t=await n.chat.completions.create({model:l,messages:r,tools:o,tool_choice:"none",stream:!1});a=t.choices?.[0]?.message?.content??""}return{output:a,toolsUsed:i}}describe(){const t=[`- **${this.name}**: ${this.description}`];return this.triggerHint&&t.push(` 触发条件:${this.triggerHint}`),t.join("\n")}}class i{skills=new Map;register(t){this.skills.set(t.name,t)}unregister(t){return this.skills.delete(t)}get(t){return this.skills.get(t)}has(t){return this.skills.has(t)}list(){return Array.from(this.skills.keys())}all(){return Array.from(this.skills.values())}visible(){return this.all().filter(t=>!1!==t.visible)}size(){return this.skills.size}describeAll(){const t=this.visible();return 0===t.length?"(暂无可用 Skill)":t.map(t=>t instanceof s?t.describe():`- **${t.name}**: ${t.description}${t.triggerHint?`\n 触发条件:${t.triggerHint}`:""}`).join("\n")}}class r{skillRegistry;llm;fallbackPrompt;routerPromptTemplate;constructor(t){this.llm=t.llm,this.fallbackPrompt=t.fallbackPrompt??"你是一个通用AI助理,请回答用户的问题。",this.routerPromptTemplate=t.routerPromptTemplate??["你是一个意图路由器。根据用户输入,从下面的 Skill 列表中选出最合适的一个。","只回答 Skill 的名称(name 字段),不要包含任何解释或标点。","","## 可用 Skills","{skills}","","## 用户输入","{query}","","## 你的选择(只填 Skill name):"].join("\n"),this.skillRegistry=new i;for(const e of t.skills??[])this.skillRegistry.register(e)}addSkill(t){this.skillRegistry.register(t)}removeSkill(t){return this.skillRegistry.unregister(t)}listSkills(){return this.skillRegistry.list()}async routeToSkill(t){const e=this.skillRegistry.visible();if(0===e.length)return;if(1===e.length)return e[0];const s=this.routerPromptTemplate.replace("{skills}",this.skillRegistry.describeAll()).replace("{query}",t),i=(await this.llm.think([{role:"user",content:s}])).trim().toLowerCase();return this.skillRegistry.get(i)??e.find(t=>t.name.toLowerCase().startsWith(i))??e.find(t=>i.includes(t.name.toLowerCase()))}async run(t,e){let s;if(e?.skillName){if(s=this.skillRegistry.get(e.skillName),!s)throw new Error(`Skill "${e.skillName}" not found. Available: ${this.skillRegistry.list().join(", ")}`)}else s=await this.routeToSkill(t);if(!s)return{output:await this.llm.think([{role:"system",content:this.fallbackPrompt},...e?.history??[],{role:"user",content:t}])};const i={query:t,metadata:e?.metadata,history:e?.history};return s.execute(i,this.llm)}async runSkill(t,e,s){return this.run(e,{skillName:t,...s})}}function o(t){const e=t.trim().match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);if(!e){const e=t.match(/^#\s+(.+)$/m);return{frontmatter:{name:e?n(e[1]):"unknown-skill",description:e?e[1]:"Unnamed skill"},body:t}}const[,s,i]=e,r={};for(const t of s.split(/\r?\n/)){const e=t.match(/^([\w-]+):\s*(.*)$/);if(!e)continue;const[,s,i]=e;r[s]="true"===i||"false"!==i&&i.replace(/^"|"$/g,"").replace(/^'|'$/g,"").trim()}if(!r.name){const t=i.match(/^#\s+(.+)$/m);r.name=t?n(t[1]):"unknown-skill"}return r.description||(r.description=r.name),{frontmatter:r,body:i.trim()}}function n(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}class l{name;description;triggerHint;visible;tools=[];systemPrompt;frontmatter;filePath;constructor(t,e,s){this.frontmatter=t,this.name=t.name,this.description=t.description,this.triggerHint=t.triggerHint,this.visible=!1!==t.visible,this.systemPrompt=e,this.filePath=s}static fromSource(t,e){const{frontmatter:s,body:i}=o(t);return new l(s,i,e)}static async fromFile(t){const{readFile:e}=await import("node:fs/promises"),s=await e(t,"utf8");return l.fromSource(s,t)}async execute(t,e){const s=[{role:"system",content:this.systemPrompt},...t.history??[],{role:"user",content:t.query}];return{output:await e.think(s)}}describe(){const t=[`- **${this.name}**: ${this.description}`];return this.triggerHint&&t.push(` 触发条件:${this.triggerHint}`),t.join("\n")}}class a{static async fromDirectory(t,e={}){const{readdir:s,stat:i}=await import("node:fs/promises"),{join:r,resolve:o}=await import("node:path"),n=e.recursive??!0,a=[],m=o(t);return await async function t(e){let o;try{o=await s(e)}catch{return}for(const s of o){const o=r(e,s);let m;try{m=await i(o)}catch{continue}if(m.isDirectory())n&&await t(o);else if(c(s))try{a.push(await l.fromFile(o))}catch(t){console.warn(`[SkillLoader] Failed to load ${o}: ${String(t)}`)}}}(m),a}static async fromFiles(t){const{resolve:e}=await import("node:path"),s=[];for(const i of t)try{s.push(await l.fromFile(e(i)))}catch(t){console.warn(`[SkillLoader] Failed to load ${i}: ${String(t)}`)}return s}static fromSources(t){return t.map(({source:t,filePath:e})=>l.fromSource(t,e))}static toRegistry(t,e){const s=e??new i;for(const e of t)s.register(e);return s}static async registryFromDirectory(t,e){const s=await a.fromDirectory(t,e);return a.toRegistry(s)}}function c(t){const e=t.toLowerCase();return"skill.md"===e||e.endsWith(".skill.md")}export{s as AgentSkill,l as MarkdownSkill,a as SkillLoader,i as SkillRegistry,r as SkillRunner,o as parseFrontmatter};
1
+ import{ToolRegistry as t,Tool as e}from"@agenticforge/tools";import{ToolCallExecutor as s}from"@agenticforge/core";class r{name;description;triggerHint;systemPrompt;tools;visible;constructor(t){this.name=t.name,this.description=t.description,this.triggerHint=t.triggerHint,this.systemPrompt=t.systemPrompt,this.tools=t.tools??[],this.visible=t.visible??!0}_registry;get toolRegistry(){if(!this._registry){this._registry=new t;for(const t of this.tools)t instanceof e?this._registry.registerTool(t):this._registry.registerFunction(t.name,t.description,t.func,t.schema)}return this._registry}async execute(t,e){const r=[{role:"system",content:this.systemPrompt??`你是专门负责"${this.description}"的助理。`},...t.history??[],{role:"user",content:t.query}],i=new s({llm:e,maxIterations:3}),n=this.tools.length>0?this.toolRegistry.getOpenAISchemas():[];try{const t=await i.run({messages:r,tools:n,executor:(t,e)=>this.toolRegistry.execute(t,e)});return{output:t.output,...t.toolsUsed.length>0?{toolsUsed:t.toolsUsed}:{}}}catch(t){const s=t instanceof Error?t.message:String(t);if(s.includes("未暴露底层 OpenAI 客户端")||s.includes("does not expose"))return{output:await e.think(r)};throw t}}describe(){const t=[`- **${this.name}**: ${this.description}`];return this.triggerHint&&t.push(` 触发条件:${this.triggerHint}`),t.join("\n")}}class i{skills=new Map;register(t){this.skills.set(t.name,t)}unregister(t){return this.skills.delete(t)}get(t){return this.skills.get(t)}has(t){return this.skills.has(t)}list(){return Array.from(this.skills.keys())}all(){return Array.from(this.skills.values())}visible(){return this.all().filter(t=>!1!==t.visible)}size(){return this.skills.size}describeAll(){const t=this.visible();return 0===t.length?"(暂无可用 Skill)":t.map(t=>t instanceof r?t.describe():`- **${t.name}**: ${t.description}${t.triggerHint?`\n 触发条件:${t.triggerHint}`:""}`).join("\n")}}const n=["你是一个意图路由器。根据用户输入,从下面的 Skill 列表中选出最合适的一个。","只回答 Skill 的名称(name 字段),不要包含任何解释或标点。","如果没有合适的 Skill,回答 __none__。","","## 可用 Skills","{skills}","","## 用户输入","{query}","","## 你的选择(只填 Skill name 或 __none__):"].join("\n");class o{registry;llm;routerPromptTemplate;triggerHintSeparator;disableRuleRouting;constructor(t,e,s={}){this.registry=t,this.llm=e,this.routerPromptTemplate=s.routerPromptTemplate??n,this.triggerHintSeparator=s.triggerHintSeparator??/[,,、]/,this.disableRuleRouting=s.disableRuleRouting??!1}async dispatch(t){const e=this.registry.visible();if(0!==e.length){if(1===e.length)return e[0];if(!this.disableRuleRouting){const s=this.ruleRoute(t,e);if(s)return s}return this.llmRoute(t,e)}}ruleRoute(t,e){const s=t.toLowerCase();for(const t of e)if(t.triggerHint&&t.triggerHint.toLowerCase().split(this.triggerHintSeparator).map(t=>t.trim()).filter(Boolean).some(t=>s.includes(t)))return t}async llmRoute(t,e){const s=this.routerPromptTemplate.replace("{skills}",this.registry.describeAll()).replace("{query}",t),r=(await this.llm.think([{role:"user",content:s}])).trim().toLowerCase();if("__none__"!==r)return this.registry.get(r)??e.find(t=>t.name.toLowerCase().startsWith(r))??e.find(t=>r.includes(t.name.toLowerCase()))}}class l{skillRegistry;llm;fallbackPrompt;dispatcher;constructor(t){this.llm=t.llm,this.fallbackPrompt=t.fallbackPrompt??"你是一个通用AI助理,请回答用户的问题。",this.skillRegistry=new i;for(const e of t.skills??[])this.skillRegistry.register(e);this.dispatcher=new o(this.skillRegistry,this.llm,t.dispatcher)}addSkill(t){this.skillRegistry.register(t)}removeSkill(t){return this.skillRegistry.unregister(t)}listSkills(){return this.skillRegistry.list()}async run(t,e){const s=await this.resolveSkill(t,e?.skillName);if(!s)return{output:await this.llm.think([{role:"system",content:this.fallbackPrompt},...e?.history??[],{role:"user",content:t}])};const r={query:t,metadata:e?.metadata,history:e?.history?.filter(t=>"user"===t.role||"assistant"===t.role)};return s.execute(r,this.llm)}async runSkill(t,e,s){return this.run(e,{skillName:t,...s})}async resolveSkill(t,e){if(e){const t=this.skillRegistry.get(e);if(!t)throw new Error(`Skill "${e}" not found. Available: ${this.skillRegistry.list().join(", ")}`);return t}return this.dispatcher.dispatch(t)}}function a(t){const e=t.trim().match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);if(!e){const e=t.match(/^#\s+(.+)$/m);return{frontmatter:{name:e?c(e[1]):"unknown-skill",description:e?e[1]:"Unnamed skill"},body:t}}const[,s,r]=e,i={};for(const t of s.split(/\r?\n/)){const e=t.match(/^([\w-]+):\s*(.*)$/);if(!e)continue;const[,s,r]=e;i[s]="true"===r||"false"!==r&&r.replace(/^"|"$/g,"").replace(/^'|'$/g,"").trim()}if(!i.name){const t=r.match(/^#\s+(.+)$/m);i.name=t?c(t[1]):"unknown-skill"}return i.description||(i.description=i.name),{frontmatter:i,body:r.trim()}}function c(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"")}class h{name;description;triggerHint;visible;tools=[];systemPrompt;frontmatter;filePath;constructor(t,e,s){this.frontmatter=t,this.name=t.name,this.description=t.description,this.triggerHint=t.triggerHint,this.visible=!1!==t.visible,this.systemPrompt=e,this.filePath=s}static fromSource(t,e){const{frontmatter:s,body:r}=a(t);return new h(s,r,e)}static async fromFile(t){const{readFile:e}=await import("node:fs/promises"),s=await e(t,"utf8");return h.fromSource(s,t)}async execute(t,e){const s=[{role:"system",content:this.systemPrompt},...t.history??[],{role:"user",content:t.query}];return{output:await e.think(s)}}describe(){const t=[`- **${this.name}**: ${this.description}`];return this.triggerHint&&t.push(` 触发条件:${this.triggerHint}`),t.join("\n")}}class u{static async fromDirectory(t,e={}){const{readdir:s,stat:r}=await import("node:fs/promises"),{join:i,resolve:n}=await import("node:path"),o=e.recursive??!0,l=[],a=n(t);return await async function t(e){let n;try{n=await s(e)}catch{return}for(const s of n){const n=i(e,s);let a;try{a=await r(n)}catch{continue}if(a.isDirectory())o&&await t(n);else if(m(s))try{l.push(await h.fromFile(n))}catch(t){console.warn(`[SkillLoader] Failed to load ${n}: ${String(t)}`)}}}(a),l}static async fromFiles(t){const{resolve:e}=await import("node:path"),s=[];for(const r of t)try{s.push(await h.fromFile(e(r)))}catch(t){console.warn(`[SkillLoader] Failed to load ${r}: ${String(t)}`)}return s}static fromSources(t){return t.map(({source:t,filePath:e})=>h.fromSource(t,e))}static toRegistry(t,e){const s=e??new i;for(const e of t)s.register(e);return s}static async registryFromDirectory(t,e){const s=await u.fromDirectory(t,e);return u.toRegistry(s)}}function m(t){const e=t.toLowerCase();return"skill.md"===e||e.endsWith(".skill.md")}class g{maxSize;cache=new Map;constructor(t=100){this.maxSize=Math.max(1,t)}get size(){return this.cache.size}has(t){return this.cache.has(t)}get(t){const e=this.cache.get(t);if(e)return this.cache.delete(t),this.cache.set(t,e),e.value}set(t,e){if(this.cache.has(t)&&this.cache.delete(t),this.cache.set(t,{value:e,timestamp:Date.now()}),this.cache.size>this.maxSize){const t=this.cache.keys().next().value;t&&this.cache.delete(t)}}delete(t){return this.cache.delete(t)}clear(){this.cache.clear()}keys(){return[...this.cache.keys()]}values(){return[...this.cache.values()].map(t=>t.value)}entries(){return[...this.cache.entries()].map(([t,e])=>[t,e.value])}}class p extends e{skill;llm;cache;constructor(t,e,s){const r=t.triggerHint?`${t.description}\n触发条件:${t.triggerHint}`:t.description;super(t.name,r),this.skill=t,this.llm=e,s?.cacheSize&&s.cacheSize>0&&(this.cache=new g(s.cacheSize))}getParameters(){return[{name:"query",type:"string",description:"用户的完整问题或指令,原文传入即可",required:!0,default:null}]}async run(t){const e=String(t.query??"").trim();if(!e)return"错误:query 不能为空";if(this.cache){const t=this.cache.get(e);if(void 0!==t)return t}const s={query:e};try{const t=await this.skill.execute(s,this.llm),r=this.formatOutput(t);return this.cache&&this.cache.set(e,r),r}catch(t){return`Error: ${t instanceof Error?t.message:String(t)}`}}async runSkill(t){const e=t.trim();if(!e)throw new Error("query 不能为空");const s={query:e};return this.skill.execute(s,this.llm)}getSkill(){return this.skill}getCacheStats(){return this.cache?{size:this.cache.size,keys:this.cache.keys()}:null}clearCache(){this.cache?.clear()}formatOutput(t){return t.toolsUsed&&t.toolsUsed.length>0?`${t.output}\n\n[tools_used: ${t.toolsUsed.join(", ")}]`:t.output}}function y(t,e,s){return t.map(t=>new p(t,e,s))}export{r as AgentSkill,h as MarkdownSkill,o as SkillDispatcher,u as SkillLoader,i as SkillRegistry,l as SkillRunner,p as SkillTool,a as parseFrontmatter,y as skillsToTools};
@@ -52,8 +52,7 @@ export declare class AgentSkill implements IAgentSkill {
52
52
  protected get toolRegistry(): ToolRegistry;
53
53
  /**
54
54
  * Default implementation: builds messages from context + systemPrompt,
55
- * appends tool schemas if tools are configured, calls the LLM,
56
- * and runs a tool-call loop (up to 3 iterations).
55
+ * delegates to ToolCallExecutor for the function-calling loop.
57
56
  *
58
57
  * Override this method for fully custom Skill logic.
59
58
  */
@@ -1 +1 @@
1
- {"version":3,"file":"AgentSkill.d.ts","sourceRoot":"","sources":["../../src/AgentSkill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAE,KAAK,YAAY,EAAE,YAAY,EAAC,MAAM,qBAAqB,CAAC;AAC1E,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAC,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,WAAW,EAAC,MAAM,SAAS,CAAC;AAMrF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,qBAAa,UAAW,YAAW,WAAW;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,GAAG,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACpE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;gBAEd,UAAU,EAAE,eAAe;IAavC,OAAO,CAAC,SAAS,CAAC,CAAe;IAEjC,SAAS,KAAK,YAAY,IAAI,YAAY,CAiBzC;IAMD;;;;;;OAMG;IACG,OAAO,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IAsG1E,QAAQ,IAAI,MAAM;CAKnB"}
1
+ {"version":3,"file":"AgentSkill.d.ts","sourceRoot":"","sources":["../../src/AgentSkill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,KAAK,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAMvF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,qBAAa,UAAW,YAAW,WAAW;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,GAAG,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACpE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;gBAEd,UAAU,EAAE,eAAe;IAavC,OAAO,CAAC,SAAS,CAAC,CAAe;IAEjC,SAAS,KAAK,YAAY,IAAI,YAAY,CAYzC;IAMD;;;;;OAKG;IACG,OAAO,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IAsC1E,QAAQ,IAAI,MAAM;CAKnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"MarkdownSkill.d.ts","sourceRoot":"","sources":["../../src/MarkdownSkill.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAC,WAAW,EAAE,YAAY,EAAE,WAAW,EAAC,MAAM,SAAS,CAAC;AAMpE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,sDAAsD;IACtD,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG;IAChD,WAAW,EAAE,gBAAgB,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;CACd,CA0CA;AAUD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,aAAc,YAAW,WAAW;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAS,KAAK,EAAE,CAAC;IAE/B,uDAAuD;IACvD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAE9B,8CAA8C;IAC9C,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IAEvC,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAE3B,OAAO;IAkBP;;OAEG;IACH,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,aAAa;IAKnE;;;OAGG;WACU,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAU/D;;;OAGG;IACG,OAAO,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IAe1E,QAAQ,IAAI,MAAM;CAKnB"}
1
+ {"version":3,"file":"MarkdownSkill.d.ts","sourceRoot":"","sources":["../../src/MarkdownSkill.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAMtE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,sDAAsD;IACtD,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG;IAChD,WAAW,EAAE,gBAAgB,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;CACd,CAgDA;AAaD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,aAAc,YAAW,WAAW;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAS,KAAK,EAAE,CAAC;IAE/B,uDAAuD;IACvD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAE9B,8CAA8C;IAC9C,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IAEvC,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAE3B,OAAO;IAcP;;OAEG;IACH,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,aAAa;IAKnE;;;OAGG;WACU,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAU/D;;;OAGG;IACG,OAAO,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IAe1E,QAAQ,IAAI,MAAM;CAKnB"}
@@ -0,0 +1,57 @@
1
+ import type { LLMClient } from "@agenticforge/core";
2
+ import type { IAgentSkill } from "./types";
3
+ import type { SkillRegistry } from "./SkillRegistry";
4
+ export interface SkillDispatcherOptions {
5
+ /** 自定义路由 prompt 模板,支持 {skills} 和 {query} 占位符 */
6
+ routerPromptTemplate?: string;
7
+ /**
8
+ * 规则路由分隔符(用于拆分 triggerHint),默认 /[,,、]/
9
+ * 示例 triggerHint:"当用户询问天气,温度,降雨时"
10
+ */
11
+ triggerHintSeparator?: RegExp;
12
+ /** 是否禁用规则路由,只走 LLM 路由,默认 false */
13
+ disableRuleRouting?: boolean;
14
+ }
15
+ /**
16
+ * 纯路由器:给定 query,从 SkillRegistry 找出最匹配的 Skill。
17
+ *
18
+ * 两级策略(依次尝试):
19
+ * 1. **规则路由**:遍历 Skill 的 `triggerHint`,关键词命中则直接返回,零 LLM 调用
20
+ * 2. **LLM 路由**:把所有可见 Skill 的描述给 LLM,让它选择最合适的一个
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * const dispatcher = new SkillDispatcher(registry, llm);
25
+ * const skill = await dispatcher.dispatch("东京今天天气怎么样?");
26
+ * if (skill) {
27
+ * const result = await skill.execute({ query }, llm);
28
+ * }
29
+ * ```
30
+ */
31
+ export declare class SkillDispatcher {
32
+ private readonly registry;
33
+ private readonly llm;
34
+ private readonly routerPromptTemplate;
35
+ private readonly triggerHintSeparator;
36
+ private readonly disableRuleRouting;
37
+ constructor(registry: SkillRegistry, llm: LLMClient, options?: SkillDispatcherOptions);
38
+ /**
39
+ * 路由:返回最匹配的 Skill,或 undefined(无匹配时)。
40
+ *
41
+ * - 只有 0 个可见 Skill → undefined
42
+ * - 只有 1 个可见 Skill → 直接返回(跳过路由开销)
43
+ * - 多个 Skill → 规则路由 → LLM 路由
44
+ */
45
+ dispatch(query: string): Promise<IAgentSkill | undefined>;
46
+ /**
47
+ * 规则路由:基于 triggerHint 关键词匹配。
48
+ * 将 triggerHint 按分隔符拆分,只要 query 包含其中一个词组,即命中。
49
+ */
50
+ private ruleRoute;
51
+ /**
52
+ * LLM 路由:把所有可见 Skill 描述给 LLM,让它选出 name。
53
+ * 三级兜底匹配:精确匹配 → startsWith → includes。
54
+ */
55
+ private llmRoute;
56
+ }
57
+ //# sourceMappingURL=SkillDispatcher.d.ts.map