fourmis-agents-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +309 -0
- package/dist/agent-loop.d.ts +36 -0
- package/dist/agent-loop.d.ts.map +1 -0
- package/dist/agent-loop.js +387 -0
- package/dist/agents/index.d.ts +8 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +2309 -0
- package/dist/agents/task-manager.d.ts +14 -0
- package/dist/agents/task-manager.d.ts.map +1 -0
- package/dist/agents/task-manager.js +67 -0
- package/dist/agents/tools.d.ts +24 -0
- package/dist/agents/tools.d.ts.map +1 -0
- package/dist/agents/tools.js +2257 -0
- package/dist/agents/types.d.ts +22 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/types.js +1 -0
- package/dist/api.d.ts +35 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +2983 -0
- package/dist/auth/login-openai.d.ts +10 -0
- package/dist/auth/login-openai.d.ts.map +1 -0
- package/dist/auth/login-openai.js +316 -0
- package/dist/auth/openai-oauth.d.ts +45 -0
- package/dist/auth/openai-oauth.d.ts.map +1 -0
- package/dist/auth/openai-oauth.js +308 -0
- package/dist/hooks.d.ts +71 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +87 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3025 -0
- package/dist/mcp/client.d.ts +24 -0
- package/dist/mcp/client.d.ts.map +1 -0
- package/dist/mcp/client.js +176 -0
- package/dist/mcp/index.d.ts +8 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +203 -0
- package/dist/mcp/server.d.ts +25 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +42 -0
- package/dist/mcp/types.d.ts +47 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +1 -0
- package/dist/permissions.d.ts +29 -0
- package/dist/permissions.d.ts.map +1 -0
- package/dist/permissions.js +157 -0
- package/dist/providers/anthropic.d.ts +26 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +382 -0
- package/dist/providers/openai.d.ts +42 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +871 -0
- package/dist/providers/registry.d.ts +11 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +1118 -0
- package/dist/providers/types.d.ts +79 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +1 -0
- package/dist/query.d.ts +9 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +36 -0
- package/dist/settings.d.ts +28 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +143 -0
- package/dist/tools/bash.d.ts +6 -0
- package/dist/tools/bash.d.ts.map +1 -0
- package/dist/tools/bash.js +88 -0
- package/dist/tools/edit.d.ts +6 -0
- package/dist/tools/edit.d.ts.map +1 -0
- package/dist/tools/edit.js +108 -0
- package/dist/tools/glob.d.ts +6 -0
- package/dist/tools/glob.d.ts.map +1 -0
- package/dist/tools/glob.js +70 -0
- package/dist/tools/grep.d.ts +7 -0
- package/dist/tools/grep.d.ts.map +1 -0
- package/dist/tools/grep.js +183 -0
- package/dist/tools/index.d.ts +18 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +595 -0
- package/dist/tools/mcp-resources.d.ts +8 -0
- package/dist/tools/mcp-resources.d.ts.map +1 -0
- package/dist/tools/mcp-resources.js +87 -0
- package/dist/tools/presets.d.ts +6 -0
- package/dist/tools/presets.d.ts.map +1 -0
- package/dist/tools/presets.js +32 -0
- package/dist/tools/read.d.ts +6 -0
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +81 -0
- package/dist/tools/registry.d.ts +31 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +52 -0
- package/dist/tools/write.d.ts +6 -0
- package/dist/tools/write.d.ts.map +1 -0
- package/dist/tools/write.js +62 -0
- package/dist/types.d.ts +201 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +39 -0
- package/dist/utils/cost.d.ts +35 -0
- package/dist/utils/cost.d.ts.map +1 -0
- package/dist/utils/cost.js +176 -0
- package/dist/utils/system-prompt.d.ts +11 -0
- package/dist/utils/system-prompt.d.ts.map +1 -0
- package/dist/utils/system-prompt.js +89 -0
- package/package.json +66 -0
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
set: (newValue) => all[name] = () => newValue
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
13
|
+
var __require = import.meta.require;
|
|
14
|
+
|
|
15
|
+
// src/tools/registry.ts
|
|
16
|
+
class ToolRegistry {
|
|
17
|
+
tools = new Map;
|
|
18
|
+
register(tool) {
|
|
19
|
+
this.tools.set(tool.name, tool);
|
|
20
|
+
}
|
|
21
|
+
get(name) {
|
|
22
|
+
return this.tools.get(name);
|
|
23
|
+
}
|
|
24
|
+
has(name) {
|
|
25
|
+
return this.tools.has(name);
|
|
26
|
+
}
|
|
27
|
+
getDefinitions() {
|
|
28
|
+
return [...this.tools.values()].map((tool) => ({
|
|
29
|
+
name: tool.name,
|
|
30
|
+
description: tool.description,
|
|
31
|
+
inputSchema: tool.inputSchema
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
async execute(name, input, ctx) {
|
|
35
|
+
const tool = this.tools.get(name);
|
|
36
|
+
if (!tool) {
|
|
37
|
+
return { content: `Unknown tool: ${name}`, isError: true };
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
return await tool.execute(input, ctx);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
43
|
+
return { content: `Tool error: ${message}`, isError: true };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
list() {
|
|
47
|
+
return [...this.tools.keys()];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/tools/presets.ts
|
|
52
|
+
var PRESETS = {
|
|
53
|
+
coding: ["Bash", "Read", "Write", "Edit", "Glob", "Grep"],
|
|
54
|
+
readonly: ["Read", "Glob", "Grep"],
|
|
55
|
+
minimal: ["Read", "Write", "Edit", "Glob", "Grep"]
|
|
56
|
+
};
|
|
57
|
+
function resolveToolNames(tools) {
|
|
58
|
+
if (!tools)
|
|
59
|
+
return PRESETS.coding;
|
|
60
|
+
if (typeof tools === "string") {
|
|
61
|
+
return PRESETS[tools] ?? [tools];
|
|
62
|
+
}
|
|
63
|
+
return tools;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/tools/bash.ts
|
|
67
|
+
var DEFAULT_TIMEOUT = 120000;
|
|
68
|
+
var MAX_TIMEOUT = 600000;
|
|
69
|
+
var MAX_OUTPUT_LENGTH = 30000;
|
|
70
|
+
var BashTool = {
|
|
71
|
+
name: "Bash",
|
|
72
|
+
description: "Executes a bash command. Use for system operations, git commands, running scripts, and other terminal tasks. " + "Working directory persists between calls. Commands timeout after 120s by default (max 600s).",
|
|
73
|
+
inputSchema: {
|
|
74
|
+
type: "object",
|
|
75
|
+
properties: {
|
|
76
|
+
command: {
|
|
77
|
+
type: "string",
|
|
78
|
+
description: "The bash command to execute"
|
|
79
|
+
},
|
|
80
|
+
description: {
|
|
81
|
+
type: "string",
|
|
82
|
+
description: "Brief description of what this command does"
|
|
83
|
+
},
|
|
84
|
+
timeout: {
|
|
85
|
+
type: "number",
|
|
86
|
+
description: "Timeout in milliseconds (max 600000)"
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
required: ["command"]
|
|
90
|
+
},
|
|
91
|
+
async execute(input, ctx) {
|
|
92
|
+
const { command, timeout: timeoutMs, description } = input;
|
|
93
|
+
if (!command || typeof command !== "string") {
|
|
94
|
+
return { content: "Error: command is required", isError: true };
|
|
95
|
+
}
|
|
96
|
+
const timeout = Math.min(timeoutMs ?? DEFAULT_TIMEOUT, MAX_TIMEOUT);
|
|
97
|
+
try {
|
|
98
|
+
const proc = Bun.spawn(["bash", "-c", command], {
|
|
99
|
+
cwd: ctx.cwd,
|
|
100
|
+
stdout: "pipe",
|
|
101
|
+
stderr: "pipe",
|
|
102
|
+
env: { ...process.env, ...ctx.env }
|
|
103
|
+
});
|
|
104
|
+
const timeoutId = setTimeout(() => {
|
|
105
|
+
proc.kill();
|
|
106
|
+
}, timeout);
|
|
107
|
+
const [stdout, stderr] = await Promise.all([
|
|
108
|
+
new Response(proc.stdout).text(),
|
|
109
|
+
new Response(proc.stderr).text()
|
|
110
|
+
]);
|
|
111
|
+
const exitCode = await proc.exited;
|
|
112
|
+
clearTimeout(timeoutId);
|
|
113
|
+
let output = "";
|
|
114
|
+
if (stdout)
|
|
115
|
+
output += stdout;
|
|
116
|
+
if (stderr)
|
|
117
|
+
output += (output ? `
|
|
118
|
+
` : "") + stderr;
|
|
119
|
+
if (output.length > MAX_OUTPUT_LENGTH) {
|
|
120
|
+
output = output.slice(0, MAX_OUTPUT_LENGTH) + `
|
|
121
|
+
... (output truncated)`;
|
|
122
|
+
}
|
|
123
|
+
if (!output) {
|
|
124
|
+
output = exitCode === 0 ? "(no output)" : `Command failed with exit code ${exitCode}`;
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
content: output,
|
|
128
|
+
isError: exitCode !== 0 ? true : undefined,
|
|
129
|
+
metadata: { exitCode }
|
|
130
|
+
};
|
|
131
|
+
} catch (err) {
|
|
132
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
133
|
+
return { content: `Error executing command: ${message}`, isError: true };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// src/tools/read.ts
|
|
139
|
+
var MAX_LINE_LENGTH = 2000;
|
|
140
|
+
var DEFAULT_LINE_LIMIT = 2000;
|
|
141
|
+
var ReadTool = {
|
|
142
|
+
name: "Read",
|
|
143
|
+
description: "Reads a file from the filesystem. Returns content with line numbers (cat -n format). " + "Supports offset/limit for large files. Lines longer than 2000 chars are truncated.",
|
|
144
|
+
inputSchema: {
|
|
145
|
+
type: "object",
|
|
146
|
+
properties: {
|
|
147
|
+
file_path: {
|
|
148
|
+
type: "string",
|
|
149
|
+
description: "Absolute path to the file to read"
|
|
150
|
+
},
|
|
151
|
+
offset: {
|
|
152
|
+
type: "number",
|
|
153
|
+
description: "Line number to start reading from (1-based)"
|
|
154
|
+
},
|
|
155
|
+
limit: {
|
|
156
|
+
type: "number",
|
|
157
|
+
description: "Number of lines to read"
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
required: ["file_path"]
|
|
161
|
+
},
|
|
162
|
+
async execute(input, ctx) {
|
|
163
|
+
const { file_path, offset, limit } = input;
|
|
164
|
+
if (!file_path) {
|
|
165
|
+
return { content: "Error: file_path is required", isError: true };
|
|
166
|
+
}
|
|
167
|
+
const resolvedPath = resolvePath(file_path, ctx.cwd);
|
|
168
|
+
try {
|
|
169
|
+
const file = Bun.file(resolvedPath);
|
|
170
|
+
const exists = await file.exists();
|
|
171
|
+
if (!exists) {
|
|
172
|
+
return { content: `Error: File not found: ${resolvedPath}`, isError: true };
|
|
173
|
+
}
|
|
174
|
+
const text = await file.text();
|
|
175
|
+
const lines = text.split(`
|
|
176
|
+
`);
|
|
177
|
+
const startLine = Math.max(1, offset ?? 1);
|
|
178
|
+
const lineLimit = limit ?? DEFAULT_LINE_LIMIT;
|
|
179
|
+
const endLine = Math.min(lines.length, startLine + lineLimit - 1);
|
|
180
|
+
const numberedLines = [];
|
|
181
|
+
for (let i = startLine - 1;i < endLine; i++) {
|
|
182
|
+
let line = lines[i];
|
|
183
|
+
if (line.length > MAX_LINE_LENGTH) {
|
|
184
|
+
line = line.slice(0, MAX_LINE_LENGTH) + "... (truncated)";
|
|
185
|
+
}
|
|
186
|
+
const lineNum = String(i + 1).padStart(6, " ");
|
|
187
|
+
numberedLines.push(`${lineNum} ${line}`);
|
|
188
|
+
}
|
|
189
|
+
return { content: numberedLines.join(`
|
|
190
|
+
`) };
|
|
191
|
+
} catch (err) {
|
|
192
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
193
|
+
return { content: `Error reading file: ${message}`, isError: true };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
function resolvePath(filePath, cwd) {
|
|
198
|
+
if (filePath.startsWith("/"))
|
|
199
|
+
return filePath;
|
|
200
|
+
return `${cwd}/${filePath}`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/tools/write.ts
|
|
204
|
+
import { mkdir } from "fs/promises";
|
|
205
|
+
import { dirname } from "path";
|
|
206
|
+
var WriteTool = {
|
|
207
|
+
name: "Write",
|
|
208
|
+
description: "Writes content to a file. Creates parent directories if needed. " + "Overwrites existing files.",
|
|
209
|
+
inputSchema: {
|
|
210
|
+
type: "object",
|
|
211
|
+
properties: {
|
|
212
|
+
file_path: {
|
|
213
|
+
type: "string",
|
|
214
|
+
description: "Absolute path to the file to write"
|
|
215
|
+
},
|
|
216
|
+
content: {
|
|
217
|
+
type: "string",
|
|
218
|
+
description: "The content to write to the file"
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
required: ["file_path", "content"]
|
|
222
|
+
},
|
|
223
|
+
async execute(input, ctx) {
|
|
224
|
+
const { file_path, content } = input;
|
|
225
|
+
if (!file_path) {
|
|
226
|
+
return { content: "Error: file_path is required", isError: true };
|
|
227
|
+
}
|
|
228
|
+
if (content === undefined || content === null) {
|
|
229
|
+
return { content: "Error: content is required", isError: true };
|
|
230
|
+
}
|
|
231
|
+
const resolvedPath = file_path.startsWith("/") ? file_path : `${ctx.cwd}/${file_path}`;
|
|
232
|
+
try {
|
|
233
|
+
const dir = dirname(resolvedPath);
|
|
234
|
+
await mkdir(dir, { recursive: true });
|
|
235
|
+
await Bun.write(resolvedPath, content);
|
|
236
|
+
const lines = content.split(`
|
|
237
|
+
`).length;
|
|
238
|
+
return {
|
|
239
|
+
content: `Successfully wrote ${lines} lines to ${resolvedPath}`,
|
|
240
|
+
metadata: { path: resolvedPath, lines }
|
|
241
|
+
};
|
|
242
|
+
} catch (err) {
|
|
243
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
244
|
+
return { content: `Error writing file: ${message}`, isError: true };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// src/tools/edit.ts
|
|
250
|
+
var EditTool = {
|
|
251
|
+
name: "Edit",
|
|
252
|
+
description: "Performs exact string replacements in files. The old_string must be unique in the file " + "unless replace_all is true. Use this for precise edits to existing files.",
|
|
253
|
+
inputSchema: {
|
|
254
|
+
type: "object",
|
|
255
|
+
properties: {
|
|
256
|
+
file_path: {
|
|
257
|
+
type: "string",
|
|
258
|
+
description: "Absolute path to the file to edit"
|
|
259
|
+
},
|
|
260
|
+
old_string: {
|
|
261
|
+
type: "string",
|
|
262
|
+
description: "The exact text to find and replace"
|
|
263
|
+
},
|
|
264
|
+
new_string: {
|
|
265
|
+
type: "string",
|
|
266
|
+
description: "The replacement text"
|
|
267
|
+
},
|
|
268
|
+
replace_all: {
|
|
269
|
+
type: "boolean",
|
|
270
|
+
description: "Replace all occurrences (default: false)",
|
|
271
|
+
default: false
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
required: ["file_path", "old_string", "new_string"]
|
|
275
|
+
},
|
|
276
|
+
async execute(input, ctx) {
|
|
277
|
+
const { file_path, old_string, new_string, replace_all = false } = input;
|
|
278
|
+
if (!file_path) {
|
|
279
|
+
return { content: "Error: file_path is required", isError: true };
|
|
280
|
+
}
|
|
281
|
+
if (old_string === undefined) {
|
|
282
|
+
return { content: "Error: old_string is required", isError: true };
|
|
283
|
+
}
|
|
284
|
+
if (new_string === undefined) {
|
|
285
|
+
return { content: "Error: new_string is required", isError: true };
|
|
286
|
+
}
|
|
287
|
+
if (old_string === new_string) {
|
|
288
|
+
return { content: "Error: old_string and new_string are identical", isError: true };
|
|
289
|
+
}
|
|
290
|
+
const resolvedPath = file_path.startsWith("/") ? file_path : `${ctx.cwd}/${file_path}`;
|
|
291
|
+
try {
|
|
292
|
+
const file = Bun.file(resolvedPath);
|
|
293
|
+
const exists = await file.exists();
|
|
294
|
+
if (!exists) {
|
|
295
|
+
return { content: `Error: File not found: ${resolvedPath}`, isError: true };
|
|
296
|
+
}
|
|
297
|
+
const content = await file.text();
|
|
298
|
+
let count = 0;
|
|
299
|
+
let searchFrom = 0;
|
|
300
|
+
while (true) {
|
|
301
|
+
const idx = content.indexOf(old_string, searchFrom);
|
|
302
|
+
if (idx === -1)
|
|
303
|
+
break;
|
|
304
|
+
count++;
|
|
305
|
+
searchFrom = idx + old_string.length;
|
|
306
|
+
}
|
|
307
|
+
if (count === 0) {
|
|
308
|
+
const preview = old_string.length > 100 ? old_string.slice(0, 100) + "..." : old_string;
|
|
309
|
+
return {
|
|
310
|
+
content: `Error: old_string not found in ${resolvedPath}. Searched for:
|
|
311
|
+
${preview}`,
|
|
312
|
+
isError: true
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
if (count > 1 && !replace_all) {
|
|
316
|
+
return {
|
|
317
|
+
content: `Error: old_string appears ${count} times in the file. Use replace_all: true to replace all occurrences, or provide a longer string with more context to make it unique.`,
|
|
318
|
+
isError: true
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
let newContent;
|
|
322
|
+
if (replace_all) {
|
|
323
|
+
newContent = content.replaceAll(old_string, new_string);
|
|
324
|
+
} else {
|
|
325
|
+
const idx = content.indexOf(old_string);
|
|
326
|
+
newContent = content.slice(0, idx) + new_string + content.slice(idx + old_string.length);
|
|
327
|
+
}
|
|
328
|
+
await Bun.write(resolvedPath, newContent);
|
|
329
|
+
const replacements = replace_all ? count : 1;
|
|
330
|
+
return {
|
|
331
|
+
content: `Successfully replaced ${replacements} occurrence${replacements > 1 ? "s" : ""} in ${resolvedPath}`,
|
|
332
|
+
metadata: { path: resolvedPath, replacements }
|
|
333
|
+
};
|
|
334
|
+
} catch (err) {
|
|
335
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
336
|
+
return { content: `Error editing file: ${message}`, isError: true };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// src/tools/glob.ts
|
|
342
|
+
var {Glob } = globalThis.Bun;
|
|
343
|
+
var GlobTool = {
|
|
344
|
+
name: "Glob",
|
|
345
|
+
description: "Fast file pattern matching. Supports glob patterns like '**/*.ts' or 'src/**/*.tsx'. " + "Returns matching file paths sorted by modification time.",
|
|
346
|
+
inputSchema: {
|
|
347
|
+
type: "object",
|
|
348
|
+
properties: {
|
|
349
|
+
pattern: {
|
|
350
|
+
type: "string",
|
|
351
|
+
description: "Glob pattern to match files against"
|
|
352
|
+
},
|
|
353
|
+
path: {
|
|
354
|
+
type: "string",
|
|
355
|
+
description: "Directory to search in (defaults to cwd)"
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
required: ["pattern"]
|
|
359
|
+
},
|
|
360
|
+
async execute(input, ctx) {
|
|
361
|
+
const { pattern, path } = input;
|
|
362
|
+
if (!pattern) {
|
|
363
|
+
return { content: "Error: pattern is required", isError: true };
|
|
364
|
+
}
|
|
365
|
+
const searchDir = path ? path.startsWith("/") ? path : `${ctx.cwd}/${path}` : ctx.cwd;
|
|
366
|
+
try {
|
|
367
|
+
const glob = new Glob(pattern);
|
|
368
|
+
const matches = [];
|
|
369
|
+
for await (const filePath of glob.scan({ cwd: searchDir, dot: false })) {
|
|
370
|
+
try {
|
|
371
|
+
const file = Bun.file(`${searchDir}/${filePath}`);
|
|
372
|
+
const stat = await file.stat();
|
|
373
|
+
matches.push({ path: filePath, mtime: stat?.mtime?.getTime() ?? 0 });
|
|
374
|
+
} catch {
|
|
375
|
+
matches.push({ path: filePath, mtime: 0 });
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
matches.sort((a, b) => b.mtime - a.mtime);
|
|
379
|
+
if (matches.length === 0) {
|
|
380
|
+
return { content: "No files matched the pattern." };
|
|
381
|
+
}
|
|
382
|
+
const result = matches.map((m) => m.path).join(`
|
|
383
|
+
`);
|
|
384
|
+
return {
|
|
385
|
+
content: result,
|
|
386
|
+
metadata: { count: matches.length }
|
|
387
|
+
};
|
|
388
|
+
} catch (err) {
|
|
389
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
390
|
+
return { content: `Error: ${message}`, isError: true };
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
// src/tools/grep.ts
|
|
396
|
+
var GrepTool = {
|
|
397
|
+
name: "Grep",
|
|
398
|
+
description: "Search file contents using regex patterns. Supports multiple output modes: " + "'content' (matching lines), 'files_with_matches' (file paths only, default), 'count' (match counts). " + "Supports context lines, case-insensitive search, glob filtering, and head_limit.",
|
|
399
|
+
inputSchema: {
|
|
400
|
+
type: "object",
|
|
401
|
+
properties: {
|
|
402
|
+
pattern: {
|
|
403
|
+
type: "string",
|
|
404
|
+
description: "Regex pattern to search for"
|
|
405
|
+
},
|
|
406
|
+
path: {
|
|
407
|
+
type: "string",
|
|
408
|
+
description: "File or directory to search in (defaults to cwd)"
|
|
409
|
+
},
|
|
410
|
+
glob: {
|
|
411
|
+
type: "string",
|
|
412
|
+
description: "Glob pattern to filter files (e.g., '*.ts')"
|
|
413
|
+
},
|
|
414
|
+
output_mode: {
|
|
415
|
+
type: "string",
|
|
416
|
+
enum: ["content", "files_with_matches", "count"],
|
|
417
|
+
description: "Output mode (default: files_with_matches)"
|
|
418
|
+
},
|
|
419
|
+
"-i": {
|
|
420
|
+
type: "boolean",
|
|
421
|
+
description: "Case insensitive search"
|
|
422
|
+
},
|
|
423
|
+
"-n": {
|
|
424
|
+
type: "boolean",
|
|
425
|
+
description: "Show line numbers (for content mode)"
|
|
426
|
+
},
|
|
427
|
+
"-A": {
|
|
428
|
+
type: "number",
|
|
429
|
+
description: "Lines to show after each match"
|
|
430
|
+
},
|
|
431
|
+
"-B": {
|
|
432
|
+
type: "number",
|
|
433
|
+
description: "Lines to show before each match"
|
|
434
|
+
},
|
|
435
|
+
"-C": {
|
|
436
|
+
type: "number",
|
|
437
|
+
description: "Context lines before and after each match"
|
|
438
|
+
},
|
|
439
|
+
head_limit: {
|
|
440
|
+
type: "number",
|
|
441
|
+
description: "Limit output to first N entries"
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
required: ["pattern"]
|
|
445
|
+
},
|
|
446
|
+
async execute(input, ctx) {
|
|
447
|
+
const opts = input;
|
|
448
|
+
if (!opts.pattern) {
|
|
449
|
+
return { content: "Error: pattern is required", isError: true };
|
|
450
|
+
}
|
|
451
|
+
const searchPath = opts.path ? opts.path.startsWith("/") ? opts.path : `${ctx.cwd}/${opts.path}` : ctx.cwd;
|
|
452
|
+
const mode = opts.output_mode ?? "files_with_matches";
|
|
453
|
+
try {
|
|
454
|
+
return await runRipgrep(opts, searchPath, mode, ctx);
|
|
455
|
+
} catch {
|
|
456
|
+
return await runJsGrep(opts, searchPath, mode, ctx);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
async function runRipgrep(opts, searchPath, mode, ctx) {
|
|
461
|
+
const args = ["rg"];
|
|
462
|
+
if (mode === "files_with_matches") {
|
|
463
|
+
args.push("-l");
|
|
464
|
+
} else if (mode === "count") {
|
|
465
|
+
args.push("-c");
|
|
466
|
+
}
|
|
467
|
+
if (opts["-i"])
|
|
468
|
+
args.push("-i");
|
|
469
|
+
if (opts["-n"] !== false && mode === "content")
|
|
470
|
+
args.push("-n");
|
|
471
|
+
if (opts["-A"])
|
|
472
|
+
args.push("-A", String(opts["-A"]));
|
|
473
|
+
if (opts["-B"])
|
|
474
|
+
args.push("-B", String(opts["-B"]));
|
|
475
|
+
if (opts["-C"])
|
|
476
|
+
args.push("-C", String(opts["-C"]));
|
|
477
|
+
if (opts.glob)
|
|
478
|
+
args.push("--glob", opts.glob);
|
|
479
|
+
args.push("--", opts.pattern, searchPath);
|
|
480
|
+
const proc = Bun.spawn(args, {
|
|
481
|
+
stdout: "pipe",
|
|
482
|
+
stderr: "pipe",
|
|
483
|
+
env: { ...process.env, ...ctx.env }
|
|
484
|
+
});
|
|
485
|
+
const stdout = await new Response(proc.stdout).text();
|
|
486
|
+
const stderr = await new Response(proc.stderr).text();
|
|
487
|
+
const exitCode = await proc.exited;
|
|
488
|
+
if (exitCode === 2) {
|
|
489
|
+
throw new Error(stderr || "ripgrep error");
|
|
490
|
+
}
|
|
491
|
+
let output = stdout.trim();
|
|
492
|
+
if (opts.head_limit && output) {
|
|
493
|
+
const lines = output.split(`
|
|
494
|
+
`);
|
|
495
|
+
output = lines.slice(0, opts.head_limit).join(`
|
|
496
|
+
`);
|
|
497
|
+
}
|
|
498
|
+
return { content: output || "No matches found." };
|
|
499
|
+
}
|
|
500
|
+
async function runJsGrep(opts, searchPath, mode, ctx) {
|
|
501
|
+
const flags = opts["-i"] ? "gi" : "g";
|
|
502
|
+
let regex;
|
|
503
|
+
try {
|
|
504
|
+
regex = new RegExp(opts.pattern, flags);
|
|
505
|
+
} catch (err) {
|
|
506
|
+
return { content: `Invalid regex: ${opts.pattern}`, isError: true };
|
|
507
|
+
}
|
|
508
|
+
const files = await collectFiles(searchPath, opts.glob);
|
|
509
|
+
const results = [];
|
|
510
|
+
let totalCount = 0;
|
|
511
|
+
for (const filePath of files) {
|
|
512
|
+
try {
|
|
513
|
+
const content = await Bun.file(filePath).text();
|
|
514
|
+
const lines = content.split(`
|
|
515
|
+
`);
|
|
516
|
+
const matchedLines = [];
|
|
517
|
+
for (let i = 0;i < lines.length; i++) {
|
|
518
|
+
if (regex.test(lines[i])) {
|
|
519
|
+
matchedLines.push({ num: i + 1, line: lines[i] });
|
|
520
|
+
regex.lastIndex = 0;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (matchedLines.length === 0)
|
|
524
|
+
continue;
|
|
525
|
+
totalCount += matchedLines.length;
|
|
526
|
+
const relativePath = filePath.startsWith(ctx.cwd) ? filePath.slice(ctx.cwd.length + 1) : filePath;
|
|
527
|
+
if (mode === "files_with_matches") {
|
|
528
|
+
results.push(relativePath);
|
|
529
|
+
} else if (mode === "count") {
|
|
530
|
+
results.push(`${relativePath}:${matchedLines.length}`);
|
|
531
|
+
} else {
|
|
532
|
+
for (const { num, line } of matchedLines) {
|
|
533
|
+
results.push(`${relativePath}:${num}:${line}`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
} catch {}
|
|
537
|
+
if (opts.head_limit && results.length >= opts.head_limit) {
|
|
538
|
+
break;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
let output = results.join(`
|
|
542
|
+
`);
|
|
543
|
+
if (opts.head_limit) {
|
|
544
|
+
const entries = output.split(`
|
|
545
|
+
`).slice(0, opts.head_limit);
|
|
546
|
+
output = entries.join(`
|
|
547
|
+
`);
|
|
548
|
+
}
|
|
549
|
+
return { content: output || "No matches found." };
|
|
550
|
+
}
|
|
551
|
+
async function collectFiles(dir, globPattern) {
|
|
552
|
+
const { Glob: Glob2 } = await Promise.resolve(globalThis.Bun);
|
|
553
|
+
const pattern = globPattern ?? "**/*";
|
|
554
|
+
const glob = new Glob2(pattern);
|
|
555
|
+
const files = [];
|
|
556
|
+
for await (const path of glob.scan({ cwd: dir, dot: false, onlyFiles: true })) {
|
|
557
|
+
files.push(`${dir}/${path}`);
|
|
558
|
+
}
|
|
559
|
+
return files;
|
|
560
|
+
}
|
|
561
|
+
// src/tools/index.ts
|
|
562
|
+
var ALL_TOOLS = {
|
|
563
|
+
Bash: BashTool,
|
|
564
|
+
Read: ReadTool,
|
|
565
|
+
Write: WriteTool,
|
|
566
|
+
Edit: EditTool,
|
|
567
|
+
Glob: GlobTool,
|
|
568
|
+
Grep: GrepTool
|
|
569
|
+
};
|
|
570
|
+
function buildToolRegistry(toolNames, allowedTools, disallowedTools) {
|
|
571
|
+
const registry = new ToolRegistry;
|
|
572
|
+
for (const name of toolNames) {
|
|
573
|
+
if (allowedTools && !allowedTools.includes(name))
|
|
574
|
+
continue;
|
|
575
|
+
if (disallowedTools?.includes(name))
|
|
576
|
+
continue;
|
|
577
|
+
const tool = ALL_TOOLS[name];
|
|
578
|
+
if (tool) {
|
|
579
|
+
registry.register(tool);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return registry;
|
|
583
|
+
}
|
|
584
|
+
export {
|
|
585
|
+
resolveToolNames,
|
|
586
|
+
buildToolRegistry,
|
|
587
|
+
WriteTool,
|
|
588
|
+
ToolRegistry,
|
|
589
|
+
ReadTool,
|
|
590
|
+
PRESETS,
|
|
591
|
+
GrepTool,
|
|
592
|
+
GlobTool,
|
|
593
|
+
EditTool,
|
|
594
|
+
BashTool
|
|
595
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP resource tools — list and read resources from MCP servers.
|
|
3
|
+
*/
|
|
4
|
+
import type { ToolImplementation } from "./registry.js";
|
|
5
|
+
import type { McpClientManager } from "../mcp/client.js";
|
|
6
|
+
export declare function createListMcpResourcesTool(mcpClient: McpClientManager): ToolImplementation;
|
|
7
|
+
export declare function createReadMcpResourceTool(mcpClient: McpClientManager): ToolImplementation;
|
|
8
|
+
//# sourceMappingURL=mcp-resources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-resources.d.ts","sourceRoot":"","sources":["../../src/tools/mcp-resources.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,gBAAgB,GAAG,kBAAkB,CA8B1F;AAED,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,gBAAgB,GAAG,kBAAkB,CAgCzF"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true,
|
|
8
|
+
configurable: true,
|
|
9
|
+
set: (newValue) => all[name] = () => newValue
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
13
|
+
var __require = import.meta.require;
|
|
14
|
+
|
|
15
|
+
// src/tools/mcp-resources.ts
|
|
16
|
+
var exports_mcp_resources = {};
|
|
17
|
+
__export(exports_mcp_resources, {
|
|
18
|
+
createReadMcpResourceTool: () => createReadMcpResourceTool,
|
|
19
|
+
createListMcpResourcesTool: () => createListMcpResourcesTool
|
|
20
|
+
});
|
|
21
|
+
function createListMcpResourcesTool(mcpClient) {
|
|
22
|
+
return {
|
|
23
|
+
name: "mcp__list_resources",
|
|
24
|
+
description: "List available resources from MCP servers.",
|
|
25
|
+
inputSchema: {
|
|
26
|
+
type: "object",
|
|
27
|
+
properties: {
|
|
28
|
+
server: {
|
|
29
|
+
type: "string",
|
|
30
|
+
description: "Optional server name to filter by. If omitted, lists resources from all servers."
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
async execute(input) {
|
|
35
|
+
const { server } = input ?? {};
|
|
36
|
+
try {
|
|
37
|
+
const resources = await mcpClient.listResources(server);
|
|
38
|
+
if (resources.length === 0) {
|
|
39
|
+
return { content: "No resources available." };
|
|
40
|
+
}
|
|
41
|
+
const lines = resources.map((r) => `[${r.server}] ${r.uri} - ${r.name}${r.description ? `: ${r.description}` : ""}`);
|
|
42
|
+
return { content: lines.join(`
|
|
43
|
+
`) };
|
|
44
|
+
} catch (err) {
|
|
45
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
46
|
+
return { content: `Error listing resources: ${message}`, isError: true };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function createReadMcpResourceTool(mcpClient) {
|
|
52
|
+
return {
|
|
53
|
+
name: "mcp__read_resource",
|
|
54
|
+
description: "Read a specific resource from an MCP server by URI.",
|
|
55
|
+
inputSchema: {
|
|
56
|
+
type: "object",
|
|
57
|
+
properties: {
|
|
58
|
+
server: {
|
|
59
|
+
type: "string",
|
|
60
|
+
description: "The MCP server name that hosts the resource."
|
|
61
|
+
},
|
|
62
|
+
uri: {
|
|
63
|
+
type: "string",
|
|
64
|
+
description: "The resource URI to read."
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
required: ["server", "uri"]
|
|
68
|
+
},
|
|
69
|
+
async execute(input) {
|
|
70
|
+
const { server, uri } = input;
|
|
71
|
+
if (!server || !uri) {
|
|
72
|
+
return { content: "Both 'server' and 'uri' are required.", isError: true };
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const content = await mcpClient.readResource(server, uri);
|
|
76
|
+
return { content };
|
|
77
|
+
} catch (err) {
|
|
78
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
79
|
+
return { content: `Error reading resource: ${message}`, isError: true };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export {
|
|
85
|
+
createReadMcpResourceTool,
|
|
86
|
+
createListMcpResourcesTool
|
|
87
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../../src/tools/presets.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAI5C,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,GAAG,MAAM,EAAE,CAM/E"}
|