formagent-sdk 0.1.3 → 0.2.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/dist/cli/index.js +1044 -48
- package/dist/index.js +1266 -52
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -5,7 +5,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
5
5
|
// src/cli/cli.ts
|
|
6
6
|
import * as readline from "node:readline";
|
|
7
7
|
import { homedir as homedir4 } from "node:os";
|
|
8
|
-
import { join as
|
|
8
|
+
import { join as join8 } from "node:path";
|
|
9
9
|
import { existsSync as existsSync10 } from "node:fs";
|
|
10
10
|
|
|
11
11
|
// src/utils/id.ts
|
|
@@ -76,6 +76,113 @@ class TypedEventEmitter {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
// src/utils/truncation.ts
|
|
80
|
+
import * as fs from "node:fs/promises";
|
|
81
|
+
import * as path from "node:path";
|
|
82
|
+
import * as os from "node:os";
|
|
83
|
+
var TRUNCATION_DEFAULTS = {
|
|
84
|
+
MAX_LINES: 2000,
|
|
85
|
+
MAX_BYTES: 50 * 1024,
|
|
86
|
+
RETENTION_MS: 7 * 24 * 60 * 60 * 1000
|
|
87
|
+
};
|
|
88
|
+
function getTempDir(config) {
|
|
89
|
+
return config?.tempDir ?? path.join(os.tmpdir(), "formagent-sdk-output");
|
|
90
|
+
}
|
|
91
|
+
function generateOutputFilename() {
|
|
92
|
+
const timestamp = Date.now();
|
|
93
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
94
|
+
return `tool_${timestamp}_${random}.txt`;
|
|
95
|
+
}
|
|
96
|
+
async function ensureTempDir(dir) {
|
|
97
|
+
try {
|
|
98
|
+
await fs.mkdir(dir, { recursive: true });
|
|
99
|
+
} catch {}
|
|
100
|
+
}
|
|
101
|
+
async function truncateOutput(text, config = {}) {
|
|
102
|
+
const maxLines = config.maxLines ?? TRUNCATION_DEFAULTS.MAX_LINES;
|
|
103
|
+
const maxBytes = config.maxBytes ?? TRUNCATION_DEFAULTS.MAX_BYTES;
|
|
104
|
+
const direction = config.direction ?? "head";
|
|
105
|
+
const saveToFile = config.saveToFile ?? true;
|
|
106
|
+
const lines = text.split(`
|
|
107
|
+
`);
|
|
108
|
+
const totalBytes = Buffer.byteLength(text, "utf-8");
|
|
109
|
+
if (lines.length <= maxLines && totalBytes <= maxBytes) {
|
|
110
|
+
return {
|
|
111
|
+
content: text,
|
|
112
|
+
truncated: false,
|
|
113
|
+
originalBytes: totalBytes,
|
|
114
|
+
originalLines: lines.length
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const out = [];
|
|
118
|
+
let bytes = 0;
|
|
119
|
+
let hitBytes = false;
|
|
120
|
+
if (direction === "head") {
|
|
121
|
+
for (let i = 0;i < lines.length && i < maxLines; i++) {
|
|
122
|
+
const lineBytes = Buffer.byteLength(lines[i], "utf-8") + (i > 0 ? 1 : 0);
|
|
123
|
+
if (bytes + lineBytes > maxBytes) {
|
|
124
|
+
hitBytes = true;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
out.push(lines[i]);
|
|
128
|
+
bytes += lineBytes;
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
for (let i = lines.length - 1;i >= 0 && out.length < maxLines; i--) {
|
|
132
|
+
const lineBytes = Buffer.byteLength(lines[i], "utf-8") + (out.length > 0 ? 1 : 0);
|
|
133
|
+
if (bytes + lineBytes > maxBytes) {
|
|
134
|
+
hitBytes = true;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
out.unshift(lines[i]);
|
|
138
|
+
bytes += lineBytes;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const removed = hitBytes ? totalBytes - bytes : lines.length - out.length;
|
|
142
|
+
const unit = hitBytes ? "bytes" : "lines";
|
|
143
|
+
const preview = out.join(`
|
|
144
|
+
`);
|
|
145
|
+
let outputPath;
|
|
146
|
+
if (saveToFile) {
|
|
147
|
+
const dir = getTempDir(config);
|
|
148
|
+
await ensureTempDir(dir);
|
|
149
|
+
outputPath = path.join(dir, generateOutputFilename());
|
|
150
|
+
await fs.writeFile(outputPath, text, "utf-8");
|
|
151
|
+
}
|
|
152
|
+
const hint = outputPath ? `Full output saved to: ${outputPath}
|
|
153
|
+
Use Read tool with offset/limit to view specific sections, or Grep to search the content.` : "Output was truncated. Consider using more specific queries.";
|
|
154
|
+
const message = direction === "head" ? `${preview}
|
|
155
|
+
|
|
156
|
+
...${removed} ${unit} truncated...
|
|
157
|
+
|
|
158
|
+
${hint}` : `...${removed} ${unit} truncated...
|
|
159
|
+
|
|
160
|
+
${hint}
|
|
161
|
+
|
|
162
|
+
${preview}`;
|
|
163
|
+
return {
|
|
164
|
+
content: message,
|
|
165
|
+
truncated: true,
|
|
166
|
+
outputPath,
|
|
167
|
+
originalBytes: totalBytes,
|
|
168
|
+
originalLines: lines.length,
|
|
169
|
+
truncatedBytes: bytes,
|
|
170
|
+
truncatedLines: out.length
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
async function truncateToolOutput(output, config) {
|
|
174
|
+
const result = await truncateOutput(output, config);
|
|
175
|
+
return result.content;
|
|
176
|
+
}
|
|
177
|
+
function needsTruncation(text, config = {}) {
|
|
178
|
+
const maxLines = config.maxLines ?? TRUNCATION_DEFAULTS.MAX_LINES;
|
|
179
|
+
const maxBytes = config.maxBytes ?? TRUNCATION_DEFAULTS.MAX_BYTES;
|
|
180
|
+
const lines = text.split(`
|
|
181
|
+
`);
|
|
182
|
+
const bytes = Buffer.byteLength(text, "utf-8");
|
|
183
|
+
return lines.length > maxLines || bytes > maxBytes;
|
|
184
|
+
}
|
|
185
|
+
|
|
79
186
|
// src/hooks/manager.ts
|
|
80
187
|
class HookTimeoutError extends Error {
|
|
81
188
|
constructor(hookName, timeout) {
|
|
@@ -312,7 +419,7 @@ class HooksManager {
|
|
|
312
419
|
// src/skills/loader.ts
|
|
313
420
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
314
421
|
import { readFile } from "node:fs/promises";
|
|
315
|
-
import { join, dirname, basename } from "node:path";
|
|
422
|
+
import { join as join2, dirname, basename } from "node:path";
|
|
316
423
|
import { homedir } from "node:os";
|
|
317
424
|
|
|
318
425
|
// src/utils/frontmatter.ts
|
|
@@ -427,13 +534,13 @@ class SkillLoader {
|
|
|
427
534
|
} = options;
|
|
428
535
|
const allDirs = [...directories];
|
|
429
536
|
if (includeUserSkills) {
|
|
430
|
-
const userSkillsDir =
|
|
537
|
+
const userSkillsDir = join2(homedir(), USER_SKILLS_DIR);
|
|
431
538
|
if (existsSync(userSkillsDir)) {
|
|
432
539
|
allDirs.push(userSkillsDir);
|
|
433
540
|
}
|
|
434
541
|
}
|
|
435
542
|
if (includeProjectSkills && this.projectRoot) {
|
|
436
|
-
const projectSkillsDir =
|
|
543
|
+
const projectSkillsDir = join2(this.projectRoot, PROJECT_SKILLS_DIR);
|
|
437
544
|
if (existsSync(projectSkillsDir)) {
|
|
438
545
|
allDirs.push(projectSkillsDir);
|
|
439
546
|
}
|
|
@@ -449,8 +556,8 @@ class SkillLoader {
|
|
|
449
556
|
if (this.skills.has(skillId)) {
|
|
450
557
|
return this.skills.get(skillId);
|
|
451
558
|
}
|
|
452
|
-
for (const
|
|
453
|
-
const skill = await this.parseSkillFile(
|
|
559
|
+
for (const path2 of this.discoveredPaths) {
|
|
560
|
+
const skill = await this.parseSkillFile(path2);
|
|
454
561
|
if (skill && skill.id === skillId) {
|
|
455
562
|
this.skills.set(skill.id, skill);
|
|
456
563
|
return skill;
|
|
@@ -550,10 +657,10 @@ class SkillLoader {
|
|
|
550
657
|
try {
|
|
551
658
|
const entries = readdirSync(dir);
|
|
552
659
|
for (const entry of entries) {
|
|
553
|
-
const fullPath =
|
|
660
|
+
const fullPath = join2(dir, entry);
|
|
554
661
|
const stat = statSync(fullPath);
|
|
555
662
|
if (stat.isDirectory()) {
|
|
556
|
-
const skillFile =
|
|
663
|
+
const skillFile = join2(fullPath, SKILL_FILE_NAME);
|
|
557
664
|
if (existsSync(skillFile)) {
|
|
558
665
|
if (!this.discoveredPaths.has(skillFile)) {
|
|
559
666
|
const skill = await this.parseSkillFile(skillFile);
|
|
@@ -633,9 +740,9 @@ var defaultSkillLoader = new SkillLoader;
|
|
|
633
740
|
|
|
634
741
|
// src/tools/skill.ts
|
|
635
742
|
import { existsSync as existsSync2 } from "node:fs";
|
|
636
|
-
import { join as
|
|
743
|
+
import { join as join3 } from "node:path";
|
|
637
744
|
import { homedir as homedir2 } from "node:os";
|
|
638
|
-
var DEFAULT_USER_SKILLS_PATH =
|
|
745
|
+
var DEFAULT_USER_SKILLS_PATH = join3(homedir2(), ".claude/skills");
|
|
639
746
|
var skillToolSchema = {
|
|
640
747
|
type: "object",
|
|
641
748
|
properties: {
|
|
@@ -662,7 +769,7 @@ function createSkillTool(config = {}) {
|
|
|
662
769
|
const paths = [];
|
|
663
770
|
if (config.settingSources && config.settingSources.length > 0) {
|
|
664
771
|
for (const source of config.settingSources) {
|
|
665
|
-
const resolvedPath = source.startsWith("~") ?
|
|
772
|
+
const resolvedPath = source.startsWith("~") ? join3(homedir2(), source.slice(1)) : source;
|
|
666
773
|
if (existsSync2(resolvedPath)) {
|
|
667
774
|
paths.push(resolvedPath);
|
|
668
775
|
}
|
|
@@ -1113,7 +1220,7 @@ var defaultSystemPromptBuilder = new SystemPromptBuilderImpl;
|
|
|
1113
1220
|
// src/prompt/claude-md.ts
|
|
1114
1221
|
import { existsSync as existsSync3 } from "node:fs";
|
|
1115
1222
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
1116
|
-
import { join as
|
|
1223
|
+
import { join as join4, dirname as dirname2 } from "node:path";
|
|
1117
1224
|
import { homedir as homedir3 } from "node:os";
|
|
1118
1225
|
var CLAUDE_MD_FILENAME = "CLAUDE.md";
|
|
1119
1226
|
var USER_CLAUDE_DIR = ".claude";
|
|
@@ -1127,7 +1234,7 @@ class ClaudeMdLoaderImpl {
|
|
|
1127
1234
|
return this.loadFile(filePath, "project");
|
|
1128
1235
|
}
|
|
1129
1236
|
async loadUserClaudeMd() {
|
|
1130
|
-
const filePath =
|
|
1237
|
+
const filePath = join4(homedir3(), USER_CLAUDE_DIR, CLAUDE_MD_FILENAME);
|
|
1131
1238
|
if (!existsSync3(filePath)) {
|
|
1132
1239
|
return;
|
|
1133
1240
|
}
|
|
@@ -1148,9 +1255,9 @@ class ClaudeMdLoaderImpl {
|
|
|
1148
1255
|
}
|
|
1149
1256
|
}
|
|
1150
1257
|
if (config.additionalPaths) {
|
|
1151
|
-
for (const
|
|
1152
|
-
if (existsSync3(
|
|
1153
|
-
const content = await this.loadFile(
|
|
1258
|
+
for (const path2 of config.additionalPaths) {
|
|
1259
|
+
if (existsSync3(path2)) {
|
|
1260
|
+
const content = await this.loadFile(path2, "project");
|
|
1154
1261
|
if (content) {
|
|
1155
1262
|
contents.push(content);
|
|
1156
1263
|
}
|
|
@@ -1177,11 +1284,11 @@ class ClaudeMdLoaderImpl {
|
|
|
1177
1284
|
async findProjectClaudeMd(startDir) {
|
|
1178
1285
|
let currentDir = startDir;
|
|
1179
1286
|
while (currentDir !== "/") {
|
|
1180
|
-
const claudeMdPath =
|
|
1287
|
+
const claudeMdPath = join4(currentDir, CLAUDE_MD_FILENAME);
|
|
1181
1288
|
if (existsSync3(claudeMdPath)) {
|
|
1182
1289
|
return claudeMdPath;
|
|
1183
1290
|
}
|
|
1184
|
-
const gitPath =
|
|
1291
|
+
const gitPath = join4(currentDir, ".git");
|
|
1185
1292
|
if (existsSync3(gitPath)) {
|
|
1186
1293
|
break;
|
|
1187
1294
|
}
|
|
@@ -1247,6 +1354,7 @@ class SessionImpl {
|
|
|
1247
1354
|
_state;
|
|
1248
1355
|
provider;
|
|
1249
1356
|
tools;
|
|
1357
|
+
toolNameLookup = new Map;
|
|
1250
1358
|
emitter;
|
|
1251
1359
|
pendingMessage = null;
|
|
1252
1360
|
isReceiving = false;
|
|
@@ -1254,6 +1362,7 @@ class SessionImpl {
|
|
|
1254
1362
|
closed = false;
|
|
1255
1363
|
hooksManager = null;
|
|
1256
1364
|
maxTurns;
|
|
1365
|
+
enableToolRepair = true;
|
|
1257
1366
|
constructor(id, config, provider, state) {
|
|
1258
1367
|
this.id = id;
|
|
1259
1368
|
this.config = config;
|
|
@@ -1276,6 +1385,7 @@ class SessionImpl {
|
|
|
1276
1385
|
if (config.tools) {
|
|
1277
1386
|
for (const tool of config.tools) {
|
|
1278
1387
|
this.tools.set(tool.name, tool);
|
|
1388
|
+
this.toolNameLookup.set(tool.name.toLowerCase(), tool.name);
|
|
1279
1389
|
}
|
|
1280
1390
|
}
|
|
1281
1391
|
if (config.settingSources && config.settingSources.length > 0) {
|
|
@@ -1284,6 +1394,7 @@ class SessionImpl {
|
|
|
1284
1394
|
cwd: config.cwd
|
|
1285
1395
|
});
|
|
1286
1396
|
this.tools.set(skillTool2.name, skillTool2);
|
|
1397
|
+
this.toolNameLookup.set(skillTool2.name.toLowerCase(), skillTool2.name);
|
|
1287
1398
|
}
|
|
1288
1399
|
this.applyAllowedToolsFilter();
|
|
1289
1400
|
if (config.hooks) {
|
|
@@ -1592,12 +1703,23 @@ class SessionImpl {
|
|
|
1592
1703
|
}
|
|
1593
1704
|
systemMessage = preResult.systemMessage;
|
|
1594
1705
|
}
|
|
1595
|
-
|
|
1706
|
+
let tool = this.tools.get(block.name);
|
|
1707
|
+
let effectiveToolName = block.name;
|
|
1708
|
+
if (!tool && this.enableToolRepair) {
|
|
1709
|
+
const lowerName = block.name.toLowerCase();
|
|
1710
|
+
const originalName = this.toolNameLookup.get(lowerName);
|
|
1711
|
+
if (originalName) {
|
|
1712
|
+
tool = this.tools.get(originalName);
|
|
1713
|
+
effectiveToolName = originalName;
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1596
1716
|
if (!tool) {
|
|
1717
|
+
const availableTools = Array.from(this.tools.keys()).slice(0, 10);
|
|
1718
|
+
const suffix = this.tools.size > 10 ? ` (and ${this.tools.size - 10} more)` : "";
|
|
1597
1719
|
return {
|
|
1598
1720
|
type: "tool_result",
|
|
1599
1721
|
tool_use_id: block.id,
|
|
1600
|
-
content: `Error: Tool "${block.name}" not found`,
|
|
1722
|
+
content: `Error: Tool "${block.name}" not found. Available tools: ${availableTools.join(", ")}${suffix}`,
|
|
1601
1723
|
is_error: true,
|
|
1602
1724
|
_hookSystemMessage: systemMessage
|
|
1603
1725
|
};
|
|
@@ -1610,7 +1732,10 @@ class SessionImpl {
|
|
|
1610
1732
|
let toolResponse;
|
|
1611
1733
|
try {
|
|
1612
1734
|
const toolResult = await tool.execute(toolInput, context);
|
|
1613
|
-
|
|
1735
|
+
let content = typeof toolResult.content === "string" ? toolResult.content : JSON.stringify(toolResult.content);
|
|
1736
|
+
if (needsTruncation(content)) {
|
|
1737
|
+
content = await truncateToolOutput(content);
|
|
1738
|
+
}
|
|
1614
1739
|
toolResponse = toolResult;
|
|
1615
1740
|
result = {
|
|
1616
1741
|
type: "tool_result",
|
|
@@ -2828,24 +2953,36 @@ var defaultMCPServerManager = new MCPServerManager;
|
|
|
2828
2953
|
// src/tools/manager.ts
|
|
2829
2954
|
class ToolManager {
|
|
2830
2955
|
tools = new Map;
|
|
2956
|
+
toolNameLookup = new Map;
|
|
2831
2957
|
mcpServers = new Map;
|
|
2832
2958
|
allowedTools;
|
|
2833
2959
|
onToolEvent;
|
|
2960
|
+
enableToolRepair;
|
|
2834
2961
|
constructor(options = {}) {
|
|
2835
2962
|
this.allowedTools = options.allowedTools;
|
|
2836
2963
|
this.onToolEvent = options.onToolEvent;
|
|
2964
|
+
this.enableToolRepair = options.enableToolRepair ?? true;
|
|
2837
2965
|
}
|
|
2838
2966
|
register(tool) {
|
|
2839
2967
|
this.tools.set(tool.name, tool);
|
|
2968
|
+
this.toolNameLookup.set(tool.name.toLowerCase(), tool.name);
|
|
2840
2969
|
}
|
|
2841
2970
|
unregister(name) {
|
|
2842
2971
|
this.tools.delete(name);
|
|
2972
|
+
this.toolNameLookup.delete(name.toLowerCase());
|
|
2843
2973
|
}
|
|
2844
2974
|
get(name) {
|
|
2845
2975
|
const tool = this.tools.get(name);
|
|
2846
2976
|
if (tool) {
|
|
2847
2977
|
return tool;
|
|
2848
2978
|
}
|
|
2979
|
+
if (this.enableToolRepair) {
|
|
2980
|
+
const lowerName = name.toLowerCase();
|
|
2981
|
+
const originalName = this.toolNameLookup.get(lowerName);
|
|
2982
|
+
if (originalName) {
|
|
2983
|
+
return this.tools.get(originalName);
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2849
2986
|
if (isMCPTool(name)) {
|
|
2850
2987
|
const parsed = parseMCPToolName(name);
|
|
2851
2988
|
if (parsed) {
|
|
@@ -2857,6 +2994,34 @@ class ToolManager {
|
|
|
2857
2994
|
}
|
|
2858
2995
|
return;
|
|
2859
2996
|
}
|
|
2997
|
+
repairToolName(name) {
|
|
2998
|
+
if (this.tools.has(name)) {
|
|
2999
|
+
return { repaired: false, originalName: name };
|
|
3000
|
+
}
|
|
3001
|
+
const lowerName = name.toLowerCase();
|
|
3002
|
+
const originalName = this.toolNameLookup.get(lowerName);
|
|
3003
|
+
if (originalName && originalName !== name) {
|
|
3004
|
+
return {
|
|
3005
|
+
repaired: true,
|
|
3006
|
+
originalName: name,
|
|
3007
|
+
repairedName: originalName
|
|
3008
|
+
};
|
|
3009
|
+
}
|
|
3010
|
+
if (isMCPTool(name)) {
|
|
3011
|
+
const parsed = parseMCPToolName(name);
|
|
3012
|
+
if (parsed) {
|
|
3013
|
+
const wrapper = this.mcpServers.get(parsed.serverName);
|
|
3014
|
+
if (wrapper) {
|
|
3015
|
+
return { repaired: false, originalName: name };
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
return {
|
|
3020
|
+
repaired: false,
|
|
3021
|
+
originalName: name,
|
|
3022
|
+
error: `Tool "${name}" not found. Available tools: ${Array.from(this.tools.keys()).join(", ")}`
|
|
3023
|
+
};
|
|
3024
|
+
}
|
|
2860
3025
|
getAll() {
|
|
2861
3026
|
const allTools = [];
|
|
2862
3027
|
for (const tool of this.tools.values()) {
|
|
@@ -2874,6 +3039,7 @@ class ToolManager {
|
|
|
2874
3039
|
}
|
|
2875
3040
|
clear() {
|
|
2876
3041
|
this.tools.clear();
|
|
3042
|
+
this.toolNameLookup.clear();
|
|
2877
3043
|
}
|
|
2878
3044
|
async registerMCPServer(server) {
|
|
2879
3045
|
const wrapper = new MCPServerWrapper(server);
|
|
@@ -2881,6 +3047,7 @@ class ToolManager {
|
|
|
2881
3047
|
const tools = await wrapper.getTools();
|
|
2882
3048
|
for (const tool of tools) {
|
|
2883
3049
|
this.tools.set(tool.name, tool);
|
|
3050
|
+
this.toolNameLookup.set(tool.name.toLowerCase(), tool.name);
|
|
2884
3051
|
}
|
|
2885
3052
|
return tools.length;
|
|
2886
3053
|
}
|
|
@@ -2892,6 +3059,7 @@ class ToolManager {
|
|
|
2892
3059
|
const parsed = parseMCPToolName(name);
|
|
2893
3060
|
if (parsed?.serverName === serverName) {
|
|
2894
3061
|
this.tools.delete(name);
|
|
3062
|
+
this.toolNameLookup.delete(name.toLowerCase());
|
|
2895
3063
|
}
|
|
2896
3064
|
}
|
|
2897
3065
|
}
|
|
@@ -2903,9 +3071,23 @@ class ToolManager {
|
|
|
2903
3071
|
return Array.from(this.mcpServers.keys());
|
|
2904
3072
|
}
|
|
2905
3073
|
async execute(name, input, context) {
|
|
2906
|
-
|
|
3074
|
+
let effectiveName = name;
|
|
3075
|
+
let tool = this.tools.get(name);
|
|
3076
|
+
if (!tool && this.enableToolRepair) {
|
|
3077
|
+
const repairResult = this.repairToolName(name);
|
|
3078
|
+
if (repairResult.repaired && repairResult.repairedName) {
|
|
3079
|
+
effectiveName = repairResult.repairedName;
|
|
3080
|
+
tool = this.tools.get(effectiveName);
|
|
3081
|
+
console.debug(`[ToolManager] Repaired tool name: "${name}" -> "${effectiveName}"`);
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
2907
3084
|
if (!tool) {
|
|
2908
|
-
|
|
3085
|
+
tool = this.get(name);
|
|
3086
|
+
}
|
|
3087
|
+
if (!tool) {
|
|
3088
|
+
const availableTools = Array.from(this.tools.keys()).slice(0, 10);
|
|
3089
|
+
const suffix = this.tools.size > 10 ? ` (and ${this.tools.size - 10} more)` : "";
|
|
3090
|
+
throw new Error(`Tool not found: "${name}". Available tools: ${availableTools.join(", ")}${suffix}`);
|
|
2909
3091
|
}
|
|
2910
3092
|
if (!this.isToolAllowed(tool)) {
|
|
2911
3093
|
throw new Error(`Tool not allowed: ${name}`);
|
|
@@ -2914,7 +3096,7 @@ class ToolManager {
|
|
|
2914
3096
|
this.emitEvent({
|
|
2915
3097
|
type: "tool_start",
|
|
2916
3098
|
toolId,
|
|
2917
|
-
toolName:
|
|
3099
|
+
toolName: effectiveName,
|
|
2918
3100
|
input
|
|
2919
3101
|
});
|
|
2920
3102
|
try {
|
|
@@ -3305,7 +3487,7 @@ Type: ${ext}`
|
|
|
3305
3487
|
}
|
|
3306
3488
|
var ReadTool = createReadTool();
|
|
3307
3489
|
// src/tools/builtin/write.ts
|
|
3308
|
-
import { writeFile, mkdir } from "node:fs/promises";
|
|
3490
|
+
import { writeFile as writeFile2, mkdir as mkdir2 } from "node:fs/promises";
|
|
3309
3491
|
import { existsSync as existsSync5 } from "node:fs";
|
|
3310
3492
|
import { dirname as dirname3 } from "node:path";
|
|
3311
3493
|
function createWriteTool(options = {}) {
|
|
@@ -3336,9 +3518,9 @@ function createWriteTool(options = {}) {
|
|
|
3336
3518
|
try {
|
|
3337
3519
|
const dir = dirname3(access.resolved);
|
|
3338
3520
|
if (!existsSync5(dir)) {
|
|
3339
|
-
await
|
|
3521
|
+
await mkdir2(dir, { recursive: true });
|
|
3340
3522
|
}
|
|
3341
|
-
await
|
|
3523
|
+
await writeFile2(access.resolved, content, "utf-8");
|
|
3342
3524
|
const lines = content.split(`
|
|
3343
3525
|
`).length;
|
|
3344
3526
|
const bytes = Buffer.byteLength(content, "utf-8");
|
|
@@ -3356,7 +3538,7 @@ function createWriteTool(options = {}) {
|
|
|
3356
3538
|
}
|
|
3357
3539
|
var WriteTool = createWriteTool();
|
|
3358
3540
|
// src/tools/builtin/edit.ts
|
|
3359
|
-
import { readFile as readFile4, writeFile as
|
|
3541
|
+
import { readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
|
|
3360
3542
|
import { existsSync as existsSync6 } from "node:fs";
|
|
3361
3543
|
function createEditTool(options = {}) {
|
|
3362
3544
|
return {
|
|
@@ -3428,7 +3610,7 @@ function createEditTool(options = {}) {
|
|
|
3428
3610
|
newContent = content.replace(old_string, new_string);
|
|
3429
3611
|
replacedCount = 1;
|
|
3430
3612
|
}
|
|
3431
|
-
await
|
|
3613
|
+
await writeFile3(access.resolved, newContent, "utf-8");
|
|
3432
3614
|
return {
|
|
3433
3615
|
content: `Successfully replaced ${replacedCount} occurrence${replacedCount > 1 ? "s" : ""} in ${access.resolved}`
|
|
3434
3616
|
};
|
|
@@ -3443,14 +3625,14 @@ function createEditTool(options = {}) {
|
|
|
3443
3625
|
}
|
|
3444
3626
|
var EditTool = createEditTool();
|
|
3445
3627
|
// src/tools/builtin/glob.ts
|
|
3446
|
-
import { readdir, stat as stat2 } from "node:fs/promises";
|
|
3628
|
+
import { readdir as readdir2, stat as stat2 } from "node:fs/promises";
|
|
3447
3629
|
import { existsSync as existsSync7 } from "node:fs";
|
|
3448
|
-
import { join as
|
|
3630
|
+
import { join as join5, relative } from "node:path";
|
|
3449
3631
|
var MAX_RESULTS = 1000;
|
|
3450
|
-
function matchGlob(pattern,
|
|
3632
|
+
function matchGlob(pattern, path2) {
|
|
3451
3633
|
const regexPattern = pattern.replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/{{GLOBSTAR}}/g, ".*").replace(/\./g, "\\.");
|
|
3452
3634
|
const regex = new RegExp(`^${regexPattern}$`);
|
|
3453
|
-
return regex.test(
|
|
3635
|
+
return regex.test(path2);
|
|
3454
3636
|
}
|
|
3455
3637
|
async function scanDir(dir, pattern, results, baseDir, maxDepth = 10, currentDepth = 0) {
|
|
3456
3638
|
if (currentDepth > maxDepth || results.length >= MAX_RESULTS) {
|
|
@@ -3460,14 +3642,14 @@ async function scanDir(dir, pattern, results, baseDir, maxDepth = 10, currentDep
|
|
|
3460
3642
|
return;
|
|
3461
3643
|
}
|
|
3462
3644
|
try {
|
|
3463
|
-
const entries = await
|
|
3645
|
+
const entries = await readdir2(dir, { withFileTypes: true });
|
|
3464
3646
|
for (const entry of entries) {
|
|
3465
3647
|
if (results.length >= MAX_RESULTS)
|
|
3466
3648
|
break;
|
|
3467
3649
|
if (entry.name.startsWith(".") || entry.name === "node_modules") {
|
|
3468
3650
|
continue;
|
|
3469
3651
|
}
|
|
3470
|
-
const fullPath =
|
|
3652
|
+
const fullPath = join5(dir, entry.name);
|
|
3471
3653
|
const relativePath = relative(baseDir, fullPath);
|
|
3472
3654
|
if (entry.isDirectory()) {
|
|
3473
3655
|
if (matchGlob(pattern, relativePath) || matchGlob(pattern, relativePath + "/")) {
|
|
@@ -3503,8 +3685,8 @@ function createGlobTool(options = {}) {
|
|
|
3503
3685
|
},
|
|
3504
3686
|
execute: async (rawInput, _context) => {
|
|
3505
3687
|
const input = rawInput;
|
|
3506
|
-
const { pattern, path = defaultCwd } = input;
|
|
3507
|
-
const access = checkDirAccess(
|
|
3688
|
+
const { pattern, path: path2 = defaultCwd } = input;
|
|
3689
|
+
const access = checkDirAccess(path2, options);
|
|
3508
3690
|
if (!access.ok) {
|
|
3509
3691
|
return { content: access.error, isError: true };
|
|
3510
3692
|
}
|
|
@@ -3552,9 +3734,9 @@ ${output}${truncated}`
|
|
|
3552
3734
|
}
|
|
3553
3735
|
var GlobTool = createGlobTool();
|
|
3554
3736
|
// src/tools/builtin/grep.ts
|
|
3555
|
-
import { readFile as readFile5, readdir as
|
|
3737
|
+
import { readFile as readFile5, readdir as readdir3, stat as stat3 } from "node:fs/promises";
|
|
3556
3738
|
import { existsSync as existsSync8 } from "node:fs";
|
|
3557
|
-
import { join as
|
|
3739
|
+
import { join as join6, extname as extname2 } from "node:path";
|
|
3558
3740
|
var MAX_RESULTS2 = 500;
|
|
3559
3741
|
var MAX_LINE_LENGTH2 = 500;
|
|
3560
3742
|
var BINARY_EXTENSIONS2 = new Set([
|
|
@@ -3629,14 +3811,14 @@ async function searchDir(dir, regex, glob, before, after, results, maxDepth = 10
|
|
|
3629
3811
|
return;
|
|
3630
3812
|
}
|
|
3631
3813
|
try {
|
|
3632
|
-
const entries = await
|
|
3814
|
+
const entries = await readdir3(dir, { withFileTypes: true });
|
|
3633
3815
|
for (const entry of entries) {
|
|
3634
3816
|
if (results.length >= MAX_RESULTS2)
|
|
3635
3817
|
break;
|
|
3636
3818
|
if (entry.name.startsWith(".") || entry.name === "node_modules") {
|
|
3637
3819
|
continue;
|
|
3638
3820
|
}
|
|
3639
|
-
const fullPath =
|
|
3821
|
+
const fullPath = join6(dir, entry.name);
|
|
3640
3822
|
if (entry.isDirectory()) {
|
|
3641
3823
|
await searchDir(fullPath, regex, glob, before, after, results, maxDepth, currentDepth + 1);
|
|
3642
3824
|
} else if (entry.isFile()) {
|
|
@@ -3692,13 +3874,13 @@ function createGrepTool(options = {}) {
|
|
|
3692
3874
|
const input = rawInput;
|
|
3693
3875
|
const {
|
|
3694
3876
|
pattern,
|
|
3695
|
-
path = defaultCwd,
|
|
3877
|
+
path: path2 = defaultCwd,
|
|
3696
3878
|
glob,
|
|
3697
3879
|
before = 0,
|
|
3698
3880
|
after = 0,
|
|
3699
3881
|
ignoreCase = false
|
|
3700
3882
|
} = input;
|
|
3701
|
-
const access = checkPathAccess(
|
|
3883
|
+
const access = checkPathAccess(path2, options, "dir");
|
|
3702
3884
|
if (!access.ok) {
|
|
3703
3885
|
return { content: access.error, isError: true };
|
|
3704
3886
|
}
|
|
@@ -3995,6 +4177,187 @@ Please fetch the new URL if you want to continue.`
|
|
|
3995
4177
|
};
|
|
3996
4178
|
}
|
|
3997
4179
|
var WebFetchTool = createWebFetchTool();
|
|
4180
|
+
// src/tools/builtin/websearch.ts
|
|
4181
|
+
var API_CONFIG = {
|
|
4182
|
+
BASE_URL: "https://mcp.exa.ai",
|
|
4183
|
+
ENDPOINTS: {
|
|
4184
|
+
SEARCH: "/mcp"
|
|
4185
|
+
},
|
|
4186
|
+
DEFAULT_NUM_RESULTS: 8,
|
|
4187
|
+
DEFAULT_CONTEXT_MAX_CHARS: 1e4,
|
|
4188
|
+
TIMEOUT_MS: 30000
|
|
4189
|
+
};
|
|
4190
|
+
var WEBSEARCH_DESCRIPTION = `Search the web for up-to-date information using Exa AI.
|
|
4191
|
+
|
|
4192
|
+
Use this tool when you need to:
|
|
4193
|
+
- Find current information, news, or recent events
|
|
4194
|
+
- Look up documentation or technical resources
|
|
4195
|
+
- Research topics beyond your knowledge cutoff
|
|
4196
|
+
- Verify facts or get multiple perspectives
|
|
4197
|
+
|
|
4198
|
+
The search returns curated, AI-optimized content with context.
|
|
4199
|
+
|
|
4200
|
+
Parameters:
|
|
4201
|
+
- query: The search query (required)
|
|
4202
|
+
- numResults: Number of results to return (default: 8, max: 20)
|
|
4203
|
+
- livecrawl: 'fallback' (default) or 'preferred' for fresh content
|
|
4204
|
+
- type: 'auto' (default), 'fast', or 'deep'
|
|
4205
|
+
- contextMaxCharacters: Max characters per result (default: 10000)
|
|
4206
|
+
|
|
4207
|
+
Best practices:
|
|
4208
|
+
- Use specific, detailed queries for better results
|
|
4209
|
+
- For technical topics, include relevant terms and context
|
|
4210
|
+
- Use 'deep' type for comprehensive research
|
|
4211
|
+
- Use 'fast' type for quick fact-checking`;
|
|
4212
|
+
function createWebSearchTool(options = {}) {
|
|
4213
|
+
return {
|
|
4214
|
+
name: "WebSearch",
|
|
4215
|
+
description: WEBSEARCH_DESCRIPTION,
|
|
4216
|
+
inputSchema: {
|
|
4217
|
+
type: "object",
|
|
4218
|
+
properties: {
|
|
4219
|
+
query: {
|
|
4220
|
+
type: "string",
|
|
4221
|
+
description: "Search query to find relevant information"
|
|
4222
|
+
},
|
|
4223
|
+
numResults: {
|
|
4224
|
+
type: "number",
|
|
4225
|
+
description: "Number of search results to return (default: 8, max: 20)"
|
|
4226
|
+
},
|
|
4227
|
+
livecrawl: {
|
|
4228
|
+
type: "string",
|
|
4229
|
+
enum: ["fallback", "preferred"],
|
|
4230
|
+
description: "Live crawl mode - 'fallback': use live crawling as backup, 'preferred': prioritize live crawling"
|
|
4231
|
+
},
|
|
4232
|
+
type: {
|
|
4233
|
+
type: "string",
|
|
4234
|
+
enum: ["auto", "fast", "deep"],
|
|
4235
|
+
description: "Search type - 'auto': balanced, 'fast': quick results, 'deep': comprehensive"
|
|
4236
|
+
},
|
|
4237
|
+
contextMaxCharacters: {
|
|
4238
|
+
type: "number",
|
|
4239
|
+
description: "Maximum characters for context per result (default: 10000)"
|
|
4240
|
+
}
|
|
4241
|
+
},
|
|
4242
|
+
required: ["query"]
|
|
4243
|
+
},
|
|
4244
|
+
execute: async (rawInput, context) => {
|
|
4245
|
+
const input = rawInput;
|
|
4246
|
+
const { query, numResults, livecrawl, type, contextMaxCharacters } = input;
|
|
4247
|
+
if (!query || typeof query !== "string" || query.trim().length === 0) {
|
|
4248
|
+
return {
|
|
4249
|
+
content: "Error: A non-empty search query is required.",
|
|
4250
|
+
isError: true
|
|
4251
|
+
};
|
|
4252
|
+
}
|
|
4253
|
+
const clampedNumResults = Math.min(Math.max(numResults || API_CONFIG.DEFAULT_NUM_RESULTS, 1), 20);
|
|
4254
|
+
const searchRequest = {
|
|
4255
|
+
jsonrpc: "2.0",
|
|
4256
|
+
id: 1,
|
|
4257
|
+
method: "tools/call",
|
|
4258
|
+
params: {
|
|
4259
|
+
name: "web_search_exa",
|
|
4260
|
+
arguments: {
|
|
4261
|
+
query: query.trim(),
|
|
4262
|
+
numResults: clampedNumResults,
|
|
4263
|
+
livecrawl: livecrawl || "fallback",
|
|
4264
|
+
type: type || "auto",
|
|
4265
|
+
contextMaxCharacters: contextMaxCharacters || API_CONFIG.DEFAULT_CONTEXT_MAX_CHARS
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4268
|
+
};
|
|
4269
|
+
const controller = new AbortController;
|
|
4270
|
+
const timeoutId = setTimeout(() => controller.abort(), API_CONFIG.TIMEOUT_MS);
|
|
4271
|
+
try {
|
|
4272
|
+
const signal = context.abortSignal ? AbortSignal.any([controller.signal, context.abortSignal]) : controller.signal;
|
|
4273
|
+
const response = await fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.SEARCH}`, {
|
|
4274
|
+
method: "POST",
|
|
4275
|
+
headers: {
|
|
4276
|
+
Accept: "application/json, text/event-stream",
|
|
4277
|
+
"Content-Type": "application/json",
|
|
4278
|
+
"User-Agent": "FormAgent-SDK/1.0"
|
|
4279
|
+
},
|
|
4280
|
+
body: JSON.stringify(searchRequest),
|
|
4281
|
+
signal
|
|
4282
|
+
});
|
|
4283
|
+
clearTimeout(timeoutId);
|
|
4284
|
+
if (!response.ok) {
|
|
4285
|
+
const errorText = await response.text();
|
|
4286
|
+
return {
|
|
4287
|
+
content: `Search error (${response.status}): ${errorText}`,
|
|
4288
|
+
isError: true
|
|
4289
|
+
};
|
|
4290
|
+
}
|
|
4291
|
+
const responseText = await response.text();
|
|
4292
|
+
const lines = responseText.split(`
|
|
4293
|
+
`);
|
|
4294
|
+
for (const line of lines) {
|
|
4295
|
+
if (line.startsWith("data: ")) {
|
|
4296
|
+
try {
|
|
4297
|
+
const data = JSON.parse(line.substring(6));
|
|
4298
|
+
if (data.error) {
|
|
4299
|
+
return {
|
|
4300
|
+
content: `Search API error: ${data.error.message}`,
|
|
4301
|
+
isError: true
|
|
4302
|
+
};
|
|
4303
|
+
}
|
|
4304
|
+
if (data.result?.content && data.result.content.length > 0) {
|
|
4305
|
+
const resultText = data.result.content[0].text;
|
|
4306
|
+
let output = `## Web Search Results
|
|
4307
|
+
|
|
4308
|
+
`;
|
|
4309
|
+
output += `**Query:** ${query}
|
|
4310
|
+
`;
|
|
4311
|
+
output += `**Results:** ${clampedNumResults} requested
|
|
4312
|
+
|
|
4313
|
+
`;
|
|
4314
|
+
output += `---
|
|
4315
|
+
|
|
4316
|
+
`;
|
|
4317
|
+
output += resultText;
|
|
4318
|
+
return {
|
|
4319
|
+
content: output
|
|
4320
|
+
};
|
|
4321
|
+
}
|
|
4322
|
+
} catch (parseError) {
|
|
4323
|
+
continue;
|
|
4324
|
+
}
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4327
|
+
try {
|
|
4328
|
+
const data = JSON.parse(responseText);
|
|
4329
|
+
if (data.result?.content && data.result.content.length > 0) {
|
|
4330
|
+
return {
|
|
4331
|
+
content: data.result.content[0].text
|
|
4332
|
+
};
|
|
4333
|
+
}
|
|
4334
|
+
} catch {}
|
|
4335
|
+
return {
|
|
4336
|
+
content: `No search results found for query: "${query}". Please try a different or more specific query.`
|
|
4337
|
+
};
|
|
4338
|
+
} catch (error) {
|
|
4339
|
+
clearTimeout(timeoutId);
|
|
4340
|
+
if (error instanceof Error) {
|
|
4341
|
+
if (error.name === "AbortError") {
|
|
4342
|
+
return {
|
|
4343
|
+
content: `Search request timed out after ${API_CONFIG.TIMEOUT_MS}ms. Please try again or use a simpler query.`,
|
|
4344
|
+
isError: true
|
|
4345
|
+
};
|
|
4346
|
+
}
|
|
4347
|
+
return {
|
|
4348
|
+
content: `Search failed: ${error.message}`,
|
|
4349
|
+
isError: true
|
|
4350
|
+
};
|
|
4351
|
+
}
|
|
4352
|
+
return {
|
|
4353
|
+
content: `Search failed: ${String(error)}`,
|
|
4354
|
+
isError: true
|
|
4355
|
+
};
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4358
|
+
};
|
|
4359
|
+
}
|
|
4360
|
+
var WebSearchTool = createWebSearchTool();
|
|
3998
4361
|
// src/tools/builtin/todo.ts
|
|
3999
4362
|
var globalTodos = [];
|
|
4000
4363
|
var onTodoChange = null;
|
|
@@ -4083,6 +4446,574 @@ function createTodoWriteTool(options = {}) {
|
|
|
4083
4446
|
};
|
|
4084
4447
|
}
|
|
4085
4448
|
var TodoWriteTool = createTodoWriteTool();
|
|
4449
|
+
// src/tools/builtin/askuser.ts
|
|
4450
|
+
var globalAskUserHandler = null;
|
|
4451
|
+
var ASKUSER_DESCRIPTION = `Ask the user questions to gather information, clarify requirements, or get decisions.
|
|
4452
|
+
|
|
4453
|
+
Use this tool when you need to:
|
|
4454
|
+
- Gather user preferences or requirements
|
|
4455
|
+
- Clarify ambiguous instructions
|
|
4456
|
+
- Get decisions on implementation choices
|
|
4457
|
+
- Offer choices about what direction to take
|
|
4458
|
+
|
|
4459
|
+
Parameters:
|
|
4460
|
+
- questions: Array of questions (1-4 questions per call)
|
|
4461
|
+
- question: The question text (required)
|
|
4462
|
+
- header: Short label for display (optional)
|
|
4463
|
+
- options: Array of choices (optional)
|
|
4464
|
+
- label: Display text
|
|
4465
|
+
- description: Explanation of the option
|
|
4466
|
+
- multiSelect: Allow multiple selections (default: false)
|
|
4467
|
+
- defaultValue: Default if no response
|
|
4468
|
+
|
|
4469
|
+
Best practices:
|
|
4470
|
+
- Keep questions clear and concise
|
|
4471
|
+
- Provide options when there are known choices
|
|
4472
|
+
- Use multiSelect for non-mutually-exclusive options
|
|
4473
|
+
- Limit to 4 questions per call to avoid overwhelming the user`;
|
|
4474
|
+
function createAskUserTool(options = {}) {
|
|
4475
|
+
return {
|
|
4476
|
+
name: "AskUser",
|
|
4477
|
+
description: ASKUSER_DESCRIPTION,
|
|
4478
|
+
inputSchema: {
|
|
4479
|
+
type: "object",
|
|
4480
|
+
properties: {
|
|
4481
|
+
questions: {
|
|
4482
|
+
type: "array",
|
|
4483
|
+
description: "Questions to ask the user (1-4 questions)",
|
|
4484
|
+
items: {
|
|
4485
|
+
type: "object",
|
|
4486
|
+
properties: {
|
|
4487
|
+
question: {
|
|
4488
|
+
type: "string",
|
|
4489
|
+
description: "The question text to display"
|
|
4490
|
+
},
|
|
4491
|
+
header: {
|
|
4492
|
+
type: "string",
|
|
4493
|
+
description: "Short label for the question (max 12 chars)"
|
|
4494
|
+
},
|
|
4495
|
+
options: {
|
|
4496
|
+
type: "array",
|
|
4497
|
+
description: "Available choices (2-4 options)",
|
|
4498
|
+
items: {
|
|
4499
|
+
type: "object",
|
|
4500
|
+
properties: {
|
|
4501
|
+
label: {
|
|
4502
|
+
type: "string",
|
|
4503
|
+
description: "Display text for this option"
|
|
4504
|
+
},
|
|
4505
|
+
description: {
|
|
4506
|
+
type: "string",
|
|
4507
|
+
description: "Explanation of what this option means"
|
|
4508
|
+
}
|
|
4509
|
+
},
|
|
4510
|
+
required: ["label"]
|
|
4511
|
+
}
|
|
4512
|
+
},
|
|
4513
|
+
multiSelect: {
|
|
4514
|
+
type: "boolean",
|
|
4515
|
+
description: "Allow multiple selections (default: false)"
|
|
4516
|
+
},
|
|
4517
|
+
defaultValue: {
|
|
4518
|
+
type: "string",
|
|
4519
|
+
description: "Default value if user doesn't respond"
|
|
4520
|
+
}
|
|
4521
|
+
},
|
|
4522
|
+
required: ["question"]
|
|
4523
|
+
},
|
|
4524
|
+
minItems: 1,
|
|
4525
|
+
maxItems: 4
|
|
4526
|
+
}
|
|
4527
|
+
},
|
|
4528
|
+
required: ["questions"]
|
|
4529
|
+
},
|
|
4530
|
+
execute: async (rawInput, context) => {
|
|
4531
|
+
const input = rawInput;
|
|
4532
|
+
const { questions } = input;
|
|
4533
|
+
if (!questions || !Array.isArray(questions) || questions.length === 0) {
|
|
4534
|
+
return {
|
|
4535
|
+
content: "Error: At least one question is required.",
|
|
4536
|
+
isError: true
|
|
4537
|
+
};
|
|
4538
|
+
}
|
|
4539
|
+
if (questions.length > 4) {
|
|
4540
|
+
return {
|
|
4541
|
+
content: "Error: Maximum 4 questions per call. Please split into multiple calls.",
|
|
4542
|
+
isError: true
|
|
4543
|
+
};
|
|
4544
|
+
}
|
|
4545
|
+
if (!globalAskUserHandler) {
|
|
4546
|
+
return {
|
|
4547
|
+
content: `Error: AskUser handler not configured. The SDK user must call setAskUserHandler() to enable user interaction.
|
|
4548
|
+
|
|
4549
|
+
Questions that would have been asked:
|
|
4550
|
+
${questions.map((q, i) => `${i + 1}. ${q.question}${q.options ? ` [Options: ${q.options.map((o) => o.label).join(", ")}]` : ""}`).join(`
|
|
4551
|
+
`)}
|
|
4552
|
+
|
|
4553
|
+
To enable this tool, the SDK user should implement an AskUser handler.`,
|
|
4554
|
+
isError: true
|
|
4555
|
+
};
|
|
4556
|
+
}
|
|
4557
|
+
try {
|
|
4558
|
+
const answers = await globalAskUserHandler(questions, context);
|
|
4559
|
+
const formatAnswer = (answer) => {
|
|
4560
|
+
if (answer === undefined || answer === null)
|
|
4561
|
+
return "(no answer)";
|
|
4562
|
+
if (Array.isArray(answer))
|
|
4563
|
+
return answer.join(", ") || "(no answer)";
|
|
4564
|
+
return answer || "(no answer)";
|
|
4565
|
+
};
|
|
4566
|
+
const formattedAnswers = questions.map((q, i) => `Q: "${q.question}"
|
|
4567
|
+
A: ${formatAnswer(answers[i])}`).join(`
|
|
4568
|
+
|
|
4569
|
+
`);
|
|
4570
|
+
return {
|
|
4571
|
+
content: `User has answered your questions:
|
|
4572
|
+
|
|
4573
|
+
${formattedAnswers}
|
|
4574
|
+
|
|
4575
|
+
You can now continue with the user's answers in mind.`,
|
|
4576
|
+
metadata: {
|
|
4577
|
+
questions: questions.map((q) => q.question),
|
|
4578
|
+
answers
|
|
4579
|
+
}
|
|
4580
|
+
};
|
|
4581
|
+
} catch (error) {
|
|
4582
|
+
return {
|
|
4583
|
+
content: `Error getting user response: ${error instanceof Error ? error.message : String(error)}`,
|
|
4584
|
+
isError: true
|
|
4585
|
+
};
|
|
4586
|
+
}
|
|
4587
|
+
}
|
|
4588
|
+
};
|
|
4589
|
+
}
|
|
4590
|
+
var AskUserTool = createAskUserTool();
|
|
4591
|
+
// src/tools/builtin/batch.ts
|
|
4592
|
+
var globalToolResolver = null;
|
|
4593
|
+
var DISALLOWED_TOOLS = new Set(["Batch", "batch", "AskUser", "askuser"]);
|
|
4594
|
+
var BATCH_DESCRIPTION = `Execute multiple tool calls in parallel for improved performance.
|
|
4595
|
+
|
|
4596
|
+
Use this tool when you need to:
|
|
4597
|
+
- Read multiple files at once
|
|
4598
|
+
- Perform several independent operations
|
|
4599
|
+
- Speed up tasks that don't have dependencies
|
|
4600
|
+
|
|
4601
|
+
Parameters:
|
|
4602
|
+
- tool_calls: Array of tool calls (1-10 calls per batch)
|
|
4603
|
+
- tool: Name of the tool to execute
|
|
4604
|
+
- parameters: Parameters for that tool
|
|
4605
|
+
|
|
4606
|
+
Limitations:
|
|
4607
|
+
- Maximum 10 tool calls per batch
|
|
4608
|
+
- Cannot nest Batch calls (no batch within batch)
|
|
4609
|
+
- Cannot batch AskUser (requires sequential interaction)
|
|
4610
|
+
- All calls execute in parallel - don't batch dependent operations
|
|
4611
|
+
|
|
4612
|
+
Best practices:
|
|
4613
|
+
- Group independent operations (e.g., reading multiple unrelated files)
|
|
4614
|
+
- Don't batch operations that depend on each other's results
|
|
4615
|
+
- Use for bulk file reads, multiple greps, or parallel web fetches
|
|
4616
|
+
|
|
4617
|
+
Example:
|
|
4618
|
+
{
|
|
4619
|
+
"tool_calls": [
|
|
4620
|
+
{ "tool": "Read", "parameters": { "file_path": "/path/to/file1.ts" } },
|
|
4621
|
+
{ "tool": "Read", "parameters": { "file_path": "/path/to/file2.ts" } },
|
|
4622
|
+
{ "tool": "Grep", "parameters": { "pattern": "TODO", "path": "./src" } }
|
|
4623
|
+
]
|
|
4624
|
+
}`;
|
|
4625
|
+
function createBatchTool(options = {}, toolMap) {
|
|
4626
|
+
return {
|
|
4627
|
+
name: "Batch",
|
|
4628
|
+
description: BATCH_DESCRIPTION,
|
|
4629
|
+
inputSchema: {
|
|
4630
|
+
type: "object",
|
|
4631
|
+
properties: {
|
|
4632
|
+
tool_calls: {
|
|
4633
|
+
type: "array",
|
|
4634
|
+
description: "Array of tool calls to execute in parallel",
|
|
4635
|
+
items: {
|
|
4636
|
+
type: "object",
|
|
4637
|
+
properties: {
|
|
4638
|
+
tool: {
|
|
4639
|
+
type: "string",
|
|
4640
|
+
description: "Name of the tool to execute"
|
|
4641
|
+
},
|
|
4642
|
+
parameters: {
|
|
4643
|
+
type: "object",
|
|
4644
|
+
description: "Parameters for the tool",
|
|
4645
|
+
additionalProperties: true
|
|
4646
|
+
}
|
|
4647
|
+
},
|
|
4648
|
+
required: ["tool", "parameters"]
|
|
4649
|
+
},
|
|
4650
|
+
minItems: 1,
|
|
4651
|
+
maxItems: 10
|
|
4652
|
+
}
|
|
4653
|
+
},
|
|
4654
|
+
required: ["tool_calls"]
|
|
4655
|
+
},
|
|
4656
|
+
execute: async (rawInput, context) => {
|
|
4657
|
+
const input = rawInput;
|
|
4658
|
+
const { tool_calls } = input;
|
|
4659
|
+
if (!tool_calls || !Array.isArray(tool_calls) || tool_calls.length === 0) {
|
|
4660
|
+
return {
|
|
4661
|
+
content: "Error: At least one tool call is required.",
|
|
4662
|
+
isError: true
|
|
4663
|
+
};
|
|
4664
|
+
}
|
|
4665
|
+
const callsToExecute = tool_calls.slice(0, 10);
|
|
4666
|
+
const discardedCalls = tool_calls.slice(10);
|
|
4667
|
+
const executeCall = async (call) => {
|
|
4668
|
+
const startTime = Date.now();
|
|
4669
|
+
try {
|
|
4670
|
+
if (DISALLOWED_TOOLS.has(call.tool)) {
|
|
4671
|
+
return {
|
|
4672
|
+
success: false,
|
|
4673
|
+
tool: call.tool,
|
|
4674
|
+
error: `Tool '${call.tool}' cannot be used in batch. Disallowed: ${Array.from(DISALLOWED_TOOLS).join(", ")}`,
|
|
4675
|
+
duration: Date.now() - startTime
|
|
4676
|
+
};
|
|
4677
|
+
}
|
|
4678
|
+
let toolExecute;
|
|
4679
|
+
if (toolMap) {
|
|
4680
|
+
const tool = toolMap.get(call.tool);
|
|
4681
|
+
if (tool) {
|
|
4682
|
+
toolExecute = tool.execute;
|
|
4683
|
+
}
|
|
4684
|
+
}
|
|
4685
|
+
if (!toolExecute && globalToolResolver) {
|
|
4686
|
+
toolExecute = globalToolResolver(call.tool);
|
|
4687
|
+
}
|
|
4688
|
+
if (!toolExecute) {
|
|
4689
|
+
const availableTools = toolMap ? Array.from(toolMap.keys()).filter((n) => !DISALLOWED_TOOLS.has(n)) : [];
|
|
4690
|
+
return {
|
|
4691
|
+
success: false,
|
|
4692
|
+
tool: call.tool,
|
|
4693
|
+
error: `Tool '${call.tool}' not found.${availableTools.length > 0 ? ` Available: ${availableTools.slice(0, 10).join(", ")}` : ""}`,
|
|
4694
|
+
duration: Date.now() - startTime
|
|
4695
|
+
};
|
|
4696
|
+
}
|
|
4697
|
+
const result = await toolExecute(call.parameters, context);
|
|
4698
|
+
return {
|
|
4699
|
+
success: !result.isError,
|
|
4700
|
+
tool: call.tool,
|
|
4701
|
+
result,
|
|
4702
|
+
duration: Date.now() - startTime
|
|
4703
|
+
};
|
|
4704
|
+
} catch (error) {
|
|
4705
|
+
return {
|
|
4706
|
+
success: false,
|
|
4707
|
+
tool: call.tool,
|
|
4708
|
+
error: error instanceof Error ? error.message : String(error),
|
|
4709
|
+
duration: Date.now() - startTime
|
|
4710
|
+
};
|
|
4711
|
+
}
|
|
4712
|
+
};
|
|
4713
|
+
const results = await Promise.all(callsToExecute.map(executeCall));
|
|
4714
|
+
for (const call of discardedCalls) {
|
|
4715
|
+
results.push({
|
|
4716
|
+
success: false,
|
|
4717
|
+
tool: call.tool,
|
|
4718
|
+
error: "Exceeded maximum of 10 tool calls per batch",
|
|
4719
|
+
duration: 0
|
|
4720
|
+
});
|
|
4721
|
+
}
|
|
4722
|
+
const successCount = results.filter((r) => r.success).length;
|
|
4723
|
+
const failCount = results.length - successCount;
|
|
4724
|
+
const totalDuration = results.reduce((sum, r) => sum + (r.duration || 0), 0);
|
|
4725
|
+
const formatResult = (r, index) => {
|
|
4726
|
+
if (r.success && r.result) {
|
|
4727
|
+
const content = typeof r.result.content === "string" ? r.result.content.slice(0, 500) + (r.result.content.length > 500 ? "..." : "") : JSON.stringify(r.result.content).slice(0, 500);
|
|
4728
|
+
return `[${index + 1}] ${r.tool}: SUCCESS (${r.duration}ms)
|
|
4729
|
+
${content}`;
|
|
4730
|
+
} else {
|
|
4731
|
+
return `[${index + 1}] ${r.tool}: FAILED (${r.duration}ms)
|
|
4732
|
+
Error: ${r.error}`;
|
|
4733
|
+
}
|
|
4734
|
+
};
|
|
4735
|
+
const resultsOutput = results.map(formatResult).join(`
|
|
4736
|
+
|
|
4737
|
+
---
|
|
4738
|
+
|
|
4739
|
+
`);
|
|
4740
|
+
const summary = failCount > 0 ? `Batch execution: ${successCount}/${results.length} succeeded, ${failCount} failed (${totalDuration}ms total)` : `Batch execution: All ${successCount} tools succeeded (${totalDuration}ms total)`;
|
|
4741
|
+
return {
|
|
4742
|
+
content: `${summary}
|
|
4743
|
+
|
|
4744
|
+
${resultsOutput}`,
|
|
4745
|
+
metadata: {
|
|
4746
|
+
totalCalls: results.length,
|
|
4747
|
+
successful: successCount,
|
|
4748
|
+
failed: failCount,
|
|
4749
|
+
totalDuration,
|
|
4750
|
+
details: results.map((r) => ({
|
|
4751
|
+
tool: r.tool,
|
|
4752
|
+
success: r.success,
|
|
4753
|
+
duration: r.duration,
|
|
4754
|
+
error: r.error
|
|
4755
|
+
}))
|
|
4756
|
+
}
|
|
4757
|
+
};
|
|
4758
|
+
}
|
|
4759
|
+
};
|
|
4760
|
+
}
|
|
4761
|
+
var BatchTool = createBatchTool();
|
|
4762
|
+
// src/tools/builtin/httprequest.ts
|
|
4763
|
+
import { lookup as lookup2 } from "node:dns/promises";
|
|
4764
|
+
var DEFAULT_TIMEOUT2 = 30000;
|
|
4765
|
+
var MAX_RESPONSE_SIZE = 5 * 1024 * 1024;
|
|
4766
|
+
function isPrivateIp(ip) {
|
|
4767
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
|
|
4768
|
+
const parts = ip.split(".").map(Number);
|
|
4769
|
+
const [a, b] = parts;
|
|
4770
|
+
if (a === 10)
|
|
4771
|
+
return true;
|
|
4772
|
+
if (a === 127)
|
|
4773
|
+
return true;
|
|
4774
|
+
if (a === 0)
|
|
4775
|
+
return true;
|
|
4776
|
+
if (a === 169 && b === 254)
|
|
4777
|
+
return true;
|
|
4778
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
4779
|
+
return true;
|
|
4780
|
+
if (a === 192 && b === 168)
|
|
4781
|
+
return true;
|
|
4782
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
4783
|
+
return true;
|
|
4784
|
+
if (a >= 224)
|
|
4785
|
+
return true;
|
|
4786
|
+
}
|
|
4787
|
+
const normalized = ip.toLowerCase();
|
|
4788
|
+
if (normalized === "::" || normalized === "::1")
|
|
4789
|
+
return true;
|
|
4790
|
+
if (normalized.startsWith("fe80:"))
|
|
4791
|
+
return true;
|
|
4792
|
+
if (normalized.startsWith("fc") || normalized.startsWith("fd"))
|
|
4793
|
+
return true;
|
|
4794
|
+
return false;
|
|
4795
|
+
}
|
|
4796
|
+
async function validateUrl(url, options) {
|
|
4797
|
+
let parsedUrl;
|
|
4798
|
+
try {
|
|
4799
|
+
parsedUrl = new URL(url);
|
|
4800
|
+
} catch {
|
|
4801
|
+
return { valid: false, error: `Invalid URL: ${url}` };
|
|
4802
|
+
}
|
|
4803
|
+
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
|
|
4804
|
+
return { valid: false, error: `Invalid protocol: ${parsedUrl.protocol}. Only http/https allowed.` };
|
|
4805
|
+
}
|
|
4806
|
+
if (!options.allowPrivateNetwork) {
|
|
4807
|
+
const hostname = parsedUrl.hostname.toLowerCase();
|
|
4808
|
+
if (hostname === "localhost" || hostname.endsWith(".localhost") || hostname.endsWith(".local")) {
|
|
4809
|
+
return { valid: false, error: `Blocked: localhost/local hostnames not allowed` };
|
|
4810
|
+
}
|
|
4811
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(hostname) && isPrivateIp(hostname)) {
|
|
4812
|
+
return { valid: false, error: `Blocked: private IP address ${hostname}` };
|
|
4813
|
+
}
|
|
4814
|
+
const resolveHostnames = options.resolveHostnames ?? true;
|
|
4815
|
+
if (resolveHostnames) {
|
|
4816
|
+
try {
|
|
4817
|
+
const addrs = await lookup2(hostname, { all: true, verbatim: true });
|
|
4818
|
+
for (const addr of addrs) {
|
|
4819
|
+
if (isPrivateIp(addr.address)) {
|
|
4820
|
+
return { valid: false, error: `Blocked: ${hostname} resolves to private IP ${addr.address}` };
|
|
4821
|
+
}
|
|
4822
|
+
}
|
|
4823
|
+
} catch (e) {
|
|
4824
|
+
return { valid: false, error: `DNS resolution failed for ${hostname}` };
|
|
4825
|
+
}
|
|
4826
|
+
}
|
|
4827
|
+
}
|
|
4828
|
+
return { valid: true, parsedUrl };
|
|
4829
|
+
}
|
|
4830
|
+
var HTTPREQUEST_DESCRIPTION = `Make HTTP requests to APIs and web services.
|
|
4831
|
+
|
|
4832
|
+
Use this tool when you need to:
|
|
4833
|
+
- Call REST APIs (GET, POST, PUT, DELETE, etc.)
|
|
4834
|
+
- Send data to web services
|
|
4835
|
+
- Fetch JSON data from APIs
|
|
4836
|
+
- Interact with webhooks
|
|
4837
|
+
|
|
4838
|
+
Parameters:
|
|
4839
|
+
- method: HTTP method (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS)
|
|
4840
|
+
- url: Full URL to request (required)
|
|
4841
|
+
- headers: Request headers as key-value pairs (optional)
|
|
4842
|
+
- body: Request body for POST/PUT/PATCH (optional, auto-serialized to JSON)
|
|
4843
|
+
- timeout: Request timeout in ms (default: 30000, max: 120000)
|
|
4844
|
+
- responseType: Expected response type - 'json', 'text', or 'binary' (default: auto-detect)
|
|
4845
|
+
|
|
4846
|
+
Security:
|
|
4847
|
+
- Private/local network access is blocked by default
|
|
4848
|
+
- Maximum response size: 5MB
|
|
4849
|
+
|
|
4850
|
+
Best practices:
|
|
4851
|
+
- Include appropriate Content-Type header for POST/PUT requests
|
|
4852
|
+
- Handle authentication via headers (Authorization, API keys, etc.)
|
|
4853
|
+
- Use responseType: 'json' when expecting JSON response
|
|
4854
|
+
|
|
4855
|
+
Example:
|
|
4856
|
+
{
|
|
4857
|
+
"method": "POST",
|
|
4858
|
+
"url": "https://api.example.com/data",
|
|
4859
|
+
"headers": {
|
|
4860
|
+
"Authorization": "Bearer token123",
|
|
4861
|
+
"Content-Type": "application/json"
|
|
4862
|
+
},
|
|
4863
|
+
"body": { "name": "test", "value": 42 }
|
|
4864
|
+
}`;
|
|
4865
|
+
function createHttpRequestTool(options = {}) {
|
|
4866
|
+
return {
|
|
4867
|
+
name: "HttpRequest",
|
|
4868
|
+
description: HTTPREQUEST_DESCRIPTION,
|
|
4869
|
+
inputSchema: {
|
|
4870
|
+
type: "object",
|
|
4871
|
+
properties: {
|
|
4872
|
+
method: {
|
|
4873
|
+
type: "string",
|
|
4874
|
+
enum: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
|
|
4875
|
+
description: "HTTP method"
|
|
4876
|
+
},
|
|
4877
|
+
url: {
|
|
4878
|
+
type: "string",
|
|
4879
|
+
description: "URL to request"
|
|
4880
|
+
},
|
|
4881
|
+
headers: {
|
|
4882
|
+
type: "object",
|
|
4883
|
+
description: "Request headers",
|
|
4884
|
+
additionalProperties: { type: "string" }
|
|
4885
|
+
},
|
|
4886
|
+
body: {
|
|
4887
|
+
description: "Request body (auto-serialized to JSON if object)"
|
|
4888
|
+
},
|
|
4889
|
+
timeout: {
|
|
4890
|
+
type: "number",
|
|
4891
|
+
description: "Request timeout in milliseconds (default: 30000, max: 120000)"
|
|
4892
|
+
},
|
|
4893
|
+
responseType: {
|
|
4894
|
+
type: "string",
|
|
4895
|
+
enum: ["json", "text", "binary"],
|
|
4896
|
+
description: "Expected response type (default: auto-detect)"
|
|
4897
|
+
}
|
|
4898
|
+
},
|
|
4899
|
+
required: ["method", "url"]
|
|
4900
|
+
},
|
|
4901
|
+
execute: async (rawInput, context) => {
|
|
4902
|
+
const input = rawInput;
|
|
4903
|
+
const { method, url, headers = {}, body, timeout, responseType } = input;
|
|
4904
|
+
const validMethods = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
4905
|
+
if (!validMethods.includes(method)) {
|
|
4906
|
+
return {
|
|
4907
|
+
content: `Invalid HTTP method: ${method}. Must be one of: ${validMethods.join(", ")}`,
|
|
4908
|
+
isError: true
|
|
4909
|
+
};
|
|
4910
|
+
}
|
|
4911
|
+
const urlValidation = await validateUrl(url, options);
|
|
4912
|
+
if (!urlValidation.valid) {
|
|
4913
|
+
return {
|
|
4914
|
+
content: urlValidation.error,
|
|
4915
|
+
isError: true
|
|
4916
|
+
};
|
|
4917
|
+
}
|
|
4918
|
+
const requestTimeout = Math.min(timeout ?? DEFAULT_TIMEOUT2, 120000);
|
|
4919
|
+
const controller = new AbortController;
|
|
4920
|
+
const timeoutId = setTimeout(() => controller.abort(), requestTimeout);
|
|
4921
|
+
const signal = context.abortSignal ? AbortSignal.any([controller.signal, context.abortSignal]) : controller.signal;
|
|
4922
|
+
const requestHeaders = {
|
|
4923
|
+
"User-Agent": "FormAgent-SDK/1.0",
|
|
4924
|
+
...headers
|
|
4925
|
+
};
|
|
4926
|
+
let requestBody;
|
|
4927
|
+
if (body !== undefined && ["POST", "PUT", "PATCH"].includes(method)) {
|
|
4928
|
+
if (typeof body === "string") {
|
|
4929
|
+
requestBody = body;
|
|
4930
|
+
} else {
|
|
4931
|
+
requestBody = JSON.stringify(body);
|
|
4932
|
+
if (!requestHeaders["Content-Type"] && !requestHeaders["content-type"]) {
|
|
4933
|
+
requestHeaders["Content-Type"] = "application/json";
|
|
4934
|
+
}
|
|
4935
|
+
}
|
|
4936
|
+
}
|
|
4937
|
+
try {
|
|
4938
|
+
const response = await fetch(urlValidation.parsedUrl.toString(), {
|
|
4939
|
+
method,
|
|
4940
|
+
headers: requestHeaders,
|
|
4941
|
+
body: requestBody,
|
|
4942
|
+
signal
|
|
4943
|
+
});
|
|
4944
|
+
clearTimeout(timeoutId);
|
|
4945
|
+
const contentLength = response.headers.get("content-length");
|
|
4946
|
+
if (contentLength && parseInt(contentLength, 10) > MAX_RESPONSE_SIZE) {
|
|
4947
|
+
return {
|
|
4948
|
+
content: `Response too large: ${contentLength} bytes (max: ${MAX_RESPONSE_SIZE})`,
|
|
4949
|
+
isError: true
|
|
4950
|
+
};
|
|
4951
|
+
}
|
|
4952
|
+
const contentType = response.headers.get("content-type") || "";
|
|
4953
|
+
let responseBody;
|
|
4954
|
+
let responseText;
|
|
4955
|
+
const effectiveResponseType = responseType || (contentType.includes("application/json") ? "json" : "text");
|
|
4956
|
+
if (effectiveResponseType === "json") {
|
|
4957
|
+
try {
|
|
4958
|
+
responseBody = await response.json();
|
|
4959
|
+
responseText = JSON.stringify(responseBody, null, 2);
|
|
4960
|
+
} catch {
|
|
4961
|
+
responseText = await response.text();
|
|
4962
|
+
responseBody = responseText;
|
|
4963
|
+
}
|
|
4964
|
+
} else if (effectiveResponseType === "binary") {
|
|
4965
|
+
const buffer = await response.arrayBuffer();
|
|
4966
|
+
responseText = `[Binary data: ${buffer.byteLength} bytes]`;
|
|
4967
|
+
responseBody = { type: "binary", size: buffer.byteLength };
|
|
4968
|
+
} else {
|
|
4969
|
+
responseText = await response.text();
|
|
4970
|
+
responseBody = responseText;
|
|
4971
|
+
}
|
|
4972
|
+
if (responseText.length > 1e5) {
|
|
4973
|
+
responseText = responseText.slice(0, 1e5) + `
|
|
4974
|
+
|
|
4975
|
+
... (truncated)`;
|
|
4976
|
+
}
|
|
4977
|
+
const statusEmoji = response.ok ? "✓" : "✗";
|
|
4978
|
+
const output = `${statusEmoji} ${method} ${url}
|
|
4979
|
+
Status: ${response.status} ${response.statusText}
|
|
4980
|
+
Content-Type: ${contentType}
|
|
4981
|
+
|
|
4982
|
+
Response:
|
|
4983
|
+
${responseText}`;
|
|
4984
|
+
return {
|
|
4985
|
+
content: output,
|
|
4986
|
+
isError: !response.ok,
|
|
4987
|
+
metadata: {
|
|
4988
|
+
status: response.status,
|
|
4989
|
+
statusText: response.statusText,
|
|
4990
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
4991
|
+
body: responseBody
|
|
4992
|
+
}
|
|
4993
|
+
};
|
|
4994
|
+
} catch (error) {
|
|
4995
|
+
clearTimeout(timeoutId);
|
|
4996
|
+
if (error instanceof Error) {
|
|
4997
|
+
if (error.name === "AbortError") {
|
|
4998
|
+
return {
|
|
4999
|
+
content: `Request timed out after ${requestTimeout}ms`,
|
|
5000
|
+
isError: true
|
|
5001
|
+
};
|
|
5002
|
+
}
|
|
5003
|
+
return {
|
|
5004
|
+
content: `Request failed: ${error.message}`,
|
|
5005
|
+
isError: true
|
|
5006
|
+
};
|
|
5007
|
+
}
|
|
5008
|
+
return {
|
|
5009
|
+
content: `Request failed: ${String(error)}`,
|
|
5010
|
+
isError: true
|
|
5011
|
+
};
|
|
5012
|
+
}
|
|
5013
|
+
}
|
|
5014
|
+
};
|
|
5015
|
+
}
|
|
5016
|
+
var HttpRequestTool = createHttpRequestTool();
|
|
4086
5017
|
// src/tools/builtin/index.ts
|
|
4087
5018
|
var builtinTools = [
|
|
4088
5019
|
BashTool,
|
|
@@ -4092,14 +5023,18 @@ var builtinTools = [
|
|
|
4092
5023
|
GlobTool,
|
|
4093
5024
|
GrepTool,
|
|
4094
5025
|
WebFetchTool,
|
|
4095
|
-
|
|
5026
|
+
WebSearchTool,
|
|
5027
|
+
HttpRequestTool,
|
|
5028
|
+
TodoWriteTool,
|
|
5029
|
+
AskUserTool,
|
|
5030
|
+
BatchTool
|
|
4096
5031
|
];
|
|
4097
5032
|
// src/utils/env.ts
|
|
4098
5033
|
import { existsSync as existsSync9, readFileSync } from "node:fs";
|
|
4099
|
-
import { join as
|
|
5034
|
+
import { join as join7 } from "node:path";
|
|
4100
5035
|
function loadEnvOverride(cwd) {
|
|
4101
5036
|
const dir = cwd || process.cwd();
|
|
4102
|
-
const envPath =
|
|
5037
|
+
const envPath = join7(dir, ".env");
|
|
4103
5038
|
if (!existsSync9(envPath)) {
|
|
4104
5039
|
return;
|
|
4105
5040
|
}
|
|
@@ -4129,7 +5064,7 @@ function loadEnvOverride(cwd) {
|
|
|
4129
5064
|
// src/cli/cli.ts
|
|
4130
5065
|
loadEnvOverride();
|
|
4131
5066
|
var VERSION = "0.1.0";
|
|
4132
|
-
var SKILLS_PATH =
|
|
5067
|
+
var SKILLS_PATH = join8(homedir4(), ".claude");
|
|
4133
5068
|
var colors = {
|
|
4134
5069
|
reset: "\x1B[0m",
|
|
4135
5070
|
bold: "\x1B[1m",
|
|
@@ -4158,7 +5093,7 @@ var totalInputTokens = 0;
|
|
|
4158
5093
|
var totalOutputTokens = 0;
|
|
4159
5094
|
var messageCount = 0;
|
|
4160
5095
|
function isGitRepo(dir) {
|
|
4161
|
-
return existsSync10(
|
|
5096
|
+
return existsSync10(join8(dir, ".git"));
|
|
4162
5097
|
}
|
|
4163
5098
|
function getOsVersion() {
|
|
4164
5099
|
try {
|
|
@@ -4206,6 +5141,7 @@ ${c.bold("Interactive Commands:")}
|
|
|
4206
5141
|
${c.cyan("/skills")} List available skills
|
|
4207
5142
|
${c.cyan("/todos")} Show current todo list
|
|
4208
5143
|
${c.cyan("/usage")} Show token usage statistics
|
|
5144
|
+
${c.cyan("/debug")} Show debug info (prompt, model, env)
|
|
4209
5145
|
${c.cyan("/exit")} Exit the CLI
|
|
4210
5146
|
|
|
4211
5147
|
${c.bold("Environment:")}
|
|
@@ -4252,6 +5188,7 @@ function printInteractiveHelp() {
|
|
|
4252
5188
|
console.log(` ${c.cyan("/skills")} List available skills`);
|
|
4253
5189
|
console.log(` ${c.cyan("/todos")} Show current todo list`);
|
|
4254
5190
|
console.log(` ${c.cyan("/usage")} Show token usage statistics`);
|
|
5191
|
+
console.log(` ${c.cyan("/debug")} Show debug info (prompt, model, env)`);
|
|
4255
5192
|
console.log(` ${c.cyan("/exit")} Exit the CLI`);
|
|
4256
5193
|
console.log();
|
|
4257
5194
|
}
|
|
@@ -4322,6 +5259,62 @@ function printUsage() {
|
|
|
4322
5259
|
console.log(` ${c.cyan("Est. cost:")} $${(inputCost + outputCost).toFixed(4)}`);
|
|
4323
5260
|
console.log();
|
|
4324
5261
|
}
|
|
5262
|
+
function printDebug() {
|
|
5263
|
+
const model = getDefaultModel();
|
|
5264
|
+
const tools = getAllTools();
|
|
5265
|
+
const systemPrompt = buildSystemPrompt();
|
|
5266
|
+
const cwd = process.cwd();
|
|
5267
|
+
console.log();
|
|
5268
|
+
console.log(c.bold("═══════════════════════════════════════════════════════════"));
|
|
5269
|
+
console.log(c.bold(" DEBUG INFORMATION "));
|
|
5270
|
+
console.log(c.bold("═══════════════════════════════════════════════════════════"));
|
|
5271
|
+
console.log();
|
|
5272
|
+
console.log(c.bold("Model:"));
|
|
5273
|
+
console.log(` ${c.cyan("Current:")} ${model}`);
|
|
5274
|
+
console.log(` ${c.cyan("ANTHROPIC_MODEL:")} ${process.env.ANTHROPIC_MODEL || c.dim("(not set)")}`);
|
|
5275
|
+
console.log(` ${c.cyan("OPENAI_MODEL:")} ${process.env.OPENAI_MODEL || c.dim("(not set)")}`);
|
|
5276
|
+
console.log(` ${c.cyan("OPENAI_BASE_URL:")} ${process.env.OPENAI_BASE_URL || c.dim("(not set)")}`);
|
|
5277
|
+
console.log();
|
|
5278
|
+
console.log(c.bold("API Keys:"));
|
|
5279
|
+
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
5280
|
+
const openaiKey = process.env.OPENAI_API_KEY;
|
|
5281
|
+
console.log(` ${c.cyan("ANTHROPIC_API_KEY:")} ${anthropicKey ? c.green("✓ set") + c.dim(` (${anthropicKey.slice(0, 8)}...${anthropicKey.slice(-4)})`) : c.red("✗ not set")}`);
|
|
5282
|
+
console.log(` ${c.cyan("OPENAI_API_KEY:")} ${openaiKey ? c.green("✓ set") + c.dim(` (${openaiKey.slice(0, 8)}...${openaiKey.slice(-4)})`) : c.red("✗ not set")}`);
|
|
5283
|
+
console.log();
|
|
5284
|
+
console.log(c.bold("Environment:"));
|
|
5285
|
+
console.log(` ${c.cyan("Working dir:")} ${cwd}`);
|
|
5286
|
+
console.log(` ${c.cyan("Git repo:")} ${isGitRepo(cwd) ? c.green("Yes") : "No"}`);
|
|
5287
|
+
console.log(` ${c.cyan("Platform:")} ${process.platform}`);
|
|
5288
|
+
console.log(` ${c.cyan("OS Version:")} ${getOsVersion()}`);
|
|
5289
|
+
console.log(` ${c.cyan("Shell:")} ${process.env.SHELL || c.dim("(not set)")}`);
|
|
5290
|
+
console.log(` ${c.cyan("Skills path:")} ${SKILLS_PATH}`);
|
|
5291
|
+
console.log();
|
|
5292
|
+
console.log(c.bold("Tools:") + c.dim(` (${tools.length} total)`));
|
|
5293
|
+
const toolNames = tools.map((t) => t.name);
|
|
5294
|
+
console.log(` ${toolNames.join(", ")}`);
|
|
5295
|
+
console.log();
|
|
5296
|
+
console.log(c.bold("Session State:"));
|
|
5297
|
+
console.log(` ${c.cyan("Active:")} ${session ? c.green("Yes") : "No"}`);
|
|
5298
|
+
console.log(` ${c.cyan("Messages:")} ${messageCount}`);
|
|
5299
|
+
console.log(` ${c.cyan("Input tokens:")} ${totalInputTokens.toLocaleString()}`);
|
|
5300
|
+
console.log(` ${c.cyan("Output tokens:")} ${totalOutputTokens.toLocaleString()}`);
|
|
5301
|
+
console.log();
|
|
5302
|
+
console.log(c.bold("System Prompt:") + c.dim(` (${systemPrompt.length} chars)`));
|
|
5303
|
+
console.log(c.dim("─".repeat(60)));
|
|
5304
|
+
const promptLines = systemPrompt.split(`
|
|
5305
|
+
`);
|
|
5306
|
+
const maxLines = 50;
|
|
5307
|
+
for (let i = 0;i < Math.min(promptLines.length, maxLines); i++) {
|
|
5308
|
+
const lineNum = String(i + 1).padStart(3, " ");
|
|
5309
|
+
const line = promptLines[i].slice(0, 75);
|
|
5310
|
+
console.log(`${c.dim(lineNum + "│")} ${line}${promptLines[i].length > 75 ? c.dim("...") : ""}`);
|
|
5311
|
+
}
|
|
5312
|
+
if (promptLines.length > maxLines) {
|
|
5313
|
+
console.log(c.dim(` ... (${promptLines.length - maxLines} more lines)`));
|
|
5314
|
+
}
|
|
5315
|
+
console.log(c.dim("─".repeat(60)));
|
|
5316
|
+
console.log();
|
|
5317
|
+
}
|
|
4325
5318
|
function formatToolInput(name, input) {
|
|
4326
5319
|
switch (name) {
|
|
4327
5320
|
case "Bash":
|
|
@@ -4449,6 +5442,9 @@ async function handleInput(input) {
|
|
|
4449
5442
|
case "/usage":
|
|
4450
5443
|
printUsage();
|
|
4451
5444
|
return true;
|
|
5445
|
+
case "/debug":
|
|
5446
|
+
printDebug();
|
|
5447
|
+
return true;
|
|
4452
5448
|
case "/exit":
|
|
4453
5449
|
case "/quit":
|
|
4454
5450
|
case "/q":
|