@agentscope-ai/agentscope 0.0.2
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/dist/agent/index.d.mts +234 -0
- package/dist/agent/index.d.ts +234 -0
- package/dist/agent/index.js +1412 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/agent/index.mjs +1375 -0
- package/dist/agent/index.mjs.map +1 -0
- package/dist/base-BOx3UzOl.d.mts +41 -0
- package/dist/base-BoIps2RL.d.ts +41 -0
- package/dist/base-C7jwyH4Z.d.mts +52 -0
- package/dist/base-Cwi4bjze.d.ts +127 -0
- package/dist/base-DYlBMCy_.d.mts +127 -0
- package/dist/base-NX-knWOv.d.ts +52 -0
- package/dist/block-VsnHrllL.d.mts +48 -0
- package/dist/block-VsnHrllL.d.ts +48 -0
- package/dist/event/index.d.mts +181 -0
- package/dist/event/index.d.ts +181 -0
- package/dist/event/index.js +58 -0
- package/dist/event/index.js.map +1 -0
- package/dist/event/index.mjs +33 -0
- package/dist/event/index.mjs.map +1 -0
- package/dist/formatter/index.d.mts +187 -0
- package/dist/formatter/index.d.ts +187 -0
- package/dist/formatter/index.js +647 -0
- package/dist/formatter/index.js.map +1 -0
- package/dist/formatter/index.mjs +616 -0
- package/dist/formatter/index.mjs.map +1 -0
- package/dist/index-BTJDlKvQ.d.mts +195 -0
- package/dist/index-BcatlwXQ.d.ts +195 -0
- package/dist/index-CAxQAkiP.d.mts +21 -0
- package/dist/index-CAxQAkiP.d.ts +21 -0
- package/dist/mcp/index.d.mts +9 -0
- package/dist/mcp/index.d.ts +9 -0
- package/dist/mcp/index.js +432 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/index.mjs +408 -0
- package/dist/mcp/index.mjs.map +1 -0
- package/dist/message/index.d.mts +10 -0
- package/dist/message/index.d.ts +10 -0
- package/dist/message/index.js +67 -0
- package/dist/message/index.js.map +1 -0
- package/dist/message/index.mjs +37 -0
- package/dist/message/index.mjs.map +1 -0
- package/dist/message-CkN21KaY.d.mts +99 -0
- package/dist/message-CzLeTlua.d.ts +99 -0
- package/dist/model/index.d.mts +377 -0
- package/dist/model/index.d.ts +377 -0
- package/dist/model/index.js +1880 -0
- package/dist/model/index.js.map +1 -0
- package/dist/model/index.mjs +1849 -0
- package/dist/model/index.mjs.map +1 -0
- package/dist/storage/index.d.mts +68 -0
- package/dist/storage/index.d.ts +68 -0
- package/dist/storage/index.js +250 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/index.mjs +212 -0
- package/dist/storage/index.mjs.map +1 -0
- package/dist/tool/index.d.mts +311 -0
- package/dist/tool/index.d.ts +311 -0
- package/dist/tool/index.js +1494 -0
- package/dist/tool/index.js.map +1 -0
- package/dist/tool/index.mjs +1447 -0
- package/dist/tool/index.mjs.map +1 -0
- package/dist/toolkit-CEpulFi0.d.ts +99 -0
- package/dist/toolkit-CGEZSZPa.d.mts +99 -0
- package/jest.config.js +11 -0
- package/package.json +92 -0
- package/src/_utils/common.ts +104 -0
- package/src/_utils/index.ts +1 -0
- package/src/agent/agent-base.ts +0 -0
- package/src/agent/agent.test.ts +1028 -0
- package/src/agent/agent.ts +1032 -0
- package/src/agent/index.ts +2 -0
- package/src/agent/interfaces.ts +23 -0
- package/src/agent/test-compression.ts +72 -0
- package/src/event/index.ts +250 -0
- package/src/formatter/base.ts +133 -0
- package/src/formatter/dashscope-chat-formatter.test.ts +372 -0
- package/src/formatter/dashscope-chat-formatter.ts +163 -0
- package/src/formatter/deepseek-chat-formatter.ts +130 -0
- package/src/formatter/index.ts +5 -0
- package/src/formatter/ollama-chat-formatter.ts +67 -0
- package/src/formatter/openai-chat-formatter.test.ts +263 -0
- package/src/formatter/openai-chat-formatter.ts +301 -0
- package/src/formatter/openai.md +767 -0
- package/src/mcp/base.ts +114 -0
- package/src/mcp/http.test.ts +303 -0
- package/src/mcp/http.ts +224 -0
- package/src/mcp/index.ts +2 -0
- package/src/mcp/stdio.test.ts +91 -0
- package/src/mcp/stdio.ts +119 -0
- package/src/message/block.ts +60 -0
- package/src/message/enums.ts +4 -0
- package/src/message/index.ts +12 -0
- package/src/message/message.test.ts +80 -0
- package/src/message/message.ts +131 -0
- package/src/model/base.ts +226 -0
- package/src/model/dashscope-model.test.ts +335 -0
- package/src/model/dashscope-model.ts +441 -0
- package/src/model/deepseek-model.test.ts +279 -0
- package/src/model/deepseek-model.ts +401 -0
- package/src/model/index.ts +7 -0
- package/src/model/ollama-model.test.ts +307 -0
- package/src/model/ollama-model.ts +356 -0
- package/src/model/openai-model.ts +327 -0
- package/src/model/response.ts +22 -0
- package/src/model/usage.ts +12 -0
- package/src/storage/base.ts +52 -0
- package/src/storage/file-system.test.ts +587 -0
- package/src/storage/file-system.ts +269 -0
- package/src/storage/index.ts +2 -0
- package/src/tool/base.ts +23 -0
- package/src/tool/bash.test.ts +174 -0
- package/src/tool/bash.ts +152 -0
- package/src/tool/edit.test.ts +83 -0
- package/src/tool/edit.ts +95 -0
- package/src/tool/glob.test.ts +63 -0
- package/src/tool/glob.ts +166 -0
- package/src/tool/grep.test.ts +74 -0
- package/src/tool/grep.ts +256 -0
- package/src/tool/index.ts +10 -0
- package/src/tool/read.test.ts +77 -0
- package/src/tool/read.ts +117 -0
- package/src/tool/response.ts +82 -0
- package/src/tool/task.test.ts +299 -0
- package/src/tool/task.ts +399 -0
- package/src/tool/toolkit.test.ts +636 -0
- package/src/tool/toolkit.ts +601 -0
- package/src/tool/write.test.ts +52 -0
- package/src/tool/write.ts +57 -0
- package/src/type/index.ts +52 -0
- package/tsconfig.build.json +4 -0
- package/tsconfig.cjs.json +11 -0
- package/tsconfig.esm.json +10 -0
- package/tsconfig.json +14 -0
- package/tsup.config.ts +20 -0
- package/typedoc.json +52 -0
|
@@ -0,0 +1,1447 @@
|
|
|
1
|
+
// src/tool/response.ts
|
|
2
|
+
function createToolResponse({
|
|
3
|
+
content,
|
|
4
|
+
state,
|
|
5
|
+
id = crypto.randomUUID(),
|
|
6
|
+
createdAt = (/* @__PURE__ */ new Date()).toISOString(),
|
|
7
|
+
metadata = {},
|
|
8
|
+
stream = false,
|
|
9
|
+
isLast = true,
|
|
10
|
+
isInterrupted = false
|
|
11
|
+
}) {
|
|
12
|
+
return {
|
|
13
|
+
content,
|
|
14
|
+
id,
|
|
15
|
+
createdAt,
|
|
16
|
+
metadata,
|
|
17
|
+
state,
|
|
18
|
+
stream,
|
|
19
|
+
isLast,
|
|
20
|
+
isInterrupted
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function isToolResponse(obj) {
|
|
24
|
+
return obj && typeof obj === "object" && typeof obj.id === "string" && typeof obj.createdAt === "string" && Array.isArray(obj.content) && typeof obj.metadata === "object" && typeof obj.stream === "boolean" && typeof obj.isLast === "boolean" && typeof obj.isInterrupted === "boolean" && ["success", "error", "interrupted", "running"].includes(obj.state);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/tool/toolkit.ts
|
|
28
|
+
import * as fs from "fs";
|
|
29
|
+
import * as path from "path";
|
|
30
|
+
import { Validator } from "@cfworker/json-schema";
|
|
31
|
+
import matter from "gray-matter";
|
|
32
|
+
import { z } from "zod";
|
|
33
|
+
|
|
34
|
+
// src/_utils/common.ts
|
|
35
|
+
import { jsonrepair } from "jsonrepair";
|
|
36
|
+
function _jsonLoadsWithRepair(input) {
|
|
37
|
+
try {
|
|
38
|
+
const jsonObj = JSON.parse(input);
|
|
39
|
+
if (typeof jsonObj === "object" && jsonObj !== null && !Array.isArray(jsonObj)) {
|
|
40
|
+
return jsonObj;
|
|
41
|
+
} else {
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
try {
|
|
46
|
+
const repairedString = jsonrepair(input);
|
|
47
|
+
const jsonObj = JSON.parse(repairedString);
|
|
48
|
+
if (typeof jsonObj === "object" && jsonObj !== null && !Array.isArray(jsonObj)) {
|
|
49
|
+
return jsonObj;
|
|
50
|
+
} else {
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.error(`Failed to parse JSON "${input}" with error:`, e);
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/tool/toolkit.ts
|
|
61
|
+
var Toolkit = class {
|
|
62
|
+
tools;
|
|
63
|
+
skills;
|
|
64
|
+
skillDirs;
|
|
65
|
+
// The cache mapping from the skill name to its corresponding tool name in the toolkit.
|
|
66
|
+
_skillCache;
|
|
67
|
+
/**
|
|
68
|
+
* Initializes a new instance of the Toolkit class.
|
|
69
|
+
* @param config - The configuration object for initializing the toolkit, which can include an array of tools, an array of skill paths, an array of skill directory paths, and a boolean indicating whether to include the built-in skill tool for reading SKILL.md files.
|
|
70
|
+
* @param config.tools - An array of tool definitions to register in the toolkit.
|
|
71
|
+
* @param config.skills - An array of file paths pointing to individual skills.
|
|
72
|
+
* @param config.skillDirs - An array of directory paths, where each directory can contain multiple skills in its subdirectories.
|
|
73
|
+
* @param config.builtInSkillTool - A boolean flag indicating whether to include the built-in skill tool for reading SKILL.md files.
|
|
74
|
+
*/
|
|
75
|
+
constructor(config) {
|
|
76
|
+
const { tools = [], skills = [], skillDirs = [], builtInSkillTool = true } = config || {};
|
|
77
|
+
this.tools = [];
|
|
78
|
+
if (builtInSkillTool) {
|
|
79
|
+
this.tools.push({
|
|
80
|
+
type: "function",
|
|
81
|
+
name: "Skill",
|
|
82
|
+
description: `Retrieves the full content of a skill by reading its SKILL.md file. Skills are packages of domain expertise that extend agent capabilities. Use this tool to access detailed instructions, examples, and guidelines for a specific skill.
|
|
83
|
+
|
|
84
|
+
Usage:
|
|
85
|
+
- Provide the skill name as the input parameter
|
|
86
|
+
- The tool will return the complete SKILL.md file content for that skill
|
|
87
|
+
- If the skill is not found, an error message with available skills will be returned
|
|
88
|
+
- Available skills are listed in the skills-system section of the agent prompt`,
|
|
89
|
+
inputSchema: z.object({ name: z.string().describe("The name of the skill") }),
|
|
90
|
+
call: this._skillTool.bind(this),
|
|
91
|
+
requireUserConfirm: false
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
tools.map((tool) => {
|
|
95
|
+
this.tools.push({
|
|
96
|
+
type: "function",
|
|
97
|
+
...tool
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
this.skills = skills;
|
|
101
|
+
this.skillDirs = skillDirs;
|
|
102
|
+
this._skillCache = {};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Registers a tool function to the toolkit. The function can be either a plain function that adheres to the ToolFunction type, or an instance of a class that extends ToolBase. When registering a plain function, the name, description, and input schema must be provided explicitly. When registering a ToolBase instance, these properties will be extracted from the instance itself.
|
|
106
|
+
*
|
|
107
|
+
* @params tool - The tool function to register, which can be either a plain function with explicit properties or an instance of a class that extends ToolBase.
|
|
108
|
+
* @returns The Toolkit instance with the new tool function registered
|
|
109
|
+
* @param tool
|
|
110
|
+
*/
|
|
111
|
+
registerToolFunction(tool) {
|
|
112
|
+
this.tools.push({
|
|
113
|
+
type: "function",
|
|
114
|
+
...tool
|
|
115
|
+
});
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Registers functions from a given MCP client.
|
|
120
|
+
*
|
|
121
|
+
* @param root0
|
|
122
|
+
* @param root0.client
|
|
123
|
+
* @param root0.enabledTools
|
|
124
|
+
* @param root0.disabledTools
|
|
125
|
+
* @param root0.requireUserConfirm
|
|
126
|
+
* @returns The Toolkit instance with the new tools registered
|
|
127
|
+
*/
|
|
128
|
+
async registerMCPClient({
|
|
129
|
+
client,
|
|
130
|
+
enabledTools,
|
|
131
|
+
disabledTools = [],
|
|
132
|
+
requireUserConfirm = false
|
|
133
|
+
}) {
|
|
134
|
+
const tools = await client.listTools();
|
|
135
|
+
const appendTools = [];
|
|
136
|
+
tools.filter(
|
|
137
|
+
(tool) => !(enabledTools && !enabledTools.includes(tool.name)) && !disabledTools.includes(tool.name)
|
|
138
|
+
).forEach((tool) => {
|
|
139
|
+
this.tools.push({
|
|
140
|
+
type: "mcp",
|
|
141
|
+
mcpName: client.name,
|
|
142
|
+
...tool,
|
|
143
|
+
requireUserConfirm
|
|
144
|
+
});
|
|
145
|
+
appendTools.push(tool.name);
|
|
146
|
+
});
|
|
147
|
+
console.log(`Registered tools from MCP client '${client.name}': ${appendTools.join(", ")}`);
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Executes a registered tool function based on the provided ToolUseBlock.
|
|
152
|
+
* Note this method always returns an AsyncGenerator of ToolResponse, regardless of the tool function type.
|
|
153
|
+
*
|
|
154
|
+
* @param toolCall - The ToolUseBlock containing the tool name and input arguments
|
|
155
|
+
* @yields Incremental ToolResponse objects as they are produced by the tool function
|
|
156
|
+
* @returns The final complete ToolResponse after the tool function execution is finished
|
|
157
|
+
*/
|
|
158
|
+
async *callToolFunction(toolCall) {
|
|
159
|
+
const tool = this.tools.find((tool2) => tool2.name === toolCall.name);
|
|
160
|
+
if (!tool) {
|
|
161
|
+
const notFoundRes = createToolResponse({
|
|
162
|
+
content: [
|
|
163
|
+
{
|
|
164
|
+
id: crypto.randomUUID(),
|
|
165
|
+
type: "text",
|
|
166
|
+
text: `FunctionNotFoundError: Cannot find the function named ${toolCall.name}`
|
|
167
|
+
}
|
|
168
|
+
],
|
|
169
|
+
state: "error"
|
|
170
|
+
});
|
|
171
|
+
yield notFoundRes;
|
|
172
|
+
return notFoundRes;
|
|
173
|
+
}
|
|
174
|
+
let parsedInput;
|
|
175
|
+
try {
|
|
176
|
+
parsedInput = _jsonLoadsWithRepair(toolCall.input);
|
|
177
|
+
if (tool.inputSchema instanceof z.ZodObject) {
|
|
178
|
+
tool.inputSchema.parse(parsedInput);
|
|
179
|
+
} else {
|
|
180
|
+
const validator = new Validator(tool.inputSchema);
|
|
181
|
+
const validation = validator.validate(parsedInput);
|
|
182
|
+
if (!validation.valid) {
|
|
183
|
+
throw new Error(`Invalid input arguments: ${validation.errors}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} catch (error) {
|
|
187
|
+
const parseErrorRes = createToolResponse({
|
|
188
|
+
content: [
|
|
189
|
+
{
|
|
190
|
+
id: crypto.randomUUID(),
|
|
191
|
+
type: "text",
|
|
192
|
+
text: `InvalidArgumentError: ${String(error)}`
|
|
193
|
+
}
|
|
194
|
+
],
|
|
195
|
+
state: "error"
|
|
196
|
+
});
|
|
197
|
+
yield parseErrorRes;
|
|
198
|
+
return parseErrorRes;
|
|
199
|
+
}
|
|
200
|
+
if (!tool.call) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`Cannot execute external tool '${toolCall.name}' because no call method is defined for it in the toolkit.`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
let finalRes = null;
|
|
206
|
+
try {
|
|
207
|
+
const res = await tool.call(parsedInput);
|
|
208
|
+
if (typeof res === "string") {
|
|
209
|
+
const textRes = createToolResponse({
|
|
210
|
+
content: [
|
|
211
|
+
{
|
|
212
|
+
id: crypto.randomUUID(),
|
|
213
|
+
type: "text",
|
|
214
|
+
text: res
|
|
215
|
+
}
|
|
216
|
+
],
|
|
217
|
+
state: "success"
|
|
218
|
+
});
|
|
219
|
+
yield textRes;
|
|
220
|
+
finalRes = textRes;
|
|
221
|
+
} else if (isToolResponse(res)) {
|
|
222
|
+
yield res;
|
|
223
|
+
finalRes = res;
|
|
224
|
+
} else if (Symbol.asyncIterator in res) {
|
|
225
|
+
const accContent = [];
|
|
226
|
+
let nextResult = await res.next();
|
|
227
|
+
while (!nextResult.done) {
|
|
228
|
+
const currentValue = nextResult.value;
|
|
229
|
+
nextResult = await res.next();
|
|
230
|
+
const isLastValue = nextResult.done;
|
|
231
|
+
if (typeof currentValue === "string") {
|
|
232
|
+
const itemRes = createToolResponse({
|
|
233
|
+
content: [
|
|
234
|
+
{
|
|
235
|
+
id: crypto.randomUUID(),
|
|
236
|
+
type: "text",
|
|
237
|
+
text: currentValue
|
|
238
|
+
}
|
|
239
|
+
],
|
|
240
|
+
isLast: isLastValue,
|
|
241
|
+
state: "running"
|
|
242
|
+
});
|
|
243
|
+
yield itemRes;
|
|
244
|
+
accContent.push({
|
|
245
|
+
id: crypto.randomUUID(),
|
|
246
|
+
type: "text",
|
|
247
|
+
text: currentValue
|
|
248
|
+
});
|
|
249
|
+
} else if (isToolResponse(currentValue)) {
|
|
250
|
+
currentValue.isLast = currentValue.isLast ?? isLastValue;
|
|
251
|
+
yield currentValue;
|
|
252
|
+
accContent.push(...currentValue.content);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
finalRes = createToolResponse({
|
|
256
|
+
content: accContent,
|
|
257
|
+
state: "success"
|
|
258
|
+
});
|
|
259
|
+
} else if (Symbol.iterator in res) {
|
|
260
|
+
const accContent = [];
|
|
261
|
+
let nextResult = res.next();
|
|
262
|
+
while (!nextResult.done) {
|
|
263
|
+
const currentValue = nextResult.value;
|
|
264
|
+
nextResult = res.next();
|
|
265
|
+
const isLastValue = nextResult.done;
|
|
266
|
+
if (typeof currentValue === "string") {
|
|
267
|
+
const itemRes = createToolResponse({
|
|
268
|
+
content: [
|
|
269
|
+
{
|
|
270
|
+
id: crypto.randomUUID(),
|
|
271
|
+
type: "text",
|
|
272
|
+
text: currentValue
|
|
273
|
+
}
|
|
274
|
+
],
|
|
275
|
+
isLast: isLastValue,
|
|
276
|
+
state: "running"
|
|
277
|
+
});
|
|
278
|
+
yield itemRes;
|
|
279
|
+
accContent.push({
|
|
280
|
+
id: crypto.randomUUID(),
|
|
281
|
+
type: "text",
|
|
282
|
+
text: currentValue
|
|
283
|
+
});
|
|
284
|
+
} else if (isToolResponse(currentValue)) {
|
|
285
|
+
currentValue.isLast = currentValue.isLast ?? isLastValue;
|
|
286
|
+
yield currentValue;
|
|
287
|
+
accContent.push(...currentValue.content);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
finalRes = createToolResponse({
|
|
291
|
+
content: accContent,
|
|
292
|
+
state: "success"
|
|
293
|
+
});
|
|
294
|
+
} else {
|
|
295
|
+
const invalidRes = createToolResponse({
|
|
296
|
+
content: [
|
|
297
|
+
{
|
|
298
|
+
id: crypto.randomUUID(),
|
|
299
|
+
type: "text",
|
|
300
|
+
text: String(res)
|
|
301
|
+
}
|
|
302
|
+
],
|
|
303
|
+
state: "running"
|
|
304
|
+
});
|
|
305
|
+
yield invalidRes;
|
|
306
|
+
finalRes = invalidRes;
|
|
307
|
+
}
|
|
308
|
+
} catch (error) {
|
|
309
|
+
const errorRes = createToolResponse({
|
|
310
|
+
content: [
|
|
311
|
+
{
|
|
312
|
+
id: crypto.randomUUID(),
|
|
313
|
+
type: "text",
|
|
314
|
+
text: `ToolExecutionError: ${String(error)}`
|
|
315
|
+
}
|
|
316
|
+
],
|
|
317
|
+
state: "error"
|
|
318
|
+
});
|
|
319
|
+
yield errorRes;
|
|
320
|
+
finalRes = errorRes;
|
|
321
|
+
}
|
|
322
|
+
if (!finalRes) {
|
|
323
|
+
return createToolResponse({
|
|
324
|
+
content: [
|
|
325
|
+
{
|
|
326
|
+
id: crypto.randomUUID(),
|
|
327
|
+
type: "text",
|
|
328
|
+
text: `Tool ${toolCall.name} executed successfully.`
|
|
329
|
+
}
|
|
330
|
+
],
|
|
331
|
+
state: "success"
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
const cleanedContent = [];
|
|
335
|
+
let textBuffer = "";
|
|
336
|
+
for (const block of finalRes.content) {
|
|
337
|
+
if (block.type === "text") {
|
|
338
|
+
textBuffer += block.text;
|
|
339
|
+
} else {
|
|
340
|
+
if (textBuffer) {
|
|
341
|
+
cleanedContent.push({
|
|
342
|
+
id: crypto.randomUUID(),
|
|
343
|
+
type: "text",
|
|
344
|
+
text: textBuffer
|
|
345
|
+
});
|
|
346
|
+
textBuffer = "";
|
|
347
|
+
}
|
|
348
|
+
cleanedContent.push(block);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (textBuffer) {
|
|
352
|
+
cleanedContent.push({
|
|
353
|
+
id: crypto.randomUUID(),
|
|
354
|
+
type: "text",
|
|
355
|
+
text: textBuffer
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
return {
|
|
359
|
+
...finalRes,
|
|
360
|
+
content: cleanedContent
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Returns the JSON schemas for all registered tools in a format compatible with LLM APIs.
|
|
365
|
+
*
|
|
366
|
+
* @returns An array of ToolJSONSchema objects
|
|
367
|
+
*/
|
|
368
|
+
getJSONSchemas() {
|
|
369
|
+
return this.tools.map((tool) => {
|
|
370
|
+
const inputSchema = tool.inputSchema instanceof z.ZodObject ? tool.inputSchema.toJSONSchema({ target: "openapi-3.0" }) : tool.inputSchema;
|
|
371
|
+
return {
|
|
372
|
+
type: "function",
|
|
373
|
+
function: {
|
|
374
|
+
name: tool.name,
|
|
375
|
+
description: tool.description,
|
|
376
|
+
parameters: inputSchema
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Get the instruction prompt for the agent to use the skills.
|
|
383
|
+
*
|
|
384
|
+
* @returns A string containing the instruction prompt of the available skills and how to use them.
|
|
385
|
+
*/
|
|
386
|
+
getSkillsPrompt() {
|
|
387
|
+
this._skillCache = {};
|
|
388
|
+
if (this.skills.length === 0 && this.skillDirs.length === 0) return "";
|
|
389
|
+
if (typeof process !== "undefined" && process.versions && process.versions.node) {
|
|
390
|
+
const skillsInfo = [];
|
|
391
|
+
this.skills.forEach((skillPath) => {
|
|
392
|
+
const absSkillPath = path.resolve(skillPath);
|
|
393
|
+
if (!fs.existsSync(absSkillPath) || !fs.statSync(absSkillPath).isDirectory()) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const skillMdPath = path.join(absSkillPath, "SKILL.md");
|
|
397
|
+
if (!fs.existsSync(skillMdPath)) return;
|
|
398
|
+
try {
|
|
399
|
+
const content = fs.readFileSync(skillMdPath, "utf-8");
|
|
400
|
+
const { data } = matter(content);
|
|
401
|
+
const name = data.name || path.basename(skillPath);
|
|
402
|
+
const description = data.description || "No description provided";
|
|
403
|
+
skillsInfo.push({
|
|
404
|
+
name,
|
|
405
|
+
description,
|
|
406
|
+
location: absSkillPath
|
|
407
|
+
});
|
|
408
|
+
this._skillCache[name] = absSkillPath;
|
|
409
|
+
} catch (e) {
|
|
410
|
+
console.error(`Error reading SKILL.md for skill at ${skillPath}:`, e);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
this.skillDirs.forEach((skillDir) => {
|
|
414
|
+
const absSkillDir = path.resolve(skillDir);
|
|
415
|
+
if (!fs.existsSync(absSkillDir) || !fs.statSync(absSkillDir).isDirectory()) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const subdirs = fs.readdirSync(absSkillDir).filter((subdir) => {
|
|
419
|
+
const subdirPath = path.join(absSkillDir, subdir);
|
|
420
|
+
return fs.statSync(subdirPath).isDirectory();
|
|
421
|
+
});
|
|
422
|
+
subdirs.forEach((subdir) => {
|
|
423
|
+
const skillMdPath = path.join(absSkillDir, subdir, "SKILL.md");
|
|
424
|
+
if (!fs.existsSync(skillMdPath)) return;
|
|
425
|
+
try {
|
|
426
|
+
const content = fs.readFileSync(skillMdPath, "utf-8");
|
|
427
|
+
const { data } = matter(content);
|
|
428
|
+
const name = data.name || subdir;
|
|
429
|
+
const description = data.description || "No description provided";
|
|
430
|
+
skillsInfo.push({
|
|
431
|
+
name,
|
|
432
|
+
description,
|
|
433
|
+
location: path.join(skillDir, subdir)
|
|
434
|
+
});
|
|
435
|
+
this._skillCache[name] = path.join(absSkillDir, subdir);
|
|
436
|
+
} catch (e) {
|
|
437
|
+
console.error(
|
|
438
|
+
`Error reading SKILL.md for skill at ${path.join(skillDir, subdir)}:`,
|
|
439
|
+
e
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
if (skillsInfo.length === 0) return "";
|
|
445
|
+
const skillsXml = skillsInfo.map(
|
|
446
|
+
(skill) => `<skill>
|
|
447
|
+
<name>${skill.name}</name>
|
|
448
|
+
<description>${skill.description}</description>
|
|
449
|
+
<location>${skill.location}</location>
|
|
450
|
+
</skill>`
|
|
451
|
+
).reduce((acc, skillInfo) => acc + `
|
|
452
|
+
${skillInfo}
|
|
453
|
+
`, "");
|
|
454
|
+
return `<skills-system>
|
|
455
|
+
## What are Skills?
|
|
456
|
+
Skills are packages of domain expertise that extend your capabilities.
|
|
457
|
+
|
|
458
|
+
## Important: How to Use Skills
|
|
459
|
+
**Skill names are NOT callable functions.** You cannot call a skill directly by its name.
|
|
460
|
+
${skillsXml}
|
|
461
|
+
</skills-system>`;
|
|
462
|
+
}
|
|
463
|
+
return "";
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* The agent skill tool to read SKILL.md file content based on the skill name.
|
|
467
|
+
* @param root0
|
|
468
|
+
* @param root0.name
|
|
469
|
+
* @returns The content of the SKILL.md file for the specified skill, or an error message if the skill is not
|
|
470
|
+
* found or the SKILL.md file cannot be read.
|
|
471
|
+
*/
|
|
472
|
+
async _skillTool({ name }) {
|
|
473
|
+
if (this._skillCache[name]) {
|
|
474
|
+
const skillDir = this._skillCache[name];
|
|
475
|
+
const skillMdPath = path.join(skillDir, "SKILL.md");
|
|
476
|
+
if (!fs.existsSync(skillMdPath)) {
|
|
477
|
+
try {
|
|
478
|
+
const fileContent = fs.readFileSync(skillMdPath, "utf-8");
|
|
479
|
+
return createToolResponse({
|
|
480
|
+
content: [
|
|
481
|
+
{
|
|
482
|
+
id: crypto.randomUUID(),
|
|
483
|
+
type: "text",
|
|
484
|
+
text: fileContent
|
|
485
|
+
}
|
|
486
|
+
],
|
|
487
|
+
state: "success"
|
|
488
|
+
});
|
|
489
|
+
} catch {
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
this.getSkillsPrompt();
|
|
494
|
+
const refreshedSkillDir = this._skillCache[name];
|
|
495
|
+
if (refreshedSkillDir) {
|
|
496
|
+
const skillMdPath = path.join(refreshedSkillDir, "SKILL.md");
|
|
497
|
+
try {
|
|
498
|
+
const fileContent = fs.readFileSync(skillMdPath, "utf-8");
|
|
499
|
+
return createToolResponse({
|
|
500
|
+
content: [
|
|
501
|
+
{
|
|
502
|
+
id: crypto.randomUUID(),
|
|
503
|
+
type: "text",
|
|
504
|
+
text: fileContent
|
|
505
|
+
}
|
|
506
|
+
],
|
|
507
|
+
state: "success"
|
|
508
|
+
});
|
|
509
|
+
} catch {
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return createToolResponse({
|
|
513
|
+
content: [
|
|
514
|
+
{
|
|
515
|
+
id: crypto.randomUUID(),
|
|
516
|
+
type: "text",
|
|
517
|
+
text: `SkillNotFoundError: Cannot find the skill named ${name}, current available skills are ${Object.keys(this._skillCache).join(", ")}`
|
|
518
|
+
}
|
|
519
|
+
],
|
|
520
|
+
state: "error"
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Checks if a tool requires user confirmation before execution based on its name.
|
|
525
|
+
* @param toolName The name of the tool to check for user confirmation requirement.
|
|
526
|
+
* @returns A boolean indicating whether the specified tool requires user confirmation before execution. If the tool is not found, it returns false.
|
|
527
|
+
*/
|
|
528
|
+
requireUserConfirm(toolName) {
|
|
529
|
+
const tool = this.tools.find((tool2) => tool2.name === toolName);
|
|
530
|
+
return tool ? tool.requireUserConfirm ?? false : false;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Checks if a tool requires external execution (e.g., by an MCP client) based on its name.
|
|
534
|
+
* @param toolName
|
|
535
|
+
* @returns A boolean indicating whether the specified tool requires external execution. If the tool is not found, it returns false.
|
|
536
|
+
*/
|
|
537
|
+
requireExternalExecution(toolName) {
|
|
538
|
+
const tool = this.tools.find((tool2) => tool2.name === toolName);
|
|
539
|
+
return tool ? !tool.call : false;
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
// src/tool/bash.ts
|
|
544
|
+
import { exec } from "child_process";
|
|
545
|
+
import { promisify } from "util";
|
|
546
|
+
import { z as z2 } from "zod";
|
|
547
|
+
var execAsync = promisify(exec);
|
|
548
|
+
function Bash() {
|
|
549
|
+
return {
|
|
550
|
+
name: "Bash",
|
|
551
|
+
description: `Executes a given bash command and returns its output.
|
|
552
|
+
|
|
553
|
+
The working directory persists between commands, but shell state does not. The shell environment is initialized from the user's profile (bash or zsh).
|
|
554
|
+
|
|
555
|
+
IMPORTANT: Avoid using this tool to run \`find\`, \`grep\`, \`cat\`, \`head\`, \`tail\`, \`sed\`, \`awk\`, or \`echo\` commands, unless explicitly instructed or after you have verified that a dedicated tool cannot accomplish your task. Instead, use the appropriate dedicated tool as this will provide a much better experience for the user:
|
|
556
|
+
|
|
557
|
+
- File search: Use Glob (NOT find or ls)
|
|
558
|
+
- Content search: Use Grep (NOT grep or rg)
|
|
559
|
+
- Read files: Use Read (NOT cat/head/tail)
|
|
560
|
+
- Edit files: Use Edit (NOT sed/awk)
|
|
561
|
+
- Write files: Use Write (NOT echo >/cat <<EOF)
|
|
562
|
+
- Communication: Output text directly (NOT echo/printf)
|
|
563
|
+
|
|
564
|
+
While the Bash tool can do similar things, it's better to use the built-in tools as they provide a better user experience and make it easier to review tool calls and give permission.
|
|
565
|
+
|
|
566
|
+
# Instructions
|
|
567
|
+
- If your command will create new directories or files, first use this tool to run \`ls\` to verify the parent directory exists and is the correct location.
|
|
568
|
+
- Always quote file paths that contain spaces with double quotes in your command (e.g., cd "path with spaces/file.txt")
|
|
569
|
+
- Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of \`cd\`. You may use \`cd\` if the User explicitly requests it.
|
|
570
|
+
- You may specify an optional timeout in milliseconds (up to 600000ms / 10 minutes). By default, your command will timeout after 120000ms (2 minutes).
|
|
571
|
+
- Write a clear, concise description of what your command does. For simple commands, keep it brief (5-10 words). For complex commands (piped commands, obscure flags, or anything hard to understand at a glance), include enough context so that the user can understand what your command will do.
|
|
572
|
+
- When issuing multiple commands:
|
|
573
|
+
- If the commands are independent and can run in parallel, make multiple Bash tool calls in a single message. Example: if you need to run "git status" and "git diff", send a single message with two Bash tool calls in parallel.
|
|
574
|
+
- If the commands depend on each other and must run sequentially, use a single Bash call with '&&' to chain them together.
|
|
575
|
+
- Use ';' only when you need to run commands sequentially but don't care if earlier commands fail.
|
|
576
|
+
- DO NOT use newlines to separate commands (newlines are ok in quoted strings).
|
|
577
|
+
- For git commands:
|
|
578
|
+
- Prefer to create a new commit rather than amending an existing commit.
|
|
579
|
+
- Before running destructive operations (e.g., git reset --hard, git push --force, git checkout --), consider whether there is a safer alternative that achieves the same goal. Only use destructive operations when they are truly the best approach.
|
|
580
|
+
- Never skip hooks (--no-verify) or bypass signing (--no-gpg-sign, -c commit.gpgsign=false) unless the user has explicitly asked for it. If a hook fails, investigate and fix the underlying issue.
|
|
581
|
+
- Avoid unnecessary \`sleep\` commands:
|
|
582
|
+
- Do not sleep between commands that can run immediately \u2014 just run them.
|
|
583
|
+
- Do not retry failing commands in a sleep loop \u2014 diagnose the root cause or consider an alternative approach.
|
|
584
|
+
- If you must sleep, keep the duration short (1-5 seconds) to avoid blocking the user.`,
|
|
585
|
+
inputSchema: z2.object({
|
|
586
|
+
command: z2.string().describe("The bash command to execute"),
|
|
587
|
+
description: z2.string().optional().describe(
|
|
588
|
+
"Clear, concise description of what this command does. For simple commands, keep it brief (5-10 words). For complex commands, include enough context."
|
|
589
|
+
),
|
|
590
|
+
timeout: z2.number().int().min(0).max(6e5).optional().describe("Optional timeout in milliseconds (default: 120000, max: 600000)")
|
|
591
|
+
}),
|
|
592
|
+
requireUserConfirm: true,
|
|
593
|
+
/**
|
|
594
|
+
* Executes a bash command and returns its output.
|
|
595
|
+
*
|
|
596
|
+
* @param root0 - The parameters object
|
|
597
|
+
* @param root0.command - The bash command to execute
|
|
598
|
+
* @param root0.description - Optional description of what the command does
|
|
599
|
+
* @param root0.timeout - Optional timeout in milliseconds (default: 120000, max: 600000)
|
|
600
|
+
* @returns The stdout of the command, or an error message if the command fails
|
|
601
|
+
*/
|
|
602
|
+
async call({
|
|
603
|
+
command,
|
|
604
|
+
description: _description,
|
|
605
|
+
timeout = 12e4
|
|
606
|
+
}) {
|
|
607
|
+
try {
|
|
608
|
+
const maxTimeout = 6e5;
|
|
609
|
+
const effectiveTimeout = Math.min(timeout, maxTimeout);
|
|
610
|
+
let shell;
|
|
611
|
+
if (process.platform === "win32") {
|
|
612
|
+
shell = process.env.COMSPEC || "cmd.exe";
|
|
613
|
+
} else {
|
|
614
|
+
shell = process.env.SHELL || "/bin/bash";
|
|
615
|
+
}
|
|
616
|
+
const { stdout } = await execAsync(command, {
|
|
617
|
+
encoding: "utf-8",
|
|
618
|
+
timeout: effectiveTimeout,
|
|
619
|
+
maxBuffer: 3e4 * 1024,
|
|
620
|
+
shell
|
|
621
|
+
});
|
|
622
|
+
const normalizedOutput = stdout.replace(/\r\n/g, "\n");
|
|
623
|
+
const maxOutputLength = 3e4;
|
|
624
|
+
if (normalizedOutput.length > maxOutputLength) {
|
|
625
|
+
return createToolResponse({
|
|
626
|
+
content: [
|
|
627
|
+
{
|
|
628
|
+
id: crypto.randomUUID(),
|
|
629
|
+
type: "text",
|
|
630
|
+
text: normalizedOutput.substring(0, maxOutputLength) + "\n\n[Output truncated - exceeded 30000 characters]"
|
|
631
|
+
}
|
|
632
|
+
],
|
|
633
|
+
state: "success"
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
return createToolResponse({
|
|
637
|
+
content: [{ id: crypto.randomUUID(), type: "text", text: normalizedOutput }],
|
|
638
|
+
state: "success"
|
|
639
|
+
});
|
|
640
|
+
} catch (error) {
|
|
641
|
+
const errorMessage = error.message || "Unknown error";
|
|
642
|
+
const stderr = error.stderr?.toString().replace(/\r\n/g, "\n") || "";
|
|
643
|
+
const stdout = error.stdout?.toString().replace(/\r\n/g, "\n") || "";
|
|
644
|
+
let result = `Command failed: ${command}
|
|
645
|
+
`;
|
|
646
|
+
if (stdout) result += `
|
|
647
|
+
Stdout:
|
|
648
|
+
${stdout}`;
|
|
649
|
+
if (stderr) result += `
|
|
650
|
+
Stderr:
|
|
651
|
+
${stderr}`;
|
|
652
|
+
if (errorMessage && !stderr) result += `
|
|
653
|
+
Error: ${errorMessage}`;
|
|
654
|
+
return createToolResponse({
|
|
655
|
+
content: [{ id: crypto.randomUUID(), type: "text", text: result }],
|
|
656
|
+
state: "error"
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// src/tool/read.ts
|
|
664
|
+
import * as fs2 from "fs";
|
|
665
|
+
import * as path2 from "path";
|
|
666
|
+
import { z as z3 } from "zod";
|
|
667
|
+
function Read() {
|
|
668
|
+
return {
|
|
669
|
+
name: "Read",
|
|
670
|
+
description: `Reads a file from the local filesystem. You can access any file directly by using this tool.
|
|
671
|
+
Assume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.
|
|
672
|
+
|
|
673
|
+
Usage:
|
|
674
|
+
- The file_path parameter must be an absolute path, not a relative path
|
|
675
|
+
- By default, it reads up to 2000 lines starting from the beginning of the file
|
|
676
|
+
- You can optionally specify a line offset and limit (especially handy for long files), but it's recommended to read the whole file by not providing these parameters
|
|
677
|
+
- Any lines longer than 2000 characters will be truncated
|
|
678
|
+
- Results are returned using cat -n format, with line numbers starting at 1
|
|
679
|
+
- This tool can only read files, not directories. To read a directory, use an ls command via the Bash tool.
|
|
680
|
+
- You can call multiple tools in a single response. It is always better to speculatively read multiple potentially useful files in parallel.
|
|
681
|
+
- If you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.`,
|
|
682
|
+
inputSchema: z3.object({
|
|
683
|
+
file_path: z3.string().describe("The absolute path to the file to read"),
|
|
684
|
+
offset: z3.number().int().positive().optional().describe(
|
|
685
|
+
"The line number to start reading from. Only provide if the file is too large to read at once"
|
|
686
|
+
),
|
|
687
|
+
limit: z3.number().int().positive().optional().describe(
|
|
688
|
+
"The number of lines to read. Only provide if the file is too large to read at once"
|
|
689
|
+
)
|
|
690
|
+
}),
|
|
691
|
+
requireUserConfirm: true,
|
|
692
|
+
/**
|
|
693
|
+
* Reads a file and returns its contents with line numbers.
|
|
694
|
+
*
|
|
695
|
+
* @param root0 - The parameters object
|
|
696
|
+
* @param root0.file_path - Absolute path to the file to read
|
|
697
|
+
* @param root0.offset - Line number to start reading from (1-based)
|
|
698
|
+
* @param root0.limit - Maximum number of lines to read (capped at 2000)
|
|
699
|
+
* @returns The file contents formatted with line numbers, or a warning if the file is empty
|
|
700
|
+
* @throws If the path is not absolute, the file does not exist, or the path is a directory
|
|
701
|
+
*/
|
|
702
|
+
call({
|
|
703
|
+
file_path,
|
|
704
|
+
offset,
|
|
705
|
+
limit
|
|
706
|
+
}) {
|
|
707
|
+
if (!path2.isAbsolute(file_path)) {
|
|
708
|
+
throw new Error(`file_path must be an absolute path, got: ${file_path}`);
|
|
709
|
+
}
|
|
710
|
+
if (!fs2.existsSync(file_path)) {
|
|
711
|
+
throw new Error(`File not found: ${file_path}`);
|
|
712
|
+
}
|
|
713
|
+
const stat = fs2.statSync(file_path);
|
|
714
|
+
if (stat.isDirectory()) {
|
|
715
|
+
throw new Error(
|
|
716
|
+
`${file_path} is a directory, not a file. Use Bash with ls to read directories.`
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
const rawContent = fs2.readFileSync(file_path, "utf-8");
|
|
720
|
+
if (rawContent.length === 0) {
|
|
721
|
+
return createToolResponse({
|
|
722
|
+
content: [{ id: crypto.randomUUID(), type: "text", text: rawContent }],
|
|
723
|
+
state: "success"
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
const allLines = rawContent.split("\n");
|
|
727
|
+
const startLine = offset !== void 0 ? offset - 1 : 0;
|
|
728
|
+
const maxLines = 2e3;
|
|
729
|
+
const effectiveLimit = limit !== void 0 ? Math.min(limit, maxLines) : maxLines;
|
|
730
|
+
const selectedLines = allLines.slice(startLine, startLine + effectiveLimit);
|
|
731
|
+
const maxLineLength = 2e3;
|
|
732
|
+
const formatted = selectedLines.map((line, i) => {
|
|
733
|
+
const lineNum = startLine + i + 1;
|
|
734
|
+
const truncated = line.length > maxLineLength ? line.substring(0, maxLineLength) + "[truncated]" : line;
|
|
735
|
+
return `${String(lineNum).padStart(6)} ${truncated}`;
|
|
736
|
+
}).join("\n");
|
|
737
|
+
return createToolResponse({
|
|
738
|
+
content: [{ id: crypto.randomUUID(), type: "text", text: formatted }],
|
|
739
|
+
state: "success"
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// src/tool/write.ts
|
|
746
|
+
import * as fs3 from "fs";
|
|
747
|
+
import * as path3 from "path";
|
|
748
|
+
import { z as z4 } from "zod";
|
|
749
|
+
function Write() {
|
|
750
|
+
return {
|
|
751
|
+
name: "Write",
|
|
752
|
+
description: `Writes a file to the local filesystem.
|
|
753
|
+
|
|
754
|
+
Usage:
|
|
755
|
+
- This tool will overwrite the existing file if there is one at the provided path.
|
|
756
|
+
- If this is an existing file, you MUST use the Read tool first to read the file's contents. This tool will fail if you did not read the file first.
|
|
757
|
+
- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
|
|
758
|
+
- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.
|
|
759
|
+
- Only use emojis if the user explicitly requests it. Avoid writing emojis to files unless asked.`,
|
|
760
|
+
inputSchema: z4.object({
|
|
761
|
+
file_path: z4.string().describe(
|
|
762
|
+
"The absolute path to the file to write (must be absolute, not relative)"
|
|
763
|
+
),
|
|
764
|
+
content: z4.string().describe("The content to write to the file")
|
|
765
|
+
}),
|
|
766
|
+
requireUserConfirm: true,
|
|
767
|
+
/**
|
|
768
|
+
* Writes content to a file, creating parent directories if necessary.
|
|
769
|
+
*
|
|
770
|
+
* @param root0 - The parameters object
|
|
771
|
+
* @param root0.file_path - Absolute path to the file to write
|
|
772
|
+
* @param root0.content - The content to write to the file
|
|
773
|
+
* @returns A success message including the number of lines written
|
|
774
|
+
* @throws If the path is not absolute
|
|
775
|
+
*/
|
|
776
|
+
call({ file_path, content }) {
|
|
777
|
+
if (!path3.isAbsolute(file_path)) {
|
|
778
|
+
throw new Error(`file_path must be an absolute path, got: ${file_path}`);
|
|
779
|
+
}
|
|
780
|
+
const dir = path3.dirname(file_path);
|
|
781
|
+
if (!fs3.existsSync(dir)) {
|
|
782
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
783
|
+
}
|
|
784
|
+
fs3.writeFileSync(file_path, content, "utf-8");
|
|
785
|
+
const lineCount = content.split("\n").length;
|
|
786
|
+
return `The file ${file_path} has been written successfully (${lineCount} lines).`;
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// src/tool/edit.ts
|
|
792
|
+
import * as fs4 from "fs";
|
|
793
|
+
import * as path4 from "path";
|
|
794
|
+
import { z as z5 } from "zod";
|
|
795
|
+
function Edit() {
|
|
796
|
+
return {
|
|
797
|
+
name: "Edit",
|
|
798
|
+
description: `Performs exact string replacements in files.
|
|
799
|
+
|
|
800
|
+
Usage:
|
|
801
|
+
- You must use your Read tool at least once in the conversation before editing. This tool will error if you attempt an edit without reading the file.
|
|
802
|
+
- When editing text from Read tool output, ensure you preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. The line number prefix format is: spaces + line number + tab. Everything after that tab is the actual file content to match. Never include any part of the line number prefix in the old_string or new_string.
|
|
803
|
+
- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
|
|
804
|
+
- Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked.
|
|
805
|
+
- The edit will FAIL if \`old_string\` is not unique in the file. Either provide a larger string with more surrounding context to make it unique or use \`replace_all\` to change every instance of \`old_string\`.
|
|
806
|
+
- Use \`replace_all\` for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance.`,
|
|
807
|
+
inputSchema: z5.object({
|
|
808
|
+
file_path: z5.string().describe("The absolute path to the file to modify"),
|
|
809
|
+
old_string: z5.string().describe("The text to replace"),
|
|
810
|
+
new_string: z5.string().describe("The text to replace it with (must be different from old_string)"),
|
|
811
|
+
replace_all: z5.boolean().optional().default(false).describe("Replace all occurrences of old_string (default false)")
|
|
812
|
+
}),
|
|
813
|
+
requireUserConfirm: true,
|
|
814
|
+
/**
|
|
815
|
+
* Performs an exact string replacement in the specified file.
|
|
816
|
+
*
|
|
817
|
+
* @param root0 - The parameters object
|
|
818
|
+
* @param root0.file_path - Absolute path to the file to modify
|
|
819
|
+
* @param root0.old_string - The exact text to find and replace
|
|
820
|
+
* @param root0.new_string - The text to replace old_string with
|
|
821
|
+
* @param root0.replace_all - If true, replaces all occurrences; otherwise only the first
|
|
822
|
+
* @returns A success message indicating the file was updated
|
|
823
|
+
* @throws If the path is not absolute, file does not exist, strings are identical,
|
|
824
|
+
* old_string is not found, or old_string is not unique and replace_all is false
|
|
825
|
+
*/
|
|
826
|
+
call({
|
|
827
|
+
file_path,
|
|
828
|
+
old_string,
|
|
829
|
+
new_string,
|
|
830
|
+
replace_all = false
|
|
831
|
+
}) {
|
|
832
|
+
if (!path4.isAbsolute(file_path)) {
|
|
833
|
+
throw new Error(`file_path must be an absolute path, got: ${file_path}`);
|
|
834
|
+
}
|
|
835
|
+
if (!fs4.existsSync(file_path)) {
|
|
836
|
+
throw new Error(`File not found: ${file_path}`);
|
|
837
|
+
}
|
|
838
|
+
if (old_string === new_string) {
|
|
839
|
+
throw new Error("old_string and new_string must be different");
|
|
840
|
+
}
|
|
841
|
+
const content = fs4.readFileSync(file_path, "utf-8");
|
|
842
|
+
const occurrences = content.split(old_string).length - 1;
|
|
843
|
+
if (occurrences === 0) {
|
|
844
|
+
throw new Error(`old_string not found in file: ${file_path}`);
|
|
845
|
+
}
|
|
846
|
+
if (!replace_all && occurrences > 1) {
|
|
847
|
+
throw new Error(
|
|
848
|
+
`old_string is not unique in the file (found ${occurrences} occurrences). Provide more surrounding context to make it unique, or use replace_all=true.`
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
const newContent = replace_all ? content.split(old_string).join(new_string) : content.replace(old_string, new_string);
|
|
852
|
+
fs4.writeFileSync(file_path, newContent, "utf-8");
|
|
853
|
+
return `The file ${file_path} has been updated successfully.`;
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// src/tool/glob.ts
|
|
859
|
+
import * as fs5 from "fs";
|
|
860
|
+
import * as path5 from "path";
|
|
861
|
+
import { z as z6 } from "zod";
|
|
862
|
+
function Glob() {
|
|
863
|
+
const globMatch = (pattern, baseDir) => {
|
|
864
|
+
const results = [];
|
|
865
|
+
const parts = pattern.split("/");
|
|
866
|
+
matchParts(parts, 0, baseDir, results);
|
|
867
|
+
return results;
|
|
868
|
+
};
|
|
869
|
+
const matchParts = (parts, partIndex, currentDir, results) => {
|
|
870
|
+
if (partIndex >= parts.length) return;
|
|
871
|
+
const part = parts[partIndex];
|
|
872
|
+
const isLast = partIndex === parts.length - 1;
|
|
873
|
+
if (part === "**") {
|
|
874
|
+
if (isLast) {
|
|
875
|
+
collectAll(currentDir, results);
|
|
876
|
+
} else {
|
|
877
|
+
matchParts(parts, partIndex + 1, currentDir, results);
|
|
878
|
+
try {
|
|
879
|
+
const entries = fs5.readdirSync(currentDir, { withFileTypes: true });
|
|
880
|
+
for (const entry of entries) {
|
|
881
|
+
if (entry.isDirectory()) {
|
|
882
|
+
const subDir = path5.join(currentDir, entry.name);
|
|
883
|
+
matchParts(parts, partIndex, subDir, results);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
} catch {
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
} else {
|
|
890
|
+
const regex = globPartToRegex(part);
|
|
891
|
+
try {
|
|
892
|
+
const entries = fs5.readdirSync(currentDir, { withFileTypes: true });
|
|
893
|
+
for (const entry of entries) {
|
|
894
|
+
if (regex.test(entry.name)) {
|
|
895
|
+
const fullPath = path5.join(currentDir, entry.name);
|
|
896
|
+
if (isLast) {
|
|
897
|
+
if (entry.isFile()) results.push(fullPath);
|
|
898
|
+
} else if (entry.isDirectory()) {
|
|
899
|
+
matchParts(parts, partIndex + 1, fullPath, results);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
} catch {
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
const collectAll = (dir, results) => {
|
|
908
|
+
try {
|
|
909
|
+
const entries = fs5.readdirSync(dir, { withFileTypes: true });
|
|
910
|
+
for (const entry of entries) {
|
|
911
|
+
const fullPath = path5.join(dir, entry.name);
|
|
912
|
+
if (entry.isFile()) {
|
|
913
|
+
results.push(fullPath);
|
|
914
|
+
} else if (entry.isDirectory()) {
|
|
915
|
+
collectAll(fullPath, results);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
} catch {
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
const globPartToRegex = (part) => {
|
|
922
|
+
const escaped = part.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
923
|
+
return new RegExp(`^${escaped}$`);
|
|
924
|
+
};
|
|
925
|
+
return {
|
|
926
|
+
name: "Glob",
|
|
927
|
+
description: `Fast file pattern matching tool that works with any codebase size.
|
|
928
|
+
- Supports glob patterns like "**/*.js" or "src/**/*.ts"
|
|
929
|
+
- Returns matching file paths sorted by modification time
|
|
930
|
+
- Use this tool when you need to find files by name patterns
|
|
931
|
+
- When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Agent tool instead
|
|
932
|
+
- You can call multiple tools in a single response. It is always better to speculatively perform multiple searches in parallel if they are potentially useful.`,
|
|
933
|
+
inputSchema: z6.object({
|
|
934
|
+
pattern: z6.string().describe("The glob pattern to match files against"),
|
|
935
|
+
path: z6.string().optional().describe(
|
|
936
|
+
'The directory to search in. If not specified, the current working directory will be used. IMPORTANT: Omit this field to use the default directory. DO NOT enter "undefined" or "null" - simply omit it for the default behavior. Must be a valid directory path if provided.'
|
|
937
|
+
)
|
|
938
|
+
}),
|
|
939
|
+
requireUserConfirm: true,
|
|
940
|
+
/**
|
|
941
|
+
* Finds files matching a glob pattern under the given directory.
|
|
942
|
+
*
|
|
943
|
+
* @param root0 - The parameters object
|
|
944
|
+
* @param root0.pattern - The glob pattern to match files against
|
|
945
|
+
* @param root0.path - The base directory to search in; defaults to cwd
|
|
946
|
+
* @returns A newline-separated list of matching file paths sorted by modification time,
|
|
947
|
+
* or a no-matches message if nothing is found
|
|
948
|
+
* @throws If the base directory does not exist
|
|
949
|
+
*/
|
|
950
|
+
call({ pattern, path: searchPath }) {
|
|
951
|
+
const baseDir = searchPath ? searchPath : process.cwd();
|
|
952
|
+
if (!fs5.existsSync(baseDir)) {
|
|
953
|
+
throw new Error(`Directory not found: ${baseDir}`);
|
|
954
|
+
}
|
|
955
|
+
const matches = globMatch(pattern, baseDir);
|
|
956
|
+
matches.sort((a, b) => {
|
|
957
|
+
const statA = fs5.statSync(a);
|
|
958
|
+
const statB = fs5.statSync(b);
|
|
959
|
+
return statB.mtimeMs - statA.mtimeMs;
|
|
960
|
+
});
|
|
961
|
+
if (matches.length === 0) {
|
|
962
|
+
return `No files found matching pattern: ${pattern}`;
|
|
963
|
+
}
|
|
964
|
+
return matches.join("\n");
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// src/tool/grep.ts
|
|
970
|
+
import * as fs6 from "fs";
|
|
971
|
+
import * as path6 from "path";
|
|
972
|
+
import { z as z7 } from "zod";
|
|
973
|
+
var TYPE_EXTENSIONS = {
|
|
974
|
+
js: [".js", ".mjs", ".cjs"],
|
|
975
|
+
ts: [".ts", ".mts", ".cts"],
|
|
976
|
+
tsx: [".tsx"],
|
|
977
|
+
jsx: [".jsx"],
|
|
978
|
+
py: [".py"],
|
|
979
|
+
rust: [".rs"],
|
|
980
|
+
go: [".go"],
|
|
981
|
+
java: [".java"],
|
|
982
|
+
cpp: [".cpp", ".cc", ".cxx", ".h", ".hpp"],
|
|
983
|
+
c: [".c", ".h"],
|
|
984
|
+
css: [".css"],
|
|
985
|
+
html: [".html", ".htm"],
|
|
986
|
+
json: [".json"],
|
|
987
|
+
md: [".md", ".markdown"],
|
|
988
|
+
yaml: [".yaml", ".yml"],
|
|
989
|
+
toml: [".toml"],
|
|
990
|
+
sh: [".sh", ".bash"]
|
|
991
|
+
};
|
|
992
|
+
function Grep() {
|
|
993
|
+
const collectFiles = (baseDir, glob, type) => {
|
|
994
|
+
const results = [];
|
|
995
|
+
const extensions = type ? TYPE_EXTENSIONS[type] : void 0;
|
|
996
|
+
const walk = (dir) => {
|
|
997
|
+
let entries;
|
|
998
|
+
try {
|
|
999
|
+
entries = fs6.readdirSync(dir, { withFileTypes: true });
|
|
1000
|
+
} catch {
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
for (const entry of entries) {
|
|
1004
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
1005
|
+
const fullPath = path6.join(dir, entry.name);
|
|
1006
|
+
if (entry.isDirectory()) {
|
|
1007
|
+
walk(fullPath);
|
|
1008
|
+
} else if (entry.isFile()) {
|
|
1009
|
+
if (extensions) {
|
|
1010
|
+
const ext = path6.extname(entry.name);
|
|
1011
|
+
if (extensions.includes(ext)) results.push(fullPath);
|
|
1012
|
+
} else if (glob) {
|
|
1013
|
+
if (matchGlob(glob, entry.name)) results.push(fullPath);
|
|
1014
|
+
} else {
|
|
1015
|
+
results.push(fullPath);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
if (fs6.existsSync(baseDir) && fs6.statSync(baseDir).isFile()) {
|
|
1021
|
+
results.push(baseDir);
|
|
1022
|
+
} else {
|
|
1023
|
+
walk(baseDir);
|
|
1024
|
+
}
|
|
1025
|
+
return results;
|
|
1026
|
+
};
|
|
1027
|
+
const matchGlob = (pattern, filename) => {
|
|
1028
|
+
const braceMatch = pattern.match(/\*\.\\{(.+)\\}/);
|
|
1029
|
+
if (braceMatch) {
|
|
1030
|
+
const exts = braceMatch[1].split(",").map((e) => `.${e.trim()}`);
|
|
1031
|
+
return exts.includes(path6.extname(filename));
|
|
1032
|
+
}
|
|
1033
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
1034
|
+
return new RegExp(`^${escaped}$`).test(filename);
|
|
1035
|
+
};
|
|
1036
|
+
return {
|
|
1037
|
+
name: "Grep",
|
|
1038
|
+
description: `A powerful search tool built on regular expressions.
|
|
1039
|
+
|
|
1040
|
+
Usage:
|
|
1041
|
+
- ALWAYS use Grep for search tasks. NEVER invoke \`grep\` or \`rg\` as a Bash command. The Grep tool has been optimized for correct permissions and access.
|
|
1042
|
+
- Supports full regex syntax (e.g., "log.*Error", "function\\s+\\w+")
|
|
1043
|
+
- Filter files with glob parameter (e.g., "*.js", "**/*.tsx") or type parameter (e.g., "js", "py", "rust")
|
|
1044
|
+
- Output modes: "content" shows matching lines, "files_with_matches" shows only file paths (default), "count" shows match counts
|
|
1045
|
+
- Use Task tool for open-ended searches requiring multiple rounds
|
|
1046
|
+
- Pattern syntax: Uses ripgrep-style regex - literal braces need escaping (use \`interface\\{\\}\` to find \`interface{}\` in Go code)
|
|
1047
|
+
- Multiline matching: By default patterns match within single lines only. For cross-line patterns, use \`multiline: true\``,
|
|
1048
|
+
inputSchema: z7.object({
|
|
1049
|
+
pattern: z7.string().describe("The regular expression pattern to search for in file contents"),
|
|
1050
|
+
path: z7.string().optional().describe("File or directory to search in. Defaults to current working directory."),
|
|
1051
|
+
glob: z7.string().optional().describe('Glob pattern to filter files (e.g. "*.js", "*.{ts,tsx}")'),
|
|
1052
|
+
type: z7.string().optional().describe(
|
|
1053
|
+
'File type to search (e.g., "js", "ts", "py"). More efficient than glob for standard file types.'
|
|
1054
|
+
),
|
|
1055
|
+
output_mode: z7.enum(["content", "files_with_matches", "count"]).optional().describe(
|
|
1056
|
+
'Output mode: "content" | "files_with_matches" | "count". Defaults to "files_with_matches".'
|
|
1057
|
+
),
|
|
1058
|
+
multiline: z7.boolean().optional().describe(
|
|
1059
|
+
"Enable multiline mode where . matches newlines and patterns can span lines. Default: false."
|
|
1060
|
+
),
|
|
1061
|
+
case_insensitive: z7.boolean().optional().describe("Case insensitive search. Default: false."),
|
|
1062
|
+
context: z7.number().int().optional().describe(
|
|
1063
|
+
'Number of lines to show before and after each match. Requires output_mode: "content".'
|
|
1064
|
+
),
|
|
1065
|
+
head_limit: z7.number().int().optional().describe("Limit output to first N lines/entries.")
|
|
1066
|
+
}),
|
|
1067
|
+
requireUserConfirm: true,
|
|
1068
|
+
/**
|
|
1069
|
+
* Searches files for a regex pattern and returns results in the specified output mode.
|
|
1070
|
+
*
|
|
1071
|
+
* @param root0 - The parameters object
|
|
1072
|
+
* @param root0.pattern - The regular expression pattern to search for
|
|
1073
|
+
* @param root0.path - File or directory to search in; defaults to cwd
|
|
1074
|
+
* @param root0.glob - Glob pattern to filter which files are searched
|
|
1075
|
+
* @param root0.type - File type shorthand (e.g. "ts", "py") to filter files
|
|
1076
|
+
* @param root0.output_mode - How to format results: "content", "files_with_matches", or "count"
|
|
1077
|
+
* @param root0.multiline - Whether the pattern can span multiple lines
|
|
1078
|
+
* @param root0.case_insensitive - Whether the search is case-insensitive
|
|
1079
|
+
* @param root0.context - Number of surrounding lines to include with each match
|
|
1080
|
+
* @param root0.head_limit - Maximum number of result entries to return
|
|
1081
|
+
* @returns A newline-separated string of results, or a no-matches message
|
|
1082
|
+
*/
|
|
1083
|
+
call({
|
|
1084
|
+
pattern,
|
|
1085
|
+
path: searchPath,
|
|
1086
|
+
glob,
|
|
1087
|
+
type,
|
|
1088
|
+
output_mode = "files_with_matches",
|
|
1089
|
+
multiline = false,
|
|
1090
|
+
case_insensitive = false,
|
|
1091
|
+
context,
|
|
1092
|
+
head_limit
|
|
1093
|
+
}) {
|
|
1094
|
+
const baseDir = searchPath ? searchPath : process.cwd();
|
|
1095
|
+
let flags = multiline ? "gms" : "gm";
|
|
1096
|
+
if (case_insensitive) flags += "i";
|
|
1097
|
+
const regex = new RegExp(pattern, flags);
|
|
1098
|
+
const files = collectFiles(baseDir, glob, type);
|
|
1099
|
+
if (files.length === 0) {
|
|
1100
|
+
return "No files found to search.";
|
|
1101
|
+
}
|
|
1102
|
+
const results = [];
|
|
1103
|
+
for (const file of files) {
|
|
1104
|
+
try {
|
|
1105
|
+
const content = fs6.readFileSync(file, "utf-8");
|
|
1106
|
+
if (output_mode === "files_with_matches") {
|
|
1107
|
+
if (regex.test(content)) {
|
|
1108
|
+
results.push(file);
|
|
1109
|
+
}
|
|
1110
|
+
regex.lastIndex = 0;
|
|
1111
|
+
} else if (output_mode === "count") {
|
|
1112
|
+
const matches = content.match(regex);
|
|
1113
|
+
if (matches) {
|
|
1114
|
+
results.push(`${file}: ${matches.length}`);
|
|
1115
|
+
}
|
|
1116
|
+
regex.lastIndex = 0;
|
|
1117
|
+
} else if (output_mode === "content") {
|
|
1118
|
+
const lines = content.split("\n");
|
|
1119
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1120
|
+
const lineRegex = new RegExp(pattern, case_insensitive ? "i" : "");
|
|
1121
|
+
if (lineRegex.test(lines[i])) {
|
|
1122
|
+
const start = context !== void 0 ? Math.max(0, i - context) : i;
|
|
1123
|
+
const end = context !== void 0 ? Math.min(lines.length - 1, i + context) : i;
|
|
1124
|
+
for (let j = start; j <= end; j++) {
|
|
1125
|
+
results.push(`${file}:${j + 1}:${lines[j]}`);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
} catch {
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
if (results.length === 0) {
|
|
1134
|
+
return `No matches found for pattern: ${pattern}`;
|
|
1135
|
+
}
|
|
1136
|
+
const output = head_limit !== void 0 ? results.slice(0, head_limit) : results;
|
|
1137
|
+
return output.join("\n");
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// src/tool/task.ts
|
|
1143
|
+
import { z as z8 } from "zod";
|
|
1144
|
+
var taskStore = /* @__PURE__ */ new Map();
|
|
1145
|
+
var nextId = 1;
|
|
1146
|
+
function generateId() {
|
|
1147
|
+
return String(nextId++);
|
|
1148
|
+
}
|
|
1149
|
+
function TaskCreate() {
|
|
1150
|
+
return {
|
|
1151
|
+
name: "TaskCreate",
|
|
1152
|
+
description: `Use this tool to create a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
|
|
1153
|
+
It also helps the user understand the progress of the task and overall progress of their requests.
|
|
1154
|
+
|
|
1155
|
+
## When to Use This Tool
|
|
1156
|
+
|
|
1157
|
+
Use this tool proactively in these scenarios:
|
|
1158
|
+
|
|
1159
|
+
- Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
|
|
1160
|
+
- Non-trivial and complex tasks - Tasks that require careful planning or multiple operations
|
|
1161
|
+
- User explicitly requests todo list - When the user directly asks you to use the todo list
|
|
1162
|
+
- User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)
|
|
1163
|
+
|
|
1164
|
+
All tasks are created with status 'pending'.`,
|
|
1165
|
+
inputSchema: z8.object({
|
|
1166
|
+
subject: z8.string().describe(
|
|
1167
|
+
'A brief, actionable title in imperative form (e.g., "Fix authentication bug in login flow")'
|
|
1168
|
+
),
|
|
1169
|
+
description: z8.string().describe(
|
|
1170
|
+
"Detailed description of what needs to be done, including context and acceptance criteria"
|
|
1171
|
+
),
|
|
1172
|
+
activeForm: z8.string().optional().describe(
|
|
1173
|
+
'Present continuous form shown in the spinner when the task is in_progress (e.g., "Fixing authentication bug"). If omitted, the spinner shows the subject instead.'
|
|
1174
|
+
),
|
|
1175
|
+
metadata: z8.record(z8.string(), z8.unknown()).optional().describe("Arbitrary metadata to attach to the task")
|
|
1176
|
+
}),
|
|
1177
|
+
requireUserConfirm: false,
|
|
1178
|
+
call({
|
|
1179
|
+
subject,
|
|
1180
|
+
description,
|
|
1181
|
+
activeForm,
|
|
1182
|
+
metadata
|
|
1183
|
+
}) {
|
|
1184
|
+
const id = generateId();
|
|
1185
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1186
|
+
const task = {
|
|
1187
|
+
id,
|
|
1188
|
+
subject,
|
|
1189
|
+
description,
|
|
1190
|
+
status: "pending",
|
|
1191
|
+
activeForm,
|
|
1192
|
+
metadata,
|
|
1193
|
+
blocks: [],
|
|
1194
|
+
blockedBy: [],
|
|
1195
|
+
createdAt: now,
|
|
1196
|
+
updatedAt: now
|
|
1197
|
+
};
|
|
1198
|
+
taskStore.set(id, task);
|
|
1199
|
+
return createToolResponse({
|
|
1200
|
+
content: [
|
|
1201
|
+
{
|
|
1202
|
+
id: crypto.randomUUID(),
|
|
1203
|
+
type: "text",
|
|
1204
|
+
text: `Task #${id} created successfully: ${subject}`
|
|
1205
|
+
}
|
|
1206
|
+
],
|
|
1207
|
+
state: "success"
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
function TaskUpdate() {
|
|
1213
|
+
return {
|
|
1214
|
+
name: "TaskUpdate",
|
|
1215
|
+
description: `Use this tool to update a task in the task list.
|
|
1216
|
+
|
|
1217
|
+
## When to Use This Tool
|
|
1218
|
+
|
|
1219
|
+
**Mark tasks as resolved:**
|
|
1220
|
+
- When you have completed the work described in a task
|
|
1221
|
+
- When a task is no longer needed or has been superseded
|
|
1222
|
+
- IMPORTANT: Always mark your assigned tasks as resolved when you finish them
|
|
1223
|
+
|
|
1224
|
+
**Delete tasks:**
|
|
1225
|
+
- When a task is no longer relevant or was created in error
|
|
1226
|
+
- Setting status to 'deleted' permanently removes the task
|
|
1227
|
+
|
|
1228
|
+
**Update task details:**
|
|
1229
|
+
- When requirements change or become clearer
|
|
1230
|
+
- When establishing dependencies between tasks`,
|
|
1231
|
+
inputSchema: z8.object({
|
|
1232
|
+
taskId: z8.string().describe("The ID of the task to update"),
|
|
1233
|
+
status: z8.enum(["pending", "in_progress", "completed", "deleted"]).optional().describe("New status for the task"),
|
|
1234
|
+
subject: z8.string().optional().describe("New subject for the task"),
|
|
1235
|
+
description: z8.string().optional().describe("New description for the task"),
|
|
1236
|
+
activeForm: z8.string().optional().describe(
|
|
1237
|
+
'Present continuous form shown in spinner when in_progress (e.g., "Running tests")'
|
|
1238
|
+
),
|
|
1239
|
+
owner: z8.string().optional().describe("New owner for the task"),
|
|
1240
|
+
metadata: z8.record(z8.string(), z8.unknown()).optional().describe("Metadata keys to merge into the task. Set a key to null to delete it."),
|
|
1241
|
+
addBlocks: z8.array(z8.string()).optional().describe("Task IDs that this task blocks"),
|
|
1242
|
+
addBlockedBy: z8.array(z8.string()).optional().describe("Task IDs that block this task")
|
|
1243
|
+
}),
|
|
1244
|
+
requireUserConfirm: false,
|
|
1245
|
+
call({
|
|
1246
|
+
taskId,
|
|
1247
|
+
status,
|
|
1248
|
+
subject,
|
|
1249
|
+
description,
|
|
1250
|
+
activeForm,
|
|
1251
|
+
owner,
|
|
1252
|
+
metadata,
|
|
1253
|
+
addBlocks,
|
|
1254
|
+
addBlockedBy
|
|
1255
|
+
}) {
|
|
1256
|
+
const task = taskStore.get(taskId);
|
|
1257
|
+
if (!task) {
|
|
1258
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
1259
|
+
}
|
|
1260
|
+
if (addBlocks) {
|
|
1261
|
+
for (const depId of addBlocks) {
|
|
1262
|
+
if (!taskStore.has(depId)) {
|
|
1263
|
+
throw new Error(`Cannot add dependency: task ${depId} does not exist`);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
if (addBlockedBy) {
|
|
1268
|
+
for (const depId of addBlockedBy) {
|
|
1269
|
+
if (!taskStore.has(depId)) {
|
|
1270
|
+
throw new Error(`Cannot add dependency: task ${depId} does not exist`);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
if (subject !== void 0) task.subject = subject;
|
|
1275
|
+
if (description !== void 0) task.description = description;
|
|
1276
|
+
if (activeForm !== void 0) task.activeForm = activeForm;
|
|
1277
|
+
if (owner !== void 0) task.owner = owner;
|
|
1278
|
+
if (metadata !== void 0) {
|
|
1279
|
+
if (!task.metadata) {
|
|
1280
|
+
task.metadata = {};
|
|
1281
|
+
}
|
|
1282
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
1283
|
+
if (value === null) {
|
|
1284
|
+
delete task.metadata[key];
|
|
1285
|
+
} else {
|
|
1286
|
+
task.metadata[key] = value;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
if (addBlocks) {
|
|
1291
|
+
task.blocks = [.../* @__PURE__ */ new Set([...task.blocks, ...addBlocks])];
|
|
1292
|
+
}
|
|
1293
|
+
if (addBlockedBy) {
|
|
1294
|
+
task.blockedBy = [.../* @__PURE__ */ new Set([...task.blockedBy, ...addBlockedBy])];
|
|
1295
|
+
}
|
|
1296
|
+
task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1297
|
+
if (status !== void 0) {
|
|
1298
|
+
if (status === "deleted") {
|
|
1299
|
+
taskStore.delete(taskId);
|
|
1300
|
+
return createToolResponse({
|
|
1301
|
+
content: [
|
|
1302
|
+
{
|
|
1303
|
+
id: crypto.randomUUID(),
|
|
1304
|
+
type: "text",
|
|
1305
|
+
text: `Task #${taskId} deleted successfully`
|
|
1306
|
+
}
|
|
1307
|
+
],
|
|
1308
|
+
state: "success"
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
task.status = status;
|
|
1312
|
+
}
|
|
1313
|
+
return createToolResponse({
|
|
1314
|
+
content: [
|
|
1315
|
+
{
|
|
1316
|
+
id: crypto.randomUUID(),
|
|
1317
|
+
type: "text",
|
|
1318
|
+
text: `Task #${taskId} updated successfully`
|
|
1319
|
+
}
|
|
1320
|
+
],
|
|
1321
|
+
state: "success"
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
function TaskGet() {
|
|
1327
|
+
return {
|
|
1328
|
+
name: "TaskGet",
|
|
1329
|
+
description: `Use this tool to retrieve a task by its ID from the task list.
|
|
1330
|
+
|
|
1331
|
+
## When to Use This Tool
|
|
1332
|
+
|
|
1333
|
+
- When you need the full description and context before starting work on a task
|
|
1334
|
+
- To understand task dependencies (what it blocks, what blocks it)
|
|
1335
|
+
- After being assigned a task, to get complete requirements`,
|
|
1336
|
+
inputSchema: z8.object({
|
|
1337
|
+
taskId: z8.string().describe("The ID of the task to retrieve")
|
|
1338
|
+
}),
|
|
1339
|
+
requireUserConfirm: false,
|
|
1340
|
+
call({ taskId }) {
|
|
1341
|
+
const task = taskStore.get(taskId);
|
|
1342
|
+
if (!task) {
|
|
1343
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
1344
|
+
}
|
|
1345
|
+
let text = `Task #${task.id}: ${task.subject}
|
|
1346
|
+
`;
|
|
1347
|
+
text += `Status: ${task.status}
|
|
1348
|
+
`;
|
|
1349
|
+
text += `Description: ${task.description}
|
|
1350
|
+
`;
|
|
1351
|
+
if (task.activeForm) {
|
|
1352
|
+
text += `Active Form: ${task.activeForm}
|
|
1353
|
+
`;
|
|
1354
|
+
}
|
|
1355
|
+
if (task.owner) {
|
|
1356
|
+
text += `Owner: ${task.owner}
|
|
1357
|
+
`;
|
|
1358
|
+
}
|
|
1359
|
+
if (task.blocks.length > 0) {
|
|
1360
|
+
text += `Blocks: ${task.blocks.join(", ")}
|
|
1361
|
+
`;
|
|
1362
|
+
}
|
|
1363
|
+
if (task.blockedBy.length > 0) {
|
|
1364
|
+
text += `Blocked By: ${task.blockedBy.join(", ")}
|
|
1365
|
+
`;
|
|
1366
|
+
}
|
|
1367
|
+
if (task.metadata && Object.keys(task.metadata).length > 0) {
|
|
1368
|
+
text += `Metadata: ${JSON.stringify(task.metadata, null, 2)}
|
|
1369
|
+
`;
|
|
1370
|
+
}
|
|
1371
|
+
text += `Created: ${task.createdAt}
|
|
1372
|
+
`;
|
|
1373
|
+
text += `Updated: ${task.updatedAt}`;
|
|
1374
|
+
return createToolResponse({
|
|
1375
|
+
content: [
|
|
1376
|
+
{
|
|
1377
|
+
id: crypto.randomUUID(),
|
|
1378
|
+
type: "text",
|
|
1379
|
+
text
|
|
1380
|
+
}
|
|
1381
|
+
],
|
|
1382
|
+
state: "success"
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
function TaskList() {
|
|
1388
|
+
return {
|
|
1389
|
+
name: "TaskList",
|
|
1390
|
+
description: `Use this tool to list all tasks in the task list.
|
|
1391
|
+
|
|
1392
|
+
## When to Use This Tool
|
|
1393
|
+
|
|
1394
|
+
- To see what tasks are available to work on (status: 'pending', no owner, not blocked)
|
|
1395
|
+
- To check overall progress on the project
|
|
1396
|
+
- To find tasks that are blocked and need dependencies resolved
|
|
1397
|
+
- After completing a task, to check for newly unblocked work or claim the next available task`,
|
|
1398
|
+
inputSchema: z8.object({}),
|
|
1399
|
+
requireUserConfirm: false,
|
|
1400
|
+
call() {
|
|
1401
|
+
const activeTasks = Array.from(taskStore.values()).filter((task) => task.status === "pending" || task.status === "in_progress").sort((a, b) => Number(a.id) - Number(b.id));
|
|
1402
|
+
if (activeTasks.length === 0) {
|
|
1403
|
+
return createToolResponse({
|
|
1404
|
+
content: [
|
|
1405
|
+
{
|
|
1406
|
+
id: crypto.randomUUID(),
|
|
1407
|
+
type: "text",
|
|
1408
|
+
text: "No active tasks found"
|
|
1409
|
+
}
|
|
1410
|
+
],
|
|
1411
|
+
state: "success"
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
1414
|
+
const lines = activeTasks.map((task) => {
|
|
1415
|
+
let line = `#${task.id} [${task.status}] ${task.subject}`;
|
|
1416
|
+
if (task.blockedBy.length > 0) {
|
|
1417
|
+
line += ` (blocked by: #${task.blockedBy.join(", #")})`;
|
|
1418
|
+
}
|
|
1419
|
+
return line;
|
|
1420
|
+
});
|
|
1421
|
+
return createToolResponse({
|
|
1422
|
+
content: [
|
|
1423
|
+
{
|
|
1424
|
+
id: crypto.randomUUID(),
|
|
1425
|
+
type: "text",
|
|
1426
|
+
text: lines.join("\n")
|
|
1427
|
+
}
|
|
1428
|
+
],
|
|
1429
|
+
state: "success"
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
export {
|
|
1435
|
+
Bash,
|
|
1436
|
+
Edit,
|
|
1437
|
+
Glob,
|
|
1438
|
+
Grep,
|
|
1439
|
+
Read,
|
|
1440
|
+
TaskCreate,
|
|
1441
|
+
TaskGet,
|
|
1442
|
+
TaskList,
|
|
1443
|
+
TaskUpdate,
|
|
1444
|
+
Toolkit,
|
|
1445
|
+
Write
|
|
1446
|
+
};
|
|
1447
|
+
//# sourceMappingURL=index.mjs.map
|