@chatbotkit/agent 1.26.3 → 1.27.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 +67 -0
- package/dist/cjs/agent.cjs +69 -19
- package/dist/cjs/agent.d.ts +4 -2
- package/dist/cjs/index.cjs +4 -1
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/skills.cjs +116 -0
- package/dist/cjs/skills.d.ts +18 -0
- package/dist/esm/agent.d.ts +4 -2
- package/dist/esm/agent.js +69 -19
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/skills.d.ts +18 -0
- package/dist/esm/skills.js +111 -0
- package/package.json +24 -2
package/README.md
CHANGED
|
@@ -9,6 +9,20 @@
|
|
|
9
9
|
|
|
10
10
|
Build autonomous AI agents that can use custom tools and execute complex tasks with the full power of the ChatBotKit platform.
|
|
11
11
|
|
|
12
|
+
## Why ChatBotKit?
|
|
13
|
+
|
|
14
|
+
**Build lighter, future-proof AI agents.** When you build with ChatBotKit, the heavy lifting happens on our servers—not in your application. This architectural advantage delivers:
|
|
15
|
+
|
|
16
|
+
- 🪶 **Lightweight Agents**: Your agents stay lean because complex AI processing, model orchestration, and tool execution happen server-side. Less code in your app means faster load times and simpler maintenance.
|
|
17
|
+
|
|
18
|
+
- 🛡️ **Robust & Streamlined**: Server-side processing provides a more reliable experience with built-in error handling, automatic retries, and consistent behavior across all platforms.
|
|
19
|
+
|
|
20
|
+
- 🔄 **Backward & Forward Compatible**: As AI technology evolves—new models, new capabilities, new paradigms—your agents automatically benefit. No code changes required on your end.
|
|
21
|
+
|
|
22
|
+
- 🔮 **Future-Proof**: Agents you build today will remain capable tomorrow. When we add support for new AI models or capabilities, your existing agents gain those powers without any updates to your codebase.
|
|
23
|
+
|
|
24
|
+
This means you can focus on building great user experiences while ChatBotKit handles the complexity of the ever-changing AI landscape.
|
|
25
|
+
|
|
12
26
|
## Installation
|
|
13
27
|
|
|
14
28
|
```bash
|
|
@@ -178,6 +192,59 @@ The `execute` mode provides system tools for task management:
|
|
|
178
192
|
- **`progress`** - Track completion status and blockers
|
|
179
193
|
- **`exit`** - Signal task completion with status code
|
|
180
194
|
|
|
195
|
+
### Skills Loading
|
|
196
|
+
|
|
197
|
+
Load skills from local directories and pass them as a feature to the agent. Skills are defined using `SKILL.md` files with front matter containing name and description.
|
|
198
|
+
|
|
199
|
+
```javascript
|
|
200
|
+
import { execute, loadSkills, createSkillsFeature } from '@chatbotkit/agent'
|
|
201
|
+
import { ChatBotKit } from '@chatbotkit/sdk'
|
|
202
|
+
|
|
203
|
+
const client = new ChatBotKit({ secret: process.env.CHATBOTKIT_API_TOKEN })
|
|
204
|
+
|
|
205
|
+
// Load skills from directories
|
|
206
|
+
const skillsResult = await loadSkills(['./skills'], { watch: true })
|
|
207
|
+
|
|
208
|
+
// Create the skills feature for the API
|
|
209
|
+
const skillsFeature = createSkillsFeature(skillsResult.skills)
|
|
210
|
+
|
|
211
|
+
const stream = execute({
|
|
212
|
+
client,
|
|
213
|
+
model: 'gpt-4o',
|
|
214
|
+
messages: [{ type: 'user', text: 'Help me with my task' }],
|
|
215
|
+
extensions: {
|
|
216
|
+
features: [skillsFeature],
|
|
217
|
+
},
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
for await (const event of stream) {
|
|
221
|
+
// Handle events
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Clean up when done
|
|
225
|
+
skillsResult.close()
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
#### SKILL.md Format
|
|
229
|
+
|
|
230
|
+
Create a `SKILL.md` file in each skill directory:
|
|
231
|
+
|
|
232
|
+
```markdown
|
|
233
|
+
---
|
|
234
|
+
name: My Skill
|
|
235
|
+
description: A brief description of what this skill does
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
# My Skill
|
|
239
|
+
|
|
240
|
+
Additional documentation for the skill...
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
#### Skills API
|
|
244
|
+
|
|
245
|
+
- **`loadSkills(directories, options)`** - Load skills from directories containing SKILL.md files
|
|
246
|
+
- **`createSkillsFeature(skills)`** - Create a feature configuration for the API
|
|
247
|
+
|
|
181
248
|
## Documentation
|
|
182
249
|
|
|
183
250
|
For comprehensive information about the ChatBotKit Agent SDK, including detailed documentation on its functionalities, helper methods, and configuration options, please visit our [type documentation page](https://chatbotkit.github.io/node-sdk/modules/_chatbotkit_agent.html).
|
package/dist/cjs/agent.cjs
CHANGED
|
@@ -5,7 +5,7 @@ exports.execute = execute;
|
|
|
5
5
|
const zod_1 = require("zod");
|
|
6
6
|
const zod_to_json_schema_1 = require("zod-to-json-schema");
|
|
7
7
|
async function* complete(options) {
|
|
8
|
-
const { client, tools, ...request } = options;
|
|
8
|
+
const { client, tools, abortSignal, ...request } = options;
|
|
9
9
|
const channelToTool = new Map();
|
|
10
10
|
const functions = tools
|
|
11
11
|
? Object.entries(tools).map(([name, tool]) => {
|
|
@@ -47,10 +47,13 @@ async function* complete(options) {
|
|
|
47
47
|
.complete(null, {
|
|
48
48
|
...request,
|
|
49
49
|
functions,
|
|
50
|
+
limits: {
|
|
51
|
+
iterations: 1,
|
|
52
|
+
},
|
|
50
53
|
})
|
|
51
|
-
.stream();
|
|
54
|
+
.stream({ abortSignal });
|
|
52
55
|
const toolEventQueue = [];
|
|
53
|
-
const
|
|
56
|
+
const runningToolPromises = [];
|
|
54
57
|
const executeToolAsync = async (channel, name, tool, args) => {
|
|
55
58
|
try {
|
|
56
59
|
let parsedArgs = args;
|
|
@@ -88,11 +91,11 @@ async function* complete(options) {
|
|
|
88
91
|
message: { error: errorMessage },
|
|
89
92
|
});
|
|
90
93
|
}
|
|
91
|
-
finally {
|
|
92
|
-
runningTools.delete(channel);
|
|
93
|
-
}
|
|
94
94
|
};
|
|
95
95
|
for await (const event of stream) {
|
|
96
|
+
if (abortSignal?.aborted) {
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
96
99
|
while (toolEventQueue.length > 0) {
|
|
97
100
|
const toolEvent = toolEventQueue.shift();
|
|
98
101
|
if (toolEvent) {
|
|
@@ -116,9 +119,7 @@ async function* complete(options) {
|
|
|
116
119
|
},
|
|
117
120
|
};
|
|
118
121
|
const toolPromise = executeToolAsync(channel, name, tool, args);
|
|
119
|
-
|
|
120
|
-
toolPromise.catch(() => {
|
|
121
|
-
});
|
|
122
|
+
runningToolPromises.push(toolPromise);
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
125
|
yield event;
|
|
@@ -129,8 +130,8 @@ async function* complete(options) {
|
|
|
129
130
|
yield toolEvent;
|
|
130
131
|
}
|
|
131
132
|
}
|
|
132
|
-
|
|
133
|
-
await
|
|
133
|
+
if (runningToolPromises.length > 0) {
|
|
134
|
+
await Promise.allSettled(runningToolPromises);
|
|
134
135
|
while (toolEventQueue.length > 0) {
|
|
135
136
|
const toolEvent = toolEventQueue.shift();
|
|
136
137
|
if (toolEvent) {
|
|
@@ -140,9 +141,10 @@ async function* complete(options) {
|
|
|
140
141
|
}
|
|
141
142
|
}
|
|
142
143
|
async function* execute(options) {
|
|
143
|
-
const { client, tools = {}, maxIterations =
|
|
144
|
-
const messages =
|
|
144
|
+
const { client, tools = {}, maxIterations = 100, abortSignal, ...request } = options;
|
|
145
|
+
const messages = request.messages || [];
|
|
145
146
|
let exitResult = null;
|
|
147
|
+
let internalAbort = null;
|
|
146
148
|
const systemTools = {
|
|
147
149
|
plan: {
|
|
148
150
|
description: 'Create or update a plan for approaching the task. Break down the task into clear, actionable steps. Use this at the start and whenever you need to revise your approach.',
|
|
@@ -209,8 +211,29 @@ async function* execute(options) {
|
|
|
209
211
|
};
|
|
210
212
|
},
|
|
211
213
|
},
|
|
214
|
+
abort: {
|
|
215
|
+
description: 'Immediately abort the current task. Use this when the user explicitly asks you to stop, cancel, or abort what you are doing. Set hard to true to kill running processes immediately.',
|
|
216
|
+
input: zod_1.z.object({
|
|
217
|
+
reason: zod_1.z.string().optional().describe('Brief reason for aborting'),
|
|
218
|
+
hard: zod_1.z
|
|
219
|
+
.boolean()
|
|
220
|
+
.optional()
|
|
221
|
+
.describe('If true, immediately kill running processes. If false (default), finish the current operation gracefully.'),
|
|
222
|
+
}),
|
|
223
|
+
handler: async (input) => {
|
|
224
|
+
const reason = input.reason || 'aborted by user request';
|
|
225
|
+
exitResult = { code: 1, message: reason };
|
|
226
|
+
if (input.hard && internalAbort) {
|
|
227
|
+
internalAbort.abort(reason);
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
success: true,
|
|
231
|
+
message: `Task aborted: ${reason}`,
|
|
232
|
+
};
|
|
233
|
+
},
|
|
234
|
+
},
|
|
212
235
|
};
|
|
213
|
-
const allTools = { ...
|
|
236
|
+
const allTools = { ...tools, ...systemTools };
|
|
214
237
|
const systemInstruction = `
|
|
215
238
|
${options.extensions?.backstory || ''}
|
|
216
239
|
|
|
@@ -222,31 +245,58 @@ The goal is to complete the assigned task efficiently and effectively. Follow th
|
|
|
222
245
|
2. **Track Progress**: Regularly use the 'progress' function to update status and identify issues
|
|
223
246
|
3. **Use Tools**: Leverage available tools to accomplish each step of your plan
|
|
224
247
|
4. **Exit When Done**: Call the 'exit' function with code 0 when successful, or non-zero code if unable to complete
|
|
225
|
-
5. **
|
|
248
|
+
5. **Abort**: If the user asks you to stop, cancel, or abort, call the 'abort' function immediately. Use hard=true if processes are running that need to be killed right away.
|
|
249
|
+
6. **Be Autonomous**: Work through the task systematically without waiting for additional input
|
|
250
|
+
7. **Be Responsive**: If the user sends a new message while you are working, acknowledge it briefly and adjust your approach if needed. Always prioritize user input over your current plan.
|
|
226
251
|
`.trim();
|
|
227
252
|
let iteration = 0;
|
|
228
253
|
while (iteration < maxIterations && exitResult === null) {
|
|
254
|
+
if (abortSignal?.aborted) {
|
|
255
|
+
exitResult = {
|
|
256
|
+
code: 1,
|
|
257
|
+
message: 'Task execution aborted',
|
|
258
|
+
};
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
229
261
|
iteration++;
|
|
230
262
|
yield { type: 'iteration', data: { iteration } };
|
|
263
|
+
let lastEndReason = null;
|
|
264
|
+
internalAbort = new AbortController();
|
|
265
|
+
if (abortSignal?.aborted) {
|
|
266
|
+
internalAbort.abort(abortSignal.reason);
|
|
267
|
+
}
|
|
268
|
+
else if (abortSignal) {
|
|
269
|
+
const capturedAbort = internalAbort;
|
|
270
|
+
abortSignal.addEventListener('abort', () => capturedAbort.abort(abortSignal.reason), { once: true });
|
|
271
|
+
}
|
|
272
|
+
const iterSignal = internalAbort.signal;
|
|
231
273
|
for await (const event of complete({
|
|
232
274
|
...request,
|
|
233
275
|
client,
|
|
234
276
|
messages,
|
|
235
277
|
tools: allTools,
|
|
278
|
+
abortSignal: iterSignal,
|
|
236
279
|
extensions: {
|
|
237
280
|
...options.extensions,
|
|
238
281
|
backstory: systemInstruction,
|
|
239
282
|
},
|
|
240
283
|
})) {
|
|
284
|
+
if (event.type === 'message') {
|
|
285
|
+
messages.push(event.data);
|
|
286
|
+
}
|
|
287
|
+
if (event.type === 'result') {
|
|
288
|
+
if (event.data.end.reason) {
|
|
289
|
+
lastEndReason = event.data.end.reason;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
241
292
|
yield event;
|
|
242
293
|
}
|
|
243
294
|
if (exitResult) {
|
|
244
295
|
break;
|
|
245
296
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
});
|
|
297
|
+
if (lastEndReason === 'stop') {
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
250
300
|
}
|
|
251
301
|
if (exitResult === null) {
|
|
252
302
|
exitResult = {
|
package/dist/cjs/agent.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
export function complete(options: ConversationCompleteRequest & {
|
|
1
|
+
export function complete(options: Omit<ConversationCompleteRequest, "functions" | "limits"> & {
|
|
2
2
|
client: ChatBotKit;
|
|
3
3
|
tools?: Tools;
|
|
4
|
+
abortSignal?: AbortSignal;
|
|
4
5
|
}): AsyncGenerator<ConversationCompleteStreamType | ToolCallStartEvent | ToolCallEndEvent | ToolCallErrorEvent, void, unknown>;
|
|
5
|
-
export function execute(options: ConversationCompleteRequest & {
|
|
6
|
+
export function execute(options: Omit<ConversationCompleteRequest, "functions" | "limits"> & {
|
|
6
7
|
client: ChatBotKit;
|
|
7
8
|
tools?: Tools;
|
|
8
9
|
maxIterations?: number;
|
|
10
|
+
abortSignal?: AbortSignal;
|
|
9
11
|
}): AsyncGenerator<ConversationCompleteStreamType | ToolCallStartEvent | ToolCallEndEvent | ToolCallErrorEvent | IterationEvent | ExitEvent, void, unknown>;
|
|
10
12
|
export type ZodObject = import("zod").ZodObject<any>;
|
|
11
13
|
export type ChatBotKit = import("@chatbotkit/sdk").ChatBotKit;
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.tools = exports.execute = exports.complete = void 0;
|
|
3
|
+
exports.createSkillsFeature = exports.loadSkills = exports.tools = exports.execute = exports.complete = void 0;
|
|
4
4
|
var agent_js_1 = require("./agent.cjs");
|
|
5
5
|
Object.defineProperty(exports, "complete", { enumerable: true, get: function () { return agent_js_1.complete; } });
|
|
6
6
|
Object.defineProperty(exports, "execute", { enumerable: true, get: function () { return agent_js_1.execute; } });
|
|
7
7
|
var tools_js_1 = require("./tools.cjs");
|
|
8
8
|
Object.defineProperty(exports, "tools", { enumerable: true, get: function () { return tools_js_1.tools; } });
|
|
9
|
+
var skills_js_1 = require("./skills.cjs");
|
|
10
|
+
Object.defineProperty(exports, "loadSkills", { enumerable: true, get: function () { return skills_js_1.loadSkills; } });
|
|
11
|
+
Object.defineProperty(exports, "createSkillsFeature", { enumerable: true, get: function () { return skills_js_1.createSkillsFeature; } });
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadSkills = loadSkills;
|
|
4
|
+
exports.createSkillsFeature = createSkillsFeature;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const promises_1 = require("fs/promises");
|
|
7
|
+
const js_yaml_1 = tslib_1.__importDefault(require("js-yaml"));
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
function parseFrontMatter(content) {
|
|
10
|
+
const frontMatterRegex = /^---\s*\n([\s\S]*?)\n---/;
|
|
11
|
+
const match = content.match(frontMatterRegex);
|
|
12
|
+
if (!match) {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const frontMatter = match[1];
|
|
17
|
+
const parsed = (js_yaml_1.default.load(frontMatter));
|
|
18
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
name: typeof parsed.name === 'string' ? parsed.name : undefined,
|
|
23
|
+
description: typeof parsed.description === 'string' ? parsed.description : undefined,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function loadSkillFromDirectory(skillDir) {
|
|
31
|
+
const skillFilePath = (0, path_1.join)(skillDir, 'SKILL.md');
|
|
32
|
+
try {
|
|
33
|
+
const content = await (0, promises_1.readFile)(skillFilePath, 'utf-8');
|
|
34
|
+
const { name, description } = parseFrontMatter(content);
|
|
35
|
+
if (!name || !description) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
name,
|
|
40
|
+
description,
|
|
41
|
+
path: (0, path_1.resolve)(skillDir),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function loadSkills(directories, options = {}) {
|
|
49
|
+
const skills = ([]);
|
|
50
|
+
const watchControllers = ([]);
|
|
51
|
+
async function scanDirectory(baseDir) {
|
|
52
|
+
try {
|
|
53
|
+
const entries = await (0, promises_1.readdir)(baseDir);
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
const entryPath = (0, path_1.join)(baseDir, entry);
|
|
56
|
+
const entryStat = await (0, promises_1.stat)(entryPath);
|
|
57
|
+
if (entryStat.isDirectory()) {
|
|
58
|
+
const skill = await loadSkillFromDirectory(entryPath);
|
|
59
|
+
if (skill) {
|
|
60
|
+
const existingIdx = skills.findIndex((s) => s.path === skill.path);
|
|
61
|
+
if (existingIdx !== -1) {
|
|
62
|
+
skills[existingIdx] = skill;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
skills.push(skill);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const dir of directories) {
|
|
75
|
+
await scanDirectory(dir);
|
|
76
|
+
}
|
|
77
|
+
if (options.watch) {
|
|
78
|
+
for (const dir of directories) {
|
|
79
|
+
const controller = new AbortController();
|
|
80
|
+
watchControllers.push(controller);
|
|
81
|
+
(async () => {
|
|
82
|
+
try {
|
|
83
|
+
const watcher = (0, promises_1.watch)(dir, {
|
|
84
|
+
recursive: true,
|
|
85
|
+
signal: controller.signal,
|
|
86
|
+
});
|
|
87
|
+
for await (const event of watcher) {
|
|
88
|
+
if (event.filename?.endsWith('SKILL.md')) {
|
|
89
|
+
await scanDirectory(dir);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
if (err instanceof Error &&
|
|
95
|
+
err.name !== 'AbortError' &&
|
|
96
|
+
!err.message.includes('AbortError')) {
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
})();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
skills,
|
|
104
|
+
close: () => {
|
|
105
|
+
for (const controller of watchControllers) {
|
|
106
|
+
controller.abort();
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function createSkillsFeature(skills) {
|
|
112
|
+
return {
|
|
113
|
+
name: ('skills'),
|
|
114
|
+
options: { skills },
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function loadSkills(directories: string[], options?: {
|
|
2
|
+
watch?: boolean;
|
|
3
|
+
}): Promise<SkillsResult>;
|
|
4
|
+
export function createSkillsFeature(skills: SkillDefinition[]): {
|
|
5
|
+
name: "skills";
|
|
6
|
+
options: {
|
|
7
|
+
skills: SkillDefinition[];
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
export type SkillDefinition = {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
path: string;
|
|
14
|
+
};
|
|
15
|
+
export type SkillsResult = {
|
|
16
|
+
skills: SkillDefinition[];
|
|
17
|
+
close: () => void;
|
|
18
|
+
};
|
package/dist/esm/agent.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
export function complete(options: ConversationCompleteRequest & {
|
|
1
|
+
export function complete(options: Omit<ConversationCompleteRequest, "functions" | "limits"> & {
|
|
2
2
|
client: ChatBotKit;
|
|
3
3
|
tools?: Tools;
|
|
4
|
+
abortSignal?: AbortSignal;
|
|
4
5
|
}): AsyncGenerator<ConversationCompleteStreamType | ToolCallStartEvent | ToolCallEndEvent | ToolCallErrorEvent, void, unknown>;
|
|
5
|
-
export function execute(options: ConversationCompleteRequest & {
|
|
6
|
+
export function execute(options: Omit<ConversationCompleteRequest, "functions" | "limits"> & {
|
|
6
7
|
client: ChatBotKit;
|
|
7
8
|
tools?: Tools;
|
|
8
9
|
maxIterations?: number;
|
|
10
|
+
abortSignal?: AbortSignal;
|
|
9
11
|
}): AsyncGenerator<ConversationCompleteStreamType | ToolCallStartEvent | ToolCallEndEvent | ToolCallErrorEvent | IterationEvent | ExitEvent, void, unknown>;
|
|
10
12
|
export type ZodObject = import("zod").ZodObject<any>;
|
|
11
13
|
export type ChatBotKit = import("@chatbotkit/sdk").ChatBotKit;
|
package/dist/esm/agent.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
3
3
|
export async function* complete(options) {
|
|
4
|
-
const { client, tools, ...request } = options;
|
|
4
|
+
const { client, tools, abortSignal, ...request } = options;
|
|
5
5
|
const channelToTool = new Map();
|
|
6
6
|
const functions = tools
|
|
7
7
|
? Object.entries(tools).map(([name, tool]) => {
|
|
@@ -43,10 +43,13 @@ export async function* complete(options) {
|
|
|
43
43
|
.complete(null, {
|
|
44
44
|
...request,
|
|
45
45
|
functions,
|
|
46
|
+
limits: {
|
|
47
|
+
iterations: 1,
|
|
48
|
+
},
|
|
46
49
|
})
|
|
47
|
-
.stream();
|
|
50
|
+
.stream({ abortSignal });
|
|
48
51
|
const toolEventQueue = [];
|
|
49
|
-
const
|
|
52
|
+
const runningToolPromises = [];
|
|
50
53
|
const executeToolAsync = async (channel, name, tool, args) => {
|
|
51
54
|
try {
|
|
52
55
|
let parsedArgs = args;
|
|
@@ -84,11 +87,11 @@ export async function* complete(options) {
|
|
|
84
87
|
message: { error: errorMessage },
|
|
85
88
|
});
|
|
86
89
|
}
|
|
87
|
-
finally {
|
|
88
|
-
runningTools.delete(channel);
|
|
89
|
-
}
|
|
90
90
|
};
|
|
91
91
|
for await (const event of stream) {
|
|
92
|
+
if (abortSignal?.aborted) {
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
92
95
|
while (toolEventQueue.length > 0) {
|
|
93
96
|
const toolEvent = toolEventQueue.shift();
|
|
94
97
|
if (toolEvent) {
|
|
@@ -112,9 +115,7 @@ export async function* complete(options) {
|
|
|
112
115
|
},
|
|
113
116
|
};
|
|
114
117
|
const toolPromise = executeToolAsync(channel, name, tool, args);
|
|
115
|
-
|
|
116
|
-
toolPromise.catch(() => {
|
|
117
|
-
});
|
|
118
|
+
runningToolPromises.push(toolPromise);
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
121
|
yield event;
|
|
@@ -125,8 +126,8 @@ export async function* complete(options) {
|
|
|
125
126
|
yield toolEvent;
|
|
126
127
|
}
|
|
127
128
|
}
|
|
128
|
-
|
|
129
|
-
await
|
|
129
|
+
if (runningToolPromises.length > 0) {
|
|
130
|
+
await Promise.allSettled(runningToolPromises);
|
|
130
131
|
while (toolEventQueue.length > 0) {
|
|
131
132
|
const toolEvent = toolEventQueue.shift();
|
|
132
133
|
if (toolEvent) {
|
|
@@ -136,9 +137,10 @@ export async function* complete(options) {
|
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
139
|
export async function* execute(options) {
|
|
139
|
-
const { client, tools = {}, maxIterations =
|
|
140
|
-
const messages =
|
|
140
|
+
const { client, tools = {}, maxIterations = 100, abortSignal, ...request } = options;
|
|
141
|
+
const messages = request.messages || [];
|
|
141
142
|
let exitResult = null;
|
|
143
|
+
let internalAbort = null;
|
|
142
144
|
const systemTools = {
|
|
143
145
|
plan: {
|
|
144
146
|
description: 'Create or update a plan for approaching the task. Break down the task into clear, actionable steps. Use this at the start and whenever you need to revise your approach.',
|
|
@@ -205,8 +207,29 @@ export async function* execute(options) {
|
|
|
205
207
|
};
|
|
206
208
|
},
|
|
207
209
|
},
|
|
210
|
+
abort: {
|
|
211
|
+
description: 'Immediately abort the current task. Use this when the user explicitly asks you to stop, cancel, or abort what you are doing. Set hard to true to kill running processes immediately.',
|
|
212
|
+
input: z.object({
|
|
213
|
+
reason: z.string().optional().describe('Brief reason for aborting'),
|
|
214
|
+
hard: z
|
|
215
|
+
.boolean()
|
|
216
|
+
.optional()
|
|
217
|
+
.describe('If true, immediately kill running processes. If false (default), finish the current operation gracefully.'),
|
|
218
|
+
}),
|
|
219
|
+
handler: async (input) => {
|
|
220
|
+
const reason = input.reason || 'aborted by user request';
|
|
221
|
+
exitResult = { code: 1, message: reason };
|
|
222
|
+
if (input.hard && internalAbort) {
|
|
223
|
+
internalAbort.abort(reason);
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
success: true,
|
|
227
|
+
message: `Task aborted: ${reason}`,
|
|
228
|
+
};
|
|
229
|
+
},
|
|
230
|
+
},
|
|
208
231
|
};
|
|
209
|
-
const allTools = { ...
|
|
232
|
+
const allTools = { ...tools, ...systemTools };
|
|
210
233
|
const systemInstruction = `
|
|
211
234
|
${options.extensions?.backstory || ''}
|
|
212
235
|
|
|
@@ -218,31 +241,58 @@ The goal is to complete the assigned task efficiently and effectively. Follow th
|
|
|
218
241
|
2. **Track Progress**: Regularly use the 'progress' function to update status and identify issues
|
|
219
242
|
3. **Use Tools**: Leverage available tools to accomplish each step of your plan
|
|
220
243
|
4. **Exit When Done**: Call the 'exit' function with code 0 when successful, or non-zero code if unable to complete
|
|
221
|
-
5. **
|
|
244
|
+
5. **Abort**: If the user asks you to stop, cancel, or abort, call the 'abort' function immediately. Use hard=true if processes are running that need to be killed right away.
|
|
245
|
+
6. **Be Autonomous**: Work through the task systematically without waiting for additional input
|
|
246
|
+
7. **Be Responsive**: If the user sends a new message while you are working, acknowledge it briefly and adjust your approach if needed. Always prioritize user input over your current plan.
|
|
222
247
|
`.trim();
|
|
223
248
|
let iteration = 0;
|
|
224
249
|
while (iteration < maxIterations && exitResult === null) {
|
|
250
|
+
if (abortSignal?.aborted) {
|
|
251
|
+
exitResult = {
|
|
252
|
+
code: 1,
|
|
253
|
+
message: 'Task execution aborted',
|
|
254
|
+
};
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
225
257
|
iteration++;
|
|
226
258
|
yield { type: 'iteration', data: { iteration } };
|
|
259
|
+
let lastEndReason = null;
|
|
260
|
+
internalAbort = new AbortController();
|
|
261
|
+
if (abortSignal?.aborted) {
|
|
262
|
+
internalAbort.abort(abortSignal.reason);
|
|
263
|
+
}
|
|
264
|
+
else if (abortSignal) {
|
|
265
|
+
const capturedAbort = internalAbort;
|
|
266
|
+
abortSignal.addEventListener('abort', () => capturedAbort.abort(abortSignal.reason), { once: true });
|
|
267
|
+
}
|
|
268
|
+
const iterSignal = internalAbort.signal;
|
|
227
269
|
for await (const event of complete({
|
|
228
270
|
...request,
|
|
229
271
|
client,
|
|
230
272
|
messages,
|
|
231
273
|
tools: allTools,
|
|
274
|
+
abortSignal: iterSignal,
|
|
232
275
|
extensions: {
|
|
233
276
|
...options.extensions,
|
|
234
277
|
backstory: systemInstruction,
|
|
235
278
|
},
|
|
236
279
|
})) {
|
|
280
|
+
if (event.type === 'message') {
|
|
281
|
+
messages.push(event.data);
|
|
282
|
+
}
|
|
283
|
+
if (event.type === 'result') {
|
|
284
|
+
if (event.data.end.reason) {
|
|
285
|
+
lastEndReason = event.data.end.reason;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
237
288
|
yield event;
|
|
238
289
|
}
|
|
239
290
|
if (exitResult) {
|
|
240
291
|
break;
|
|
241
292
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
});
|
|
293
|
+
if (lastEndReason === 'stop') {
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
246
296
|
}
|
|
247
297
|
if (exitResult === null) {
|
|
248
298
|
exitResult = {
|
package/dist/esm/index.d.ts
CHANGED
package/dist/esm/index.js
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function loadSkills(directories: string[], options?: {
|
|
2
|
+
watch?: boolean;
|
|
3
|
+
}): Promise<SkillsResult>;
|
|
4
|
+
export function createSkillsFeature(skills: SkillDefinition[]): {
|
|
5
|
+
name: "skills";
|
|
6
|
+
options: {
|
|
7
|
+
skills: SkillDefinition[];
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
export type SkillDefinition = {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
path: string;
|
|
14
|
+
};
|
|
15
|
+
export type SkillsResult = {
|
|
16
|
+
skills: SkillDefinition[];
|
|
17
|
+
close: () => void;
|
|
18
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { readFile, readdir, stat, watch } from 'fs/promises';
|
|
2
|
+
import yaml from 'js-yaml';
|
|
3
|
+
import { join, resolve } from 'path';
|
|
4
|
+
function parseFrontMatter(content) {
|
|
5
|
+
const frontMatterRegex = /^---\s*\n([\s\S]*?)\n---/;
|
|
6
|
+
const match = content.match(frontMatterRegex);
|
|
7
|
+
if (!match) {
|
|
8
|
+
return {};
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const frontMatter = match[1];
|
|
12
|
+
const parsed = (yaml.load(frontMatter));
|
|
13
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
name: typeof parsed.name === 'string' ? parsed.name : undefined,
|
|
18
|
+
description: typeof parsed.description === 'string' ? parsed.description : undefined,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function loadSkillFromDirectory(skillDir) {
|
|
26
|
+
const skillFilePath = join(skillDir, 'SKILL.md');
|
|
27
|
+
try {
|
|
28
|
+
const content = await readFile(skillFilePath, 'utf-8');
|
|
29
|
+
const { name, description } = parseFrontMatter(content);
|
|
30
|
+
if (!name || !description) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
name,
|
|
35
|
+
description,
|
|
36
|
+
path: resolve(skillDir),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export async function loadSkills(directories, options = {}) {
|
|
44
|
+
const skills = ([]);
|
|
45
|
+
const watchControllers = ([]);
|
|
46
|
+
async function scanDirectory(baseDir) {
|
|
47
|
+
try {
|
|
48
|
+
const entries = await readdir(baseDir);
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
const entryPath = join(baseDir, entry);
|
|
51
|
+
const entryStat = await stat(entryPath);
|
|
52
|
+
if (entryStat.isDirectory()) {
|
|
53
|
+
const skill = await loadSkillFromDirectory(entryPath);
|
|
54
|
+
if (skill) {
|
|
55
|
+
const existingIdx = skills.findIndex((s) => s.path === skill.path);
|
|
56
|
+
if (existingIdx !== -1) {
|
|
57
|
+
skills[existingIdx] = skill;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
skills.push(skill);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const dir of directories) {
|
|
70
|
+
await scanDirectory(dir);
|
|
71
|
+
}
|
|
72
|
+
if (options.watch) {
|
|
73
|
+
for (const dir of directories) {
|
|
74
|
+
const controller = new AbortController();
|
|
75
|
+
watchControllers.push(controller);
|
|
76
|
+
(async () => {
|
|
77
|
+
try {
|
|
78
|
+
const watcher = watch(dir, {
|
|
79
|
+
recursive: true,
|
|
80
|
+
signal: controller.signal,
|
|
81
|
+
});
|
|
82
|
+
for await (const event of watcher) {
|
|
83
|
+
if (event.filename?.endsWith('SKILL.md')) {
|
|
84
|
+
await scanDirectory(dir);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
if (err instanceof Error &&
|
|
90
|
+
err.name !== 'AbortError' &&
|
|
91
|
+
!err.message.includes('AbortError')) {
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
})();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
skills,
|
|
99
|
+
close: () => {
|
|
100
|
+
for (const controller of watchControllers) {
|
|
101
|
+
controller.abort();
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
export function createSkillsFeature(skills) {
|
|
107
|
+
return {
|
|
108
|
+
name: ('skills'),
|
|
109
|
+
options: { skills },
|
|
110
|
+
};
|
|
111
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chatbotkit/agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.27.0",
|
|
4
4
|
"description": "ChatBotKit Agent implementation",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"engines": {
|
|
@@ -72,6 +72,26 @@
|
|
|
72
72
|
"default": "./dist/cjs/index.cjs"
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
|
+
"./skills": {
|
|
76
|
+
"import": {
|
|
77
|
+
"types": "./dist/esm/skills.d.ts",
|
|
78
|
+
"default": "./dist/esm/skills.js"
|
|
79
|
+
},
|
|
80
|
+
"require": {
|
|
81
|
+
"types": "./dist/cjs/skills.d.ts",
|
|
82
|
+
"default": "./dist/cjs/skills.cjs"
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"./skills.js": {
|
|
86
|
+
"import": {
|
|
87
|
+
"types": "./dist/esm/skills.d.ts",
|
|
88
|
+
"default": "./dist/esm/skills.js"
|
|
89
|
+
},
|
|
90
|
+
"require": {
|
|
91
|
+
"types": "./dist/cjs/skills.d.ts",
|
|
92
|
+
"default": "./dist/cjs/skills.cjs"
|
|
93
|
+
}
|
|
94
|
+
},
|
|
75
95
|
"./tools": {
|
|
76
96
|
"import": {
|
|
77
97
|
"types": "./dist/esm/tools.d.ts",
|
|
@@ -96,12 +116,14 @@
|
|
|
96
116
|
},
|
|
97
117
|
"types": "./dist/cjs/index.d.ts",
|
|
98
118
|
"dependencies": {
|
|
119
|
+
"js-yaml": "^4.1.0",
|
|
99
120
|
"tslib": "^2.6.2",
|
|
100
121
|
"zod": "^3.25.76",
|
|
101
122
|
"zod-to-json-schema": "^3.24.6",
|
|
102
|
-
"@chatbotkit/sdk": "1.
|
|
123
|
+
"@chatbotkit/sdk": "1.27.0"
|
|
103
124
|
},
|
|
104
125
|
"devDependencies": {
|
|
126
|
+
"@types/js-yaml": "^4.0.9",
|
|
105
127
|
"npm-run-all": "^4.1.5",
|
|
106
128
|
"typedoc": "^0.28.14",
|
|
107
129
|
"typedoc-plugin-markdown": "^4.9.0",
|