@robota-sdk/agent-tools 3.0.0-beta.60 → 3.0.0-beta.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -21
- package/dist/node/index.cjs +440 -95
- package/dist/node/index.d.cts +170 -15
- package/dist/node/index.d.ts +170 -15
- package/dist/node/index.js +417 -80
- package/package.json +4 -3
package/dist/node/index.js
CHANGED
|
@@ -1,3 +1,272 @@
|
|
|
1
|
+
// src/sandbox/e2b-sandbox-client.ts
|
|
2
|
+
var E2BSandboxClient = class {
|
|
3
|
+
sandbox;
|
|
4
|
+
connectSandbox;
|
|
5
|
+
createSandboxFromSnapshot;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.sandbox = options.sandbox;
|
|
8
|
+
this.connectSandbox = options.connectSandbox;
|
|
9
|
+
this.createSandboxFromSnapshot = options.createSandboxFromSnapshot;
|
|
10
|
+
}
|
|
11
|
+
async run(command, options) {
|
|
12
|
+
const result = await this.sandbox.commands.run(command, {
|
|
13
|
+
background: false,
|
|
14
|
+
timeoutMs: options?.timeoutMs,
|
|
15
|
+
cwd: options?.workingDirectory
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
stdout: result.stdout ?? "",
|
|
19
|
+
stderr: result.stderr ?? "",
|
|
20
|
+
exitCode: result.exitCode ?? result.exit_code ?? 0
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async readFile(path) {
|
|
24
|
+
const content = await this.sandbox.files.read(path);
|
|
25
|
+
return typeof content === "string" ? content : Buffer.from(content).toString("utf8");
|
|
26
|
+
}
|
|
27
|
+
async writeFile(path, content) {
|
|
28
|
+
await this.sandbox.files.write(path, content);
|
|
29
|
+
}
|
|
30
|
+
async snapshot() {
|
|
31
|
+
if (this.sandbox.createSnapshot) {
|
|
32
|
+
const snapshot = await this.sandbox.createSnapshot();
|
|
33
|
+
const snapshotId = snapshot.snapshotId ?? snapshot.id;
|
|
34
|
+
if (!snapshotId) {
|
|
35
|
+
throw new Error("E2B createSnapshot() did not return a snapshot id.");
|
|
36
|
+
}
|
|
37
|
+
return snapshotId;
|
|
38
|
+
}
|
|
39
|
+
const sandboxId = this.sandbox.sandboxId;
|
|
40
|
+
if (!sandboxId) {
|
|
41
|
+
throw new Error("E2B sandboxId is required to create a resumable sandbox snapshot.");
|
|
42
|
+
}
|
|
43
|
+
if (!this.sandbox.pause) {
|
|
44
|
+
throw new Error("E2B sandbox adapter does not expose pause().");
|
|
45
|
+
}
|
|
46
|
+
await this.sandbox.pause();
|
|
47
|
+
return sandboxId;
|
|
48
|
+
}
|
|
49
|
+
async restore(snapshotId) {
|
|
50
|
+
if (this.createSandboxFromSnapshot) {
|
|
51
|
+
this.sandbox = await this.createSandboxFromSnapshot(snapshotId);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (this.connectSandbox) {
|
|
55
|
+
this.sandbox = await this.connectSandbox(snapshotId);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (this.sandbox.sandboxId === snapshotId && this.sandbox.connect) {
|
|
59
|
+
this.sandbox = await this.sandbox.connect();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
throw new Error(
|
|
63
|
+
"E2B sandbox restore requires connectSandbox(snapshotId) or sandbox.connect()."
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// src/sandbox/in-memory-sandbox-client.ts
|
|
69
|
+
var InMemorySandboxClient = class {
|
|
70
|
+
files = /* @__PURE__ */ new Map();
|
|
71
|
+
snapshots = /* @__PURE__ */ new Map();
|
|
72
|
+
runHandler;
|
|
73
|
+
snapshotSequence = 0;
|
|
74
|
+
constructor(options = {}) {
|
|
75
|
+
for (const [path, content] of Object.entries(options.files ?? {})) {
|
|
76
|
+
this.files.set(path, content);
|
|
77
|
+
}
|
|
78
|
+
this.runHandler = options.runHandler;
|
|
79
|
+
}
|
|
80
|
+
async run(command, options) {
|
|
81
|
+
if (this.runHandler) {
|
|
82
|
+
return this.runHandler(command, options, this.files);
|
|
83
|
+
}
|
|
84
|
+
return { stdout: "", stderr: "", exitCode: 0 };
|
|
85
|
+
}
|
|
86
|
+
async readFile(path) {
|
|
87
|
+
const content = this.files.get(path);
|
|
88
|
+
if (content === void 0) {
|
|
89
|
+
throw new Error(`Sandbox file not found: ${path}`);
|
|
90
|
+
}
|
|
91
|
+
return content;
|
|
92
|
+
}
|
|
93
|
+
async writeFile(path, content) {
|
|
94
|
+
this.files.set(path, content);
|
|
95
|
+
}
|
|
96
|
+
async snapshot() {
|
|
97
|
+
const snapshotId = `snapshot-${++this.snapshotSequence}`;
|
|
98
|
+
this.snapshots.set(snapshotId, new Map(this.files));
|
|
99
|
+
return snapshotId;
|
|
100
|
+
}
|
|
101
|
+
async restore(snapshotId) {
|
|
102
|
+
const snapshot = this.snapshots.get(snapshotId);
|
|
103
|
+
if (!snapshot) {
|
|
104
|
+
throw new Error(`Sandbox snapshot not found: ${snapshotId}`);
|
|
105
|
+
}
|
|
106
|
+
this.files.clear();
|
|
107
|
+
for (const [path, content] of snapshot.entries()) {
|
|
108
|
+
this.files.set(path, content);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
getFile(path) {
|
|
112
|
+
return this.files.get(path);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// src/sandbox/workspace-manifest.ts
|
|
117
|
+
import { readdir, readFile } from "fs/promises";
|
|
118
|
+
import { isAbsolute, join, posix, resolve } from "path";
|
|
119
|
+
var DEFAULT_TARGET_ROOT = "/workspace";
|
|
120
|
+
var WINDOWS_ABSOLUTE_PATH_PATTERN = /^[A-Za-z]:[\\/]/;
|
|
121
|
+
var SHELL_QUOTE_PATTERN = /'/g;
|
|
122
|
+
async function applyWorkspaceManifest(sandboxClient, manifest, options = {}) {
|
|
123
|
+
if (sandboxClient.applyManifest) {
|
|
124
|
+
return sandboxClient.applyManifest(manifest, options);
|
|
125
|
+
}
|
|
126
|
+
const targetRoot = normalizeSandboxRoot(options.targetRoot ?? DEFAULT_TARGET_ROOT);
|
|
127
|
+
const appliedEntries = [];
|
|
128
|
+
for (const [rawPath, entry] of Object.entries(manifest.entries)) {
|
|
129
|
+
const path = validateWorkspaceManifestPath(rawPath);
|
|
130
|
+
const targetPath = joinSandboxPath(targetRoot, path);
|
|
131
|
+
appliedEntries.push(
|
|
132
|
+
await applyManifestEntry(sandboxClient, path, targetPath, targetRoot, entry, options)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return { entries: appliedEntries };
|
|
136
|
+
}
|
|
137
|
+
function validateWorkspaceManifestPath(path) {
|
|
138
|
+
if (path.length === 0) {
|
|
139
|
+
throw new Error("workspace manifest path must not be empty");
|
|
140
|
+
}
|
|
141
|
+
if (path.includes("\0")) {
|
|
142
|
+
throw new Error("workspace manifest path must not contain NUL bytes");
|
|
143
|
+
}
|
|
144
|
+
if (path.startsWith("/") || path.startsWith("\\") || WINDOWS_ABSOLUTE_PATH_PATTERN.test(path)) {
|
|
145
|
+
throw new Error("workspace manifest path must be workspace-relative");
|
|
146
|
+
}
|
|
147
|
+
const parts = path.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
148
|
+
if (parts.length === 0) {
|
|
149
|
+
throw new Error("workspace manifest path must not resolve to the workspace root");
|
|
150
|
+
}
|
|
151
|
+
if (parts.some((part) => part === "..")) {
|
|
152
|
+
throw new Error("workspace manifest path cannot contain traversal segments");
|
|
153
|
+
}
|
|
154
|
+
const normalizedParts = parts.filter((part) => part !== ".");
|
|
155
|
+
if (normalizedParts.length === 0) {
|
|
156
|
+
throw new Error("workspace manifest path must not resolve to the workspace root");
|
|
157
|
+
}
|
|
158
|
+
return normalizedParts.join("/");
|
|
159
|
+
}
|
|
160
|
+
async function applyManifestEntry(sandboxClient, path, targetPath, targetRoot, entry, options) {
|
|
161
|
+
switch (entry.type) {
|
|
162
|
+
case "file":
|
|
163
|
+
await writeSandboxFile(sandboxClient, targetPath, targetRoot, entry.content);
|
|
164
|
+
return createAppliedEntry(path, entry.type);
|
|
165
|
+
case "dir":
|
|
166
|
+
await createSandboxDirectory(sandboxClient, targetPath);
|
|
167
|
+
return createAppliedEntry(path, entry.type);
|
|
168
|
+
case "localFile":
|
|
169
|
+
await copyLocalFile(sandboxClient, entry.src, targetPath, targetRoot, options);
|
|
170
|
+
return createAppliedEntry(path, entry.type);
|
|
171
|
+
case "localDir":
|
|
172
|
+
await copyLocalDirectory(sandboxClient, entry.src, targetPath, options);
|
|
173
|
+
return createAppliedEntry(path, entry.type);
|
|
174
|
+
case "gitRepo":
|
|
175
|
+
await cloneGitRepository(sandboxClient, entry, targetPath);
|
|
176
|
+
return createAppliedEntry(path, entry.type);
|
|
177
|
+
case "s3Mount":
|
|
178
|
+
case "gcsMount":
|
|
179
|
+
case "r2Mount":
|
|
180
|
+
case "azureBlobMount":
|
|
181
|
+
return {
|
|
182
|
+
path,
|
|
183
|
+
type: entry.type,
|
|
184
|
+
status: "unsupported",
|
|
185
|
+
message: `${entry.type} requires a provider-specific sandbox adapter.`
|
|
186
|
+
};
|
|
187
|
+
default:
|
|
188
|
+
return assertUnreachable(entry);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function createAppliedEntry(path, type) {
|
|
192
|
+
return { path, type, status: "applied" };
|
|
193
|
+
}
|
|
194
|
+
async function copyLocalFile(sandboxClient, source, targetPath, targetRoot, options) {
|
|
195
|
+
const hostSourcePath = resolveHostSourcePath(source, options.hostRoot);
|
|
196
|
+
const content = await readFile(hostSourcePath, "utf8");
|
|
197
|
+
await writeSandboxFile(sandboxClient, targetPath, targetRoot, content);
|
|
198
|
+
}
|
|
199
|
+
async function copyLocalDirectory(sandboxClient, source, targetPath, options) {
|
|
200
|
+
const hostSourcePath = resolveHostSourcePath(source, options.hostRoot);
|
|
201
|
+
await copyLocalDirectoryRecursive(sandboxClient, hostSourcePath, targetPath);
|
|
202
|
+
}
|
|
203
|
+
async function copyLocalDirectoryRecursive(sandboxClient, sourcePath, targetPath) {
|
|
204
|
+
await createSandboxDirectory(sandboxClient, targetPath);
|
|
205
|
+
const entries = await readdir(sourcePath, { withFileTypes: true });
|
|
206
|
+
for (const entry of entries) {
|
|
207
|
+
const childSourcePath = join(sourcePath, entry.name);
|
|
208
|
+
const childTargetPath = joinSandboxPath(targetPath, entry.name);
|
|
209
|
+
if (entry.isDirectory()) {
|
|
210
|
+
await copyLocalDirectoryRecursive(sandboxClient, childSourcePath, childTargetPath);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (entry.isFile()) {
|
|
214
|
+
const content = await readFile(childSourcePath, "utf8");
|
|
215
|
+
await sandboxClient.writeFile(childTargetPath, content);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async function cloneGitRepository(sandboxClient, entry, targetPath) {
|
|
220
|
+
const shallowArgs = entry.shallow === false ? "" : " --depth 1";
|
|
221
|
+
const refArgs = entry.ref ? ` --branch ${quoteShellArg(entry.ref)}` : "";
|
|
222
|
+
await runSandboxCommand(
|
|
223
|
+
sandboxClient,
|
|
224
|
+
`git clone${shallowArgs}${refArgs} ${quoteShellArg(entry.url)} ${quoteShellArg(targetPath)}`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
async function writeSandboxFile(sandboxClient, targetPath, targetRoot, content) {
|
|
228
|
+
const parentPath = posix.dirname(targetPath);
|
|
229
|
+
if (parentPath !== targetRoot) {
|
|
230
|
+
await createSandboxDirectory(sandboxClient, parentPath);
|
|
231
|
+
}
|
|
232
|
+
await sandboxClient.writeFile(targetPath, content);
|
|
233
|
+
}
|
|
234
|
+
async function createSandboxDirectory(sandboxClient, targetPath) {
|
|
235
|
+
await runSandboxCommand(sandboxClient, `mkdir -p ${quoteShellArg(targetPath)}`);
|
|
236
|
+
}
|
|
237
|
+
async function runSandboxCommand(sandboxClient, command) {
|
|
238
|
+
const result = await sandboxClient.run(command);
|
|
239
|
+
if (result.exitCode !== 0) {
|
|
240
|
+
throw new Error(
|
|
241
|
+
`workspace manifest command failed: ${command}
|
|
242
|
+
${result.stderr ?? result.stdout}`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function resolveHostSourcePath(source, hostRoot) {
|
|
247
|
+
return isAbsolute(source) ? resolve(source) : resolve(hostRoot ?? process.cwd(), source);
|
|
248
|
+
}
|
|
249
|
+
function normalizeSandboxRoot(root) {
|
|
250
|
+
const normalized = root.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
251
|
+
if (!normalized.startsWith("/")) {
|
|
252
|
+
throw new Error("workspace manifest targetRoot must be an absolute sandbox path");
|
|
253
|
+
}
|
|
254
|
+
return normalized.length === 0 ? "/" : normalized;
|
|
255
|
+
}
|
|
256
|
+
function joinSandboxPath(root, path) {
|
|
257
|
+
const normalizedRoot = normalizeSandboxRoot(root);
|
|
258
|
+
if (normalizedRoot === "/") {
|
|
259
|
+
return `/${path}`;
|
|
260
|
+
}
|
|
261
|
+
return `${normalizedRoot}/${path}`;
|
|
262
|
+
}
|
|
263
|
+
function quoteShellArg(value) {
|
|
264
|
+
return `'${value.replace(SHELL_QUOTE_PATTERN, "'\\''")}'`;
|
|
265
|
+
}
|
|
266
|
+
function assertUnreachable(value) {
|
|
267
|
+
throw new Error(`unsupported workspace manifest entry: ${JSON.stringify(value)}`);
|
|
268
|
+
}
|
|
269
|
+
|
|
1
270
|
// src/registry/tool-registry.ts
|
|
2
271
|
import { ValidationError } from "@robota-sdk/agent-core";
|
|
3
272
|
import { logger } from "@robota-sdk/agent-core";
|
|
@@ -810,9 +1079,33 @@ var BashSchema = z.object({
|
|
|
810
1079
|
timeout: z.number().optional().describe("Optional timeout in milliseconds (max 600000). Default is 120000 (2 minutes)"),
|
|
811
1080
|
workingDirectory: z.string().optional().describe("Working directory for the command. Defaults to the current working directory")
|
|
812
1081
|
});
|
|
813
|
-
async function runBash(args) {
|
|
1082
|
+
async function runBash(args, options = {}) {
|
|
814
1083
|
const { command, timeout = DEFAULT_TIMEOUT_MS, workingDirectory } = args;
|
|
815
|
-
|
|
1084
|
+
if (options.sandboxClient) {
|
|
1085
|
+
try {
|
|
1086
|
+
const sandboxResult = await options.sandboxClient.run(command, {
|
|
1087
|
+
timeoutMs: timeout,
|
|
1088
|
+
workingDirectory
|
|
1089
|
+
});
|
|
1090
|
+
const output = sandboxResult.stderr ? `${sandboxResult.stdout}
|
|
1091
|
+
stderr:
|
|
1092
|
+
${sandboxResult.stderr}` : sandboxResult.stdout;
|
|
1093
|
+
const result = {
|
|
1094
|
+
success: true,
|
|
1095
|
+
output,
|
|
1096
|
+
exitCode: sandboxResult.exitCode
|
|
1097
|
+
};
|
|
1098
|
+
return JSON.stringify(result);
|
|
1099
|
+
} catch (err) {
|
|
1100
|
+
const result = {
|
|
1101
|
+
success: false,
|
|
1102
|
+
output: "",
|
|
1103
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1104
|
+
};
|
|
1105
|
+
return JSON.stringify(result);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
return new Promise((resolve4) => {
|
|
816
1109
|
const stdoutChunks = [];
|
|
817
1110
|
const stderrChunks = [];
|
|
818
1111
|
let timedOut = false;
|
|
@@ -841,7 +1134,7 @@ async function runBash(args) {
|
|
|
841
1134
|
if (settled) return;
|
|
842
1135
|
settled = true;
|
|
843
1136
|
clearTimeout(timer);
|
|
844
|
-
|
|
1137
|
+
resolve4(JSON.stringify(result));
|
|
845
1138
|
}
|
|
846
1139
|
child.on("error", (err) => {
|
|
847
1140
|
settle({
|
|
@@ -874,17 +1167,20 @@ ${stderr}` : stdout;
|
|
|
874
1167
|
});
|
|
875
1168
|
});
|
|
876
1169
|
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1170
|
+
function createBashTool(options = {}) {
|
|
1171
|
+
return createZodFunctionTool(
|
|
1172
|
+
"Bash",
|
|
1173
|
+
"Executes a given bash command and returns its output.\n\nThe working directory persists between commands, but shell state does not.\n\nIMPORTANT: Avoid using this tool to run `find`, `grep`, `cat`, `head`, `tail`, `sed`, `awk`, or `echo` commands. Instead, use the appropriate dedicated tool:\n - File search: Use Glob (NOT find or ls)\n - Content search: Use Grep (NOT grep or rg)\n - Read files: Use Read (NOT cat/head/tail)\n - Edit files: Use Edit (NOT sed/awk)\n\nFor simple commands, keep the description brief (5-10 words). For complex commands, include enough context to clarify what the command does.\n\nOutput is limited to 30,000 characters. Longer output will be middle-truncated.",
|
|
1174
|
+
BashSchema,
|
|
1175
|
+
async (params) => {
|
|
1176
|
+
return runBash(params, options);
|
|
1177
|
+
}
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
var bashTool = createBashTool();
|
|
885
1181
|
|
|
886
1182
|
// src/builtins/read-tool.ts
|
|
887
|
-
import { readFile, stat } from "fs/promises";
|
|
1183
|
+
import { readFile as readFile2, stat } from "fs/promises";
|
|
888
1184
|
import { z as z2 } from "zod";
|
|
889
1185
|
var DEFAULT_LIMIT = 2e3;
|
|
890
1186
|
var ReadSchema = z2.object({
|
|
@@ -911,74 +1207,93 @@ function formatWithLineNumbers(lines, startLine) {
|
|
|
911
1207
|
return `${lineNum} ${line}`;
|
|
912
1208
|
}).join("\n");
|
|
913
1209
|
}
|
|
914
|
-
|
|
1210
|
+
function formatReadResult(filePath, content, startLine, limit) {
|
|
1211
|
+
const allLines = content.split("\n");
|
|
1212
|
+
if (allLines[allLines.length - 1] === "") {
|
|
1213
|
+
allLines.pop();
|
|
1214
|
+
}
|
|
1215
|
+
const zeroBasedStart = startLine - 1;
|
|
1216
|
+
const selectedLines = allLines.slice(zeroBasedStart, zeroBasedStart + limit);
|
|
1217
|
+
const output = formatWithLineNumbers(selectedLines, startLine);
|
|
1218
|
+
const totalLines = allLines.length;
|
|
1219
|
+
const returnedLines = selectedLines.length;
|
|
1220
|
+
const header = returnedLines < totalLines ? `[File: ${filePath} (lines ${startLine}-${startLine + returnedLines - 1} of ${totalLines})]
|
|
1221
|
+
` : `[File: ${filePath} (${totalLines} lines)]
|
|
1222
|
+
`;
|
|
1223
|
+
const result = {
|
|
1224
|
+
success: true,
|
|
1225
|
+
output: header + output
|
|
1226
|
+
};
|
|
1227
|
+
return JSON.stringify(result);
|
|
1228
|
+
}
|
|
1229
|
+
async function readFileTool(args, options = {}) {
|
|
915
1230
|
const { filePath, offset, limit = DEFAULT_LIMIT } = args;
|
|
916
1231
|
const startLine = offset !== void 0 && offset > 0 ? offset : 1;
|
|
1232
|
+
if (options.sandboxClient) {
|
|
1233
|
+
try {
|
|
1234
|
+
const content2 = await options.sandboxClient.readFile(filePath);
|
|
1235
|
+
return formatReadResult(filePath, content2, startLine, limit);
|
|
1236
|
+
} catch (err) {
|
|
1237
|
+
const result = {
|
|
1238
|
+
success: false,
|
|
1239
|
+
output: "",
|
|
1240
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1241
|
+
};
|
|
1242
|
+
return JSON.stringify(result);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
917
1245
|
let fileStats;
|
|
918
1246
|
try {
|
|
919
1247
|
fileStats = await stat(filePath);
|
|
920
1248
|
} catch (err) {
|
|
921
|
-
const
|
|
1249
|
+
const result = {
|
|
922
1250
|
success: false,
|
|
923
1251
|
output: "",
|
|
924
1252
|
error: `File not found: ${filePath}`
|
|
925
1253
|
};
|
|
926
|
-
return JSON.stringify(
|
|
1254
|
+
return JSON.stringify(result);
|
|
927
1255
|
}
|
|
928
1256
|
if (!fileStats.isFile()) {
|
|
929
|
-
const
|
|
1257
|
+
const result = {
|
|
930
1258
|
success: false,
|
|
931
1259
|
output: "",
|
|
932
1260
|
error: `Path is not a file: ${filePath}`
|
|
933
1261
|
};
|
|
934
|
-
return JSON.stringify(
|
|
1262
|
+
return JSON.stringify(result);
|
|
935
1263
|
}
|
|
936
1264
|
let buffer;
|
|
937
1265
|
try {
|
|
938
|
-
buffer = await
|
|
1266
|
+
buffer = await readFile2(filePath);
|
|
939
1267
|
} catch (err) {
|
|
940
|
-
const
|
|
1268
|
+
const result = {
|
|
941
1269
|
success: false,
|
|
942
1270
|
output: "",
|
|
943
1271
|
error: err instanceof Error ? err.message : String(err)
|
|
944
1272
|
};
|
|
945
|
-
return JSON.stringify(
|
|
1273
|
+
return JSON.stringify(result);
|
|
946
1274
|
}
|
|
947
1275
|
if (isBinary(buffer)) {
|
|
948
|
-
const
|
|
1276
|
+
const result = {
|
|
949
1277
|
success: false,
|
|
950
1278
|
output: "",
|
|
951
1279
|
error: `Binary file not supported: ${filePath}`
|
|
952
1280
|
};
|
|
953
|
-
return JSON.stringify(
|
|
1281
|
+
return JSON.stringify(result);
|
|
954
1282
|
}
|
|
955
1283
|
const content = buffer.toString("utf8");
|
|
956
|
-
|
|
957
|
-
if (allLines[allLines.length - 1] === "") {
|
|
958
|
-
allLines.pop();
|
|
959
|
-
}
|
|
960
|
-
const zeroBasedStart = startLine - 1;
|
|
961
|
-
const selectedLines = allLines.slice(zeroBasedStart, zeroBasedStart + limit);
|
|
962
|
-
const output = formatWithLineNumbers(selectedLines, startLine);
|
|
963
|
-
const totalLines = allLines.length;
|
|
964
|
-
const returnedLines = selectedLines.length;
|
|
965
|
-
const header = returnedLines < totalLines ? `[File: ${filePath} (lines ${startLine}-${startLine + returnedLines - 1} of ${totalLines})]
|
|
966
|
-
` : `[File: ${filePath} (${totalLines} lines)]
|
|
967
|
-
`;
|
|
968
|
-
const result = {
|
|
969
|
-
success: true,
|
|
970
|
-
output: header + output
|
|
971
|
-
};
|
|
972
|
-
return JSON.stringify(result);
|
|
1284
|
+
return formatReadResult(filePath, content, startLine, limit);
|
|
973
1285
|
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1286
|
+
function createReadTool(options = {}) {
|
|
1287
|
+
return createZodFunctionTool(
|
|
1288
|
+
"Read",
|
|
1289
|
+
"Reads a file from the local filesystem.\n\nBy default, reads up to 2000 lines from the beginning of the file. You can optionally specify offset and limit for partial reads.\n\nResults are returned using cat -n format, with line numbers starting at 1.\n\nThe file_path parameter must be an absolute path, not a relative path.",
|
|
1290
|
+
ReadSchema,
|
|
1291
|
+
async (params) => {
|
|
1292
|
+
return readFileTool(params, options);
|
|
1293
|
+
}
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
var readTool = createReadTool();
|
|
982
1297
|
|
|
983
1298
|
// src/builtins/write-tool.ts
|
|
984
1299
|
import { z as z3 } from "zod";
|
|
@@ -986,7 +1301,7 @@ import { z as z3 } from "zod";
|
|
|
986
1301
|
// src/builtins/atomic-file-write.ts
|
|
987
1302
|
import { randomBytes } from "crypto";
|
|
988
1303
|
import { chmod, mkdir, rename, rm, stat as stat2, writeFile } from "fs/promises";
|
|
989
|
-
import { basename, dirname, join } from "path";
|
|
1304
|
+
import { basename, dirname, join as join2 } from "path";
|
|
990
1305
|
var TEMP_RANDOM_BYTES = 6;
|
|
991
1306
|
var PRESERVED_MODE_BITS = 4095;
|
|
992
1307
|
var MISSING_FILE_ERROR_CODE = "ENOENT";
|
|
@@ -994,7 +1309,7 @@ function createTempFilePath(filePath) {
|
|
|
994
1309
|
const dir = dirname(filePath);
|
|
995
1310
|
const name = basename(filePath);
|
|
996
1311
|
const suffix = randomBytes(TEMP_RANDOM_BYTES).toString("hex");
|
|
997
|
-
return
|
|
1312
|
+
return join2(dir, `.${name}.robota-tmp-${process.pid}-${Date.now()}-${suffix}`);
|
|
998
1313
|
}
|
|
999
1314
|
async function readExistingMode(filePath) {
|
|
1000
1315
|
try {
|
|
@@ -1030,10 +1345,14 @@ var WriteSchema = z3.object({
|
|
|
1030
1345
|
filePath: z3.string().describe("The absolute path to the file to write"),
|
|
1031
1346
|
content: z3.string().describe("The content to write to the file")
|
|
1032
1347
|
});
|
|
1033
|
-
async function writeFileTool(args) {
|
|
1348
|
+
async function writeFileTool(args, options = {}) {
|
|
1034
1349
|
const { filePath, content } = args;
|
|
1035
1350
|
try {
|
|
1036
|
-
|
|
1351
|
+
if (options.sandboxClient) {
|
|
1352
|
+
await options.sandboxClient.writeFile(filePath, content);
|
|
1353
|
+
} else {
|
|
1354
|
+
await atomicWriteUtf8File(filePath, content);
|
|
1355
|
+
}
|
|
1037
1356
|
const result = {
|
|
1038
1357
|
success: true,
|
|
1039
1358
|
output: `Written ${Buffer.byteLength(content, "utf8")} bytes to ${filePath}`
|
|
@@ -1048,17 +1367,20 @@ async function writeFileTool(args) {
|
|
|
1048
1367
|
return JSON.stringify(result);
|
|
1049
1368
|
}
|
|
1050
1369
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1370
|
+
function createWriteTool(options = {}) {
|
|
1371
|
+
return createZodFunctionTool(
|
|
1372
|
+
"Write",
|
|
1373
|
+
"Writes a file to the local filesystem. This will overwrite an existing file if one exists.\n\nALWAYS prefer the Edit tool for modifying existing files \u2014 it only sends the diff. Only use this tool to create new files or for complete rewrites.\n\nNEVER create documentation files (*.md) or README files unless explicitly requested by the user.",
|
|
1374
|
+
WriteSchema,
|
|
1375
|
+
async (params) => {
|
|
1376
|
+
return writeFileTool(params, options);
|
|
1377
|
+
}
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
var writeTool = createWriteTool();
|
|
1059
1381
|
|
|
1060
1382
|
// src/builtins/edit-tool.ts
|
|
1061
|
-
import { readFile as
|
|
1383
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
1062
1384
|
import { z as z4 } from "zod";
|
|
1063
1385
|
var EditSchema = z4.object({
|
|
1064
1386
|
filePath: z4.string().describe("The absolute path to the file to modify"),
|
|
@@ -1068,11 +1390,11 @@ var EditSchema = z4.object({
|
|
|
1068
1390
|
"Replace all occurrences of old_string (default: false). Useful for renaming variables"
|
|
1069
1391
|
)
|
|
1070
1392
|
});
|
|
1071
|
-
async function editFileTool(args) {
|
|
1393
|
+
async function editFileTool(args, options = {}) {
|
|
1072
1394
|
const { filePath, oldString, newString, replaceAll = false } = args;
|
|
1073
1395
|
let content;
|
|
1074
1396
|
try {
|
|
1075
|
-
content = await
|
|
1397
|
+
content = options.sandboxClient ? await options.sandboxClient.readFile(filePath) : await readFile3(filePath, "utf8");
|
|
1076
1398
|
} catch (err) {
|
|
1077
1399
|
const result2 = {
|
|
1078
1400
|
success: false,
|
|
@@ -1104,7 +1426,11 @@ async function editFileTool(args) {
|
|
|
1104
1426
|
}
|
|
1105
1427
|
const updated = replaceAll ? content.split(oldString).join(newString) : content.slice(0, content.indexOf(oldString)) + newString + content.slice(content.indexOf(oldString) + oldString.length);
|
|
1106
1428
|
try {
|
|
1107
|
-
|
|
1429
|
+
if (options.sandboxClient) {
|
|
1430
|
+
await options.sandboxClient.writeFile(filePath, updated);
|
|
1431
|
+
} else {
|
|
1432
|
+
await atomicWriteUtf8File(filePath, updated);
|
|
1433
|
+
}
|
|
1108
1434
|
} catch (err) {
|
|
1109
1435
|
const result2 = {
|
|
1110
1436
|
success: false,
|
|
@@ -1123,18 +1449,21 @@ async function editFileTool(args) {
|
|
|
1123
1449
|
};
|
|
1124
1450
|
return JSON.stringify(result);
|
|
1125
1451
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1452
|
+
function createEditTool(options = {}) {
|
|
1453
|
+
return createZodFunctionTool(
|
|
1454
|
+
"Edit",
|
|
1455
|
+
"Performs exact string replacements in files.\n\nYou must use the Read tool at least once before editing. When editing text from Read output, preserve the exact indentation.\n\nThe edit will FAIL if old_string is not unique in the file. Either provide more surrounding context to make it unique, or use replace_all to change every instance.\n\nALWAYS prefer editing existing files over creating new ones.",
|
|
1456
|
+
EditSchema,
|
|
1457
|
+
async (params) => {
|
|
1458
|
+
return editFileTool(params, options);
|
|
1459
|
+
}
|
|
1460
|
+
);
|
|
1461
|
+
}
|
|
1462
|
+
var editTool = createEditTool();
|
|
1134
1463
|
|
|
1135
1464
|
// src/builtins/glob-tool.ts
|
|
1136
1465
|
import { stat as stat3 } from "fs/promises";
|
|
1137
|
-
import { resolve } from "path";
|
|
1466
|
+
import { resolve as resolve2 } from "path";
|
|
1138
1467
|
import fg from "fast-glob";
|
|
1139
1468
|
import { z as z5 } from "zod";
|
|
1140
1469
|
var DEFAULT_MAX_RESULTS = 1e3;
|
|
@@ -1149,7 +1478,7 @@ var GlobSchema = z5.object({
|
|
|
1149
1478
|
});
|
|
1150
1479
|
async function globFileTool(args) {
|
|
1151
1480
|
const { pattern, path: basePath } = args;
|
|
1152
|
-
const cwd = basePath ?
|
|
1481
|
+
const cwd = basePath ? resolve2(basePath) : process.cwd();
|
|
1153
1482
|
let matches;
|
|
1154
1483
|
try {
|
|
1155
1484
|
matches = await fg(pattern, {
|
|
@@ -1168,7 +1497,7 @@ async function globFileTool(args) {
|
|
|
1168
1497
|
}
|
|
1169
1498
|
const withMtime = await Promise.all(
|
|
1170
1499
|
matches.map(async (p) => {
|
|
1171
|
-
const absPath =
|
|
1500
|
+
const absPath = resolve2(cwd, p);
|
|
1172
1501
|
try {
|
|
1173
1502
|
const s = await stat3(absPath);
|
|
1174
1503
|
return { path: p, mtime: s.mtimeMs };
|
|
@@ -1205,8 +1534,8 @@ var globTool = createZodFunctionTool(
|
|
|
1205
1534
|
);
|
|
1206
1535
|
|
|
1207
1536
|
// src/builtins/grep-tool.ts
|
|
1208
|
-
import { readFile as
|
|
1209
|
-
import { join as
|
|
1537
|
+
import { readFile as readFile4, readdir as readdir2, stat as stat4 } from "fs/promises";
|
|
1538
|
+
import { join as join3, resolve as resolve3 } from "path";
|
|
1210
1539
|
import { z as z6 } from "zod";
|
|
1211
1540
|
var GrepSchema = z6.object({
|
|
1212
1541
|
pattern: z6.string().describe("The regular expression pattern to search for in file contents"),
|
|
@@ -1234,13 +1563,13 @@ async function collectFiles(dirPath, glob) {
|
|
|
1234
1563
|
async function walk(current) {
|
|
1235
1564
|
let entryNames;
|
|
1236
1565
|
try {
|
|
1237
|
-
entryNames = await
|
|
1566
|
+
entryNames = await readdir2(current);
|
|
1238
1567
|
} catch {
|
|
1239
1568
|
return;
|
|
1240
1569
|
}
|
|
1241
1570
|
for (const name of entryNames) {
|
|
1242
1571
|
if (name === "node_modules" || name === ".git") continue;
|
|
1243
|
-
const fullPath =
|
|
1572
|
+
const fullPath = join3(current, name);
|
|
1244
1573
|
let fileStat;
|
|
1245
1574
|
try {
|
|
1246
1575
|
fileStat = await stat4(fullPath);
|
|
@@ -1299,7 +1628,7 @@ async function grepFileTool(args) {
|
|
|
1299
1628
|
contextLines = 0,
|
|
1300
1629
|
outputMode = "files_with_matches"
|
|
1301
1630
|
} = args;
|
|
1302
|
-
const targetPath = searchPath ?
|
|
1631
|
+
const targetPath = searchPath ? resolve3(searchPath) : process.cwd();
|
|
1303
1632
|
let regex;
|
|
1304
1633
|
try {
|
|
1305
1634
|
regex = new RegExp(pattern);
|
|
@@ -1332,7 +1661,7 @@ async function grepFileTool(args) {
|
|
|
1332
1661
|
for (const filePath of files) {
|
|
1333
1662
|
let content;
|
|
1334
1663
|
try {
|
|
1335
|
-
const buffer = await
|
|
1664
|
+
const buffer = await readFile4(filePath);
|
|
1336
1665
|
const checkLen = Math.min(buffer.length, 8192);
|
|
1337
1666
|
let hasBinary = false;
|
|
1338
1667
|
for (let i = 0; i < checkLen; i++) {
|
|
@@ -1496,17 +1825,25 @@ var webSearchTool = createZodFunctionTool(
|
|
|
1496
1825
|
async (params) => runWebSearch(params)
|
|
1497
1826
|
);
|
|
1498
1827
|
export {
|
|
1828
|
+
E2BSandboxClient,
|
|
1499
1829
|
FunctionTool,
|
|
1830
|
+
InMemorySandboxClient,
|
|
1500
1831
|
OpenAPITool,
|
|
1501
1832
|
ToolRegistry,
|
|
1833
|
+
applyWorkspaceManifest,
|
|
1502
1834
|
bashTool,
|
|
1835
|
+
createBashTool,
|
|
1836
|
+
createEditTool,
|
|
1503
1837
|
createFunctionTool,
|
|
1504
1838
|
createOpenAPITool,
|
|
1839
|
+
createReadTool,
|
|
1840
|
+
createWriteTool,
|
|
1505
1841
|
createZodFunctionTool,
|
|
1506
1842
|
editTool,
|
|
1507
1843
|
globTool,
|
|
1508
1844
|
grepTool,
|
|
1509
1845
|
readTool,
|
|
1846
|
+
validateWorkspaceManifestPath,
|
|
1510
1847
|
webFetchTool,
|
|
1511
1848
|
webSearchTool,
|
|
1512
1849
|
writeTool,
|