@agent-api/sdk 1.1.5 → 1.2.1
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/CHANGELOG.md +13 -6
- package/README.md +21 -17
- package/dist/local/context.d.ts +4 -4
- package/dist/local/context.js +10 -10
- package/dist/local/core.d.ts +30 -30
- package/dist/local/core.js +12 -12
- package/dist/local/index.d.ts +1 -0
- package/dist/local/index.js +1 -0
- package/dist/local/shell.d.ts +82 -0
- package/dist/local/shell.js +318 -0
- package/dist/local/tools.d.ts +17 -17
- package/dist/local/tools.js +46 -46
- package/dist/node.d.ts +2 -2
- package/dist/node.js +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/dist-cjs/local/context.js +10 -10
- package/dist-cjs/local/core.js +15 -15
- package/dist-cjs/local/index.js +1 -0
- package/dist-cjs/local/shell.js +329 -0
- package/dist-cjs/local/tools.js +51 -51
- package/dist-cjs/node.js +11 -6
- package/dist-cjs/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export class HostLocalShellRunner {
|
|
4
|
+
cwd;
|
|
5
|
+
shell;
|
|
6
|
+
timeoutMs;
|
|
7
|
+
maxOutputBytes;
|
|
8
|
+
env;
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.cwd = path.resolve(options.cwd ?? process.cwd());
|
|
11
|
+
this.shell = options.shell ?? true;
|
|
12
|
+
this.timeoutMs = positiveInteger(options.timeoutMs, 2 * 60 * 1000, "timeoutMs");
|
|
13
|
+
this.maxOutputBytes = positiveInteger(options.maxOutputBytes, 128 * 1024, "maxOutputBytes");
|
|
14
|
+
this.env = { ...process.env, ...(options.env ?? {}) };
|
|
15
|
+
}
|
|
16
|
+
async run(request, context = {}) {
|
|
17
|
+
const command = requiredString(request.command, "command");
|
|
18
|
+
const cwd = resolveContainedPath(this.cwd, request.workdir);
|
|
19
|
+
const timeoutMs = request.timeoutMs == null
|
|
20
|
+
? this.timeoutMs
|
|
21
|
+
: positiveInteger(request.timeoutMs, this.timeoutMs, "timeoutMs");
|
|
22
|
+
const started = Date.now();
|
|
23
|
+
const stdout = new BoundedText(this.maxOutputBytes);
|
|
24
|
+
const stderr = new BoundedText(this.maxOutputBytes);
|
|
25
|
+
let timedOut = false;
|
|
26
|
+
return await new Promise((resolve, reject) => {
|
|
27
|
+
const child = spawn(command, [], {
|
|
28
|
+
cwd,
|
|
29
|
+
env: { ...this.env, ...(request.env ?? {}) },
|
|
30
|
+
shell: this.shell,
|
|
31
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
32
|
+
windowsHide: true,
|
|
33
|
+
});
|
|
34
|
+
let settled = false;
|
|
35
|
+
const finish = (exitCode, signal) => {
|
|
36
|
+
if (settled)
|
|
37
|
+
return;
|
|
38
|
+
settled = true;
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
context.signal?.removeEventListener("abort", abort);
|
|
41
|
+
const stdoutText = stdout.text();
|
|
42
|
+
const stderrText = stderr.text();
|
|
43
|
+
const output = [stdoutText, stderrText].filter(Boolean).join(stderrText ? "\n" : "");
|
|
44
|
+
resolve({
|
|
45
|
+
ok: true,
|
|
46
|
+
command,
|
|
47
|
+
description: request.description,
|
|
48
|
+
cwd,
|
|
49
|
+
exit_code: exitCode,
|
|
50
|
+
signal,
|
|
51
|
+
stdout: stdoutText,
|
|
52
|
+
stderr: stderrText,
|
|
53
|
+
output: output || "(no output)",
|
|
54
|
+
duration_ms: Date.now() - started,
|
|
55
|
+
timed_out: timedOut,
|
|
56
|
+
truncated: stdout.truncated || stderr.truncated,
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
const kill = () => {
|
|
60
|
+
if (child.killed)
|
|
61
|
+
return;
|
|
62
|
+
child.kill(process.platform === "win32" ? undefined : "SIGTERM");
|
|
63
|
+
setTimeout(() => {
|
|
64
|
+
if (!child.killed)
|
|
65
|
+
child.kill(process.platform === "win32" ? undefined : "SIGKILL");
|
|
66
|
+
}, 3000).unref();
|
|
67
|
+
};
|
|
68
|
+
const abort = () => {
|
|
69
|
+
kill();
|
|
70
|
+
};
|
|
71
|
+
const timer = setTimeout(() => {
|
|
72
|
+
timedOut = true;
|
|
73
|
+
kill();
|
|
74
|
+
}, timeoutMs);
|
|
75
|
+
timer.unref();
|
|
76
|
+
if (context.signal?.aborted) {
|
|
77
|
+
abort();
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
context.signal?.addEventListener("abort", abort, { once: true });
|
|
81
|
+
}
|
|
82
|
+
child.stdout?.on("data", (chunk) => stdout.append(chunk));
|
|
83
|
+
child.stderr?.on("data", (chunk) => stderr.append(chunk));
|
|
84
|
+
child.on("error", (error) => {
|
|
85
|
+
if (settled)
|
|
86
|
+
return;
|
|
87
|
+
settled = true;
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
context.signal?.removeEventListener("abort", abort);
|
|
90
|
+
reject(error);
|
|
91
|
+
});
|
|
92
|
+
child.on("close", finish);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export class LocalShellDriver {
|
|
97
|
+
accessMode;
|
|
98
|
+
runner;
|
|
99
|
+
constructor(options = {}) {
|
|
100
|
+
const cwd = options.workdir?.root ?? options.cwd;
|
|
101
|
+
this.accessMode = options.accessMode ?? "approval";
|
|
102
|
+
this.runner = options.runner ?? new HostLocalShellRunner({
|
|
103
|
+
cwd,
|
|
104
|
+
shell: options.shell,
|
|
105
|
+
timeoutMs: options.timeoutMs,
|
|
106
|
+
maxOutputBytes: options.maxOutputBytes,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async dispatch(args, context = {}) {
|
|
110
|
+
const request = shellRequest(args);
|
|
111
|
+
if (this.accessMode !== "full") {
|
|
112
|
+
return shellApprovalRequired(request);
|
|
113
|
+
}
|
|
114
|
+
return shellToolResult(await this.runner.run(request, context));
|
|
115
|
+
}
|
|
116
|
+
requiresApproval() {
|
|
117
|
+
return this.accessMode !== "full";
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
export function createLocalShellToolRegistry(options = {}) {
|
|
121
|
+
const toolName = options.toolName ?? "local_shell";
|
|
122
|
+
const driver = new LocalShellDriver(options);
|
|
123
|
+
const hostRunner = driver.runner instanceof HostLocalShellRunner ? driver.runner : undefined;
|
|
124
|
+
const definition = localShellToolDefinition(toolName, {
|
|
125
|
+
accessMode: driver.accessMode,
|
|
126
|
+
cwd: options.workdir?.root ?? hostRunner?.cwd ?? options.cwd,
|
|
127
|
+
shell: hostRunner?.shell ?? options.shell,
|
|
128
|
+
timeoutMs: hostRunner?.timeoutMs ?? options.timeoutMs,
|
|
129
|
+
maxOutputBytes: hostRunner?.maxOutputBytes ?? options.maxOutputBytes,
|
|
130
|
+
});
|
|
131
|
+
return {
|
|
132
|
+
accessMode: driver.accessMode,
|
|
133
|
+
toolName,
|
|
134
|
+
definitions: () => [{ ...definition }],
|
|
135
|
+
handlers: () => ({ [toolName]: (args) => driver.dispatch(args) }),
|
|
136
|
+
execute: async (name, args, context = {}) => {
|
|
137
|
+
if (name !== toolName) {
|
|
138
|
+
throw new Error(`unknown local shell tool: ${name}`);
|
|
139
|
+
}
|
|
140
|
+
return await driver.dispatch(args, context);
|
|
141
|
+
},
|
|
142
|
+
requiresApproval: (name) => name === toolName && driver.requiresApproval(),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
export function localShellToolDefinition(name = "local_shell", options = {}) {
|
|
146
|
+
return {
|
|
147
|
+
type: "function",
|
|
148
|
+
name,
|
|
149
|
+
description: localShellToolDescription(options),
|
|
150
|
+
parameters: localShellToolParameters(),
|
|
151
|
+
strict: false,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
export function localShellToolInstructions(options = {}) {
|
|
155
|
+
return localShellToolDescription(options);
|
|
156
|
+
}
|
|
157
|
+
function localShellToolDescription(options = {}) {
|
|
158
|
+
const platform = options.platform ?? process.platform;
|
|
159
|
+
const shell = shellDisplayName(options.shell);
|
|
160
|
+
const accessMode = options.accessMode ?? "approval";
|
|
161
|
+
const cwd = options.cwd ? path.resolve(options.cwd) : undefined;
|
|
162
|
+
const timeoutMs = options.timeoutMs ?? 2 * 60 * 1000;
|
|
163
|
+
const maxOutputBytes = options.maxOutputBytes ?? 128 * 1024;
|
|
164
|
+
return [
|
|
165
|
+
"Run a local shell command through one model-facing primitive.",
|
|
166
|
+
"Prefer local_workdir for file discovery, reading, and editing. Use local_shell for package managers, tests, build commands, git, and other process-level tasks.",
|
|
167
|
+
`Execution environment: platform=${platform}; shell=${shell}; access_mode=${accessMode}; default_timeout_ms=${timeoutMs}; max_output_bytes=${maxOutputBytes}.`,
|
|
168
|
+
cwd ? `Default cwd: ${cwd}. Relative command paths resolve from this cwd unless workdir is set.` : "Relative command paths resolve from the configured cwd unless workdir is set.",
|
|
169
|
+
"The workdir parameter must be a relative child path of the configured cwd. Use workdir instead of cd when possible.",
|
|
170
|
+
"Absolute paths inside the command are permitted by the host OS if the user/process has permission; this tool is not a filesystem sandbox or security sandbox.",
|
|
171
|
+
"Captured stdout/stderr may be truncated when output exceeds the advertised max_output_bytes.",
|
|
172
|
+
"In approval mode, calls return requires_approval instead of executing. In full mode, commands execute immediately.",
|
|
173
|
+
shellGuidance(platform, options.shell),
|
|
174
|
+
].filter(Boolean).join(" ");
|
|
175
|
+
}
|
|
176
|
+
function localShellToolParameters() {
|
|
177
|
+
return {
|
|
178
|
+
type: "object",
|
|
179
|
+
properties: {
|
|
180
|
+
command: {
|
|
181
|
+
type: "string",
|
|
182
|
+
description: "Shell command to execute. Keep it focused and quote paths containing spaces.",
|
|
183
|
+
},
|
|
184
|
+
description: {
|
|
185
|
+
type: "string",
|
|
186
|
+
description: "Short human-readable description of why this command is being run.",
|
|
187
|
+
},
|
|
188
|
+
workdir: {
|
|
189
|
+
type: "string",
|
|
190
|
+
description: "Optional relative working directory. Use this instead of cd. Absolute paths are rejected by the default host runner.",
|
|
191
|
+
},
|
|
192
|
+
timeout_ms: {
|
|
193
|
+
type: "integer",
|
|
194
|
+
description: "Optional timeout in milliseconds.",
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
required: ["command"],
|
|
198
|
+
additionalProperties: false,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function shellRequest(args) {
|
|
202
|
+
return {
|
|
203
|
+
command: requiredString(args.command, "command"),
|
|
204
|
+
description: optionalString(args.description, "description"),
|
|
205
|
+
workdir: optionalString(args.workdir, "workdir"),
|
|
206
|
+
timeoutMs: optionalNumber(args.timeoutMs ?? args.timeout_ms, "timeout_ms"),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function shellApprovalRequired(request) {
|
|
210
|
+
return {
|
|
211
|
+
ok: false,
|
|
212
|
+
requires_approval: true,
|
|
213
|
+
action: "run",
|
|
214
|
+
command: request.command,
|
|
215
|
+
description: request.description,
|
|
216
|
+
workdir: request.workdir,
|
|
217
|
+
timeout_ms: request.timeoutMs,
|
|
218
|
+
message: "local_shell command execution requires approval",
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function shellToolResult(result) {
|
|
222
|
+
return {
|
|
223
|
+
...result,
|
|
224
|
+
action: "run",
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function resolveContainedPath(root, child) {
|
|
228
|
+
if (!child)
|
|
229
|
+
return root;
|
|
230
|
+
if (path.isAbsolute(child)) {
|
|
231
|
+
throw new Error("workdir must be relative to the configured local shell cwd");
|
|
232
|
+
}
|
|
233
|
+
const resolved = path.resolve(root, child);
|
|
234
|
+
const relative = path.relative(root, resolved);
|
|
235
|
+
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
236
|
+
throw new Error("workdir must stay inside the configured local shell cwd");
|
|
237
|
+
}
|
|
238
|
+
return resolved;
|
|
239
|
+
}
|
|
240
|
+
function requiredString(value, name) {
|
|
241
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
242
|
+
throw new Error(`${name} must be a non-empty string`);
|
|
243
|
+
}
|
|
244
|
+
return value;
|
|
245
|
+
}
|
|
246
|
+
function optionalString(value, name) {
|
|
247
|
+
if (value == null || value === "")
|
|
248
|
+
return undefined;
|
|
249
|
+
if (typeof value !== "string") {
|
|
250
|
+
throw new Error(`${name} must be a string`);
|
|
251
|
+
}
|
|
252
|
+
return value;
|
|
253
|
+
}
|
|
254
|
+
function optionalNumber(value, name) {
|
|
255
|
+
if (value == null)
|
|
256
|
+
return undefined;
|
|
257
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
258
|
+
throw new Error(`${name} must be a number`);
|
|
259
|
+
}
|
|
260
|
+
return Math.trunc(value);
|
|
261
|
+
}
|
|
262
|
+
function positiveInteger(value, fallback, name) {
|
|
263
|
+
if (value == null)
|
|
264
|
+
return fallback;
|
|
265
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
266
|
+
throw new Error(`${name} must be a positive number`);
|
|
267
|
+
}
|
|
268
|
+
return Math.trunc(value);
|
|
269
|
+
}
|
|
270
|
+
function shellDisplayName(shell) {
|
|
271
|
+
if (typeof shell === "string" && shell.trim())
|
|
272
|
+
return shell;
|
|
273
|
+
if (shell === false)
|
|
274
|
+
return "direct process execution";
|
|
275
|
+
if (process.platform === "win32")
|
|
276
|
+
return "cmd.exe";
|
|
277
|
+
return "/bin/sh";
|
|
278
|
+
}
|
|
279
|
+
function shellGuidance(platform, shell) {
|
|
280
|
+
const name = typeof shell === "string" ? path.basename(shell).toLowerCase() : shellDisplayName(shell).toLowerCase();
|
|
281
|
+
if (platform === "win32" || name.includes("powershell") || name === "pwsh") {
|
|
282
|
+
return "On Windows/PowerShell, prefer PowerShell-compatible commands and quote paths with spaces.";
|
|
283
|
+
}
|
|
284
|
+
if (name.includes("cmd.exe") || name === "cmd") {
|
|
285
|
+
return "On cmd.exe, prefer cmd-compatible syntax and use && only for dependent command chaining.";
|
|
286
|
+
}
|
|
287
|
+
return "On POSIX shells, prefer portable shell syntax, quote paths with spaces, and use && only when later commands depend on earlier success.";
|
|
288
|
+
}
|
|
289
|
+
class BoundedText {
|
|
290
|
+
maxBytes;
|
|
291
|
+
chunks = [];
|
|
292
|
+
size = 0;
|
|
293
|
+
truncated = false;
|
|
294
|
+
constructor(maxBytes) {
|
|
295
|
+
this.maxBytes = maxBytes;
|
|
296
|
+
}
|
|
297
|
+
append(chunk) {
|
|
298
|
+
this.chunks.push(chunk);
|
|
299
|
+
this.size += chunk.byteLength;
|
|
300
|
+
while (this.size > this.maxBytes && this.chunks.length > 1) {
|
|
301
|
+
const removed = this.chunks.shift();
|
|
302
|
+
if (!removed)
|
|
303
|
+
break;
|
|
304
|
+
this.size -= removed.byteLength;
|
|
305
|
+
this.truncated = true;
|
|
306
|
+
}
|
|
307
|
+
if (this.size > this.maxBytes && this.chunks.length === 1) {
|
|
308
|
+
const current = this.chunks[0];
|
|
309
|
+
this.chunks[0] = current.subarray(current.byteLength - this.maxBytes);
|
|
310
|
+
this.size = this.chunks[0].byteLength;
|
|
311
|
+
this.truncated = true;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
text() {
|
|
315
|
+
const value = Buffer.concat(this.chunks, this.size).toString("utf8");
|
|
316
|
+
return this.truncated ? `...output truncated...\n${value}` : value;
|
|
317
|
+
}
|
|
318
|
+
}
|
package/dist/local/tools.d.ts
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import type { FunctionTool } from "../types/tools.js";
|
|
2
|
-
import type {
|
|
3
|
-
export type
|
|
4
|
-
export type
|
|
5
|
-
export interface
|
|
6
|
-
accessMode?:
|
|
2
|
+
import type { LocalWorkdir } from "./core.js";
|
|
3
|
+
export type LocalWorkdirAccessMode = "approval" | "full";
|
|
4
|
+
export type LocalWorkdirAction = "summarize" | "list" | "search" | "grep" | "read" | "read_lines" | "context" | "snapshot" | "classify_path" | "preview_edits" | "apply_edits" | "write" | "mkdir" | "delete";
|
|
5
|
+
export interface LocalWorkdirToolRegistryOptions {
|
|
6
|
+
accessMode?: LocalWorkdirAccessMode;
|
|
7
7
|
toolName?: string;
|
|
8
8
|
}
|
|
9
|
-
export interface
|
|
10
|
-
readonly
|
|
11
|
-
readonly accessMode:
|
|
9
|
+
export interface LocalWorkdirToolRegistry {
|
|
10
|
+
readonly workdir: LocalWorkdir;
|
|
11
|
+
readonly accessMode: LocalWorkdirAccessMode;
|
|
12
12
|
readonly toolName: string;
|
|
13
13
|
definitions(): FunctionTool[];
|
|
14
14
|
handlers(): Record<string, (args: Record<string, unknown>) => Promise<Record<string, unknown>>>;
|
|
15
15
|
execute(name: string, args: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
16
16
|
requiresApproval(name: string, args?: Record<string, unknown>): boolean;
|
|
17
17
|
}
|
|
18
|
-
export interface
|
|
19
|
-
accessMode?:
|
|
18
|
+
export interface LocalWorkdirDriverOptions {
|
|
19
|
+
accessMode?: LocalWorkdirAccessMode;
|
|
20
20
|
}
|
|
21
|
-
export declare class
|
|
22
|
-
readonly
|
|
23
|
-
readonly accessMode:
|
|
24
|
-
constructor(
|
|
21
|
+
export declare class LocalWorkdirDriver {
|
|
22
|
+
readonly workdir: LocalWorkdir;
|
|
23
|
+
readonly accessMode: LocalWorkdirAccessMode;
|
|
24
|
+
constructor(workdir: LocalWorkdir, options?: LocalWorkdirDriverOptions);
|
|
25
25
|
dispatch(args: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
26
26
|
requiresApproval(args: Record<string, unknown>): boolean;
|
|
27
27
|
private dispatchApplyEdits;
|
|
@@ -29,6 +29,6 @@ export declare class LocalWorkspaceDriver {
|
|
|
29
29
|
private dispatchMkdir;
|
|
30
30
|
private dispatchDelete;
|
|
31
31
|
}
|
|
32
|
-
export declare function
|
|
33
|
-
export declare function
|
|
34
|
-
export declare function
|
|
32
|
+
export declare function createLocalWorkdirToolRegistry(workdir: LocalWorkdir, options?: LocalWorkdirToolRegistryOptions): LocalWorkdirToolRegistry;
|
|
33
|
+
export declare function localWorkdirToolDefinition(name?: string): FunctionTool;
|
|
34
|
+
export declare function localWorkdirToolInstructions(): string;
|
package/dist/local/tools.js
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
import { createLocalContextPackage } from "./context.js";
|
|
2
|
-
export class
|
|
3
|
-
|
|
2
|
+
export class LocalWorkdirDriver {
|
|
3
|
+
workdir;
|
|
4
4
|
accessMode;
|
|
5
|
-
constructor(
|
|
6
|
-
this.
|
|
5
|
+
constructor(workdir, options = {}) {
|
|
6
|
+
this.workdir = workdir;
|
|
7
7
|
this.accessMode = options.accessMode ?? "approval";
|
|
8
8
|
}
|
|
9
9
|
async dispatch(args) {
|
|
10
|
-
const action =
|
|
10
|
+
const action = workdirAction(args);
|
|
11
11
|
switch (action) {
|
|
12
12
|
case "summarize":
|
|
13
|
-
return localToolResult(action, localSummaryResult(await this.
|
|
13
|
+
return localToolResult(action, localSummaryResult(await this.workdir.summarize(summaryArgs(args))));
|
|
14
14
|
case "list":
|
|
15
|
-
return localToolResult(action, await this.
|
|
15
|
+
return localToolResult(action, await this.workdir.listEntries(optionalStringArg(args, "path") ?? ".", listArgs(args)));
|
|
16
16
|
case "search":
|
|
17
|
-
return localToolResult(action, await this.
|
|
17
|
+
return localToolResult(action, await this.workdir.searchEntries(searchEntriesArgs(args)));
|
|
18
18
|
case "grep":
|
|
19
|
-
return localToolResult(action, await this.
|
|
19
|
+
return localToolResult(action, await this.workdir.grep(grepArgs(args)));
|
|
20
20
|
case "read":
|
|
21
|
-
return localToolResult(action, await this.
|
|
21
|
+
return localToolResult(action, await this.workdir.readFile(stringArg(args, "path"), readFileArgs(args)));
|
|
22
22
|
case "read_lines":
|
|
23
|
-
return localToolResult(action, await this.
|
|
23
|
+
return localToolResult(action, await this.workdir.readLines(stringArg(args, "path"), readLinesArgs(args)));
|
|
24
24
|
case "context":
|
|
25
|
-
return localToolResult(action, await createLocalContextPackage(this.
|
|
25
|
+
return localToolResult(action, await createLocalContextPackage(this.workdir, contextPackageArgs(args)));
|
|
26
26
|
case "snapshot":
|
|
27
|
-
return localToolResult(action, await this.
|
|
27
|
+
return localToolResult(action, await this.workdir.snapshot(snapshotArgs(args)));
|
|
28
28
|
case "classify_path":
|
|
29
|
-
return localToolResult(action, this.
|
|
29
|
+
return localToolResult(action, this.workdir.classifyPath(stringArg(args, "path")));
|
|
30
30
|
case "preview_edits":
|
|
31
|
-
return localToolResult(action, await this.
|
|
31
|
+
return localToolResult(action, await this.workdir.previewEdits(editsArg(args)));
|
|
32
32
|
case "apply_edits":
|
|
33
33
|
return await this.dispatchApplyEdits(args);
|
|
34
34
|
case "write":
|
|
@@ -38,73 +38,73 @@ export class LocalWorkspaceDriver {
|
|
|
38
38
|
case "delete":
|
|
39
39
|
return await this.dispatchDelete(args);
|
|
40
40
|
default:
|
|
41
|
-
throw new Error(`unsupported
|
|
41
|
+
throw new Error(`unsupported local_workdir action: ${action}`);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
requiresApproval(args) {
|
|
45
45
|
if (this.accessMode === "full") {
|
|
46
46
|
return false;
|
|
47
47
|
}
|
|
48
|
-
return
|
|
48
|
+
return mutatingLocalWorkdirActions.has(workdirAction(args));
|
|
49
49
|
}
|
|
50
50
|
async dispatchApplyEdits(args) {
|
|
51
51
|
const edits = editsArg(args);
|
|
52
52
|
if (this.accessMode !== "full") {
|
|
53
|
-
return approvalRequired("apply_edits", args, await this.
|
|
53
|
+
return approvalRequired("apply_edits", args, await this.workdir.previewEdits(edits));
|
|
54
54
|
}
|
|
55
|
-
return localToolResult("apply_edits", await this.
|
|
55
|
+
return localToolResult("apply_edits", await this.workdir.applyEdits(edits));
|
|
56
56
|
}
|
|
57
57
|
async dispatchWrite(args) {
|
|
58
58
|
if (this.accessMode !== "full") {
|
|
59
59
|
return approvalRequired("write", args);
|
|
60
60
|
}
|
|
61
|
-
return localToolResult("write", await this.
|
|
61
|
+
return localToolResult("write", await this.workdir.writeText(stringArg(args, "path"), stringArg(args, "content")));
|
|
62
62
|
}
|
|
63
63
|
async dispatchMkdir(args) {
|
|
64
64
|
if (this.accessMode !== "full") {
|
|
65
65
|
return approvalRequired("mkdir", args);
|
|
66
66
|
}
|
|
67
|
-
return localToolResult("mkdir", await this.
|
|
67
|
+
return localToolResult("mkdir", await this.workdir.createDirectory(stringArg(args, "path")));
|
|
68
68
|
}
|
|
69
69
|
async dispatchDelete(args) {
|
|
70
70
|
if (this.accessMode !== "full") {
|
|
71
71
|
return approvalRequired("delete", args);
|
|
72
72
|
}
|
|
73
|
-
return localToolResult("delete", await this.
|
|
73
|
+
return localToolResult("delete", await this.workdir.deletePath(stringArg(args, "path")));
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
-
export function
|
|
77
|
-
const toolName = options.toolName ?? "
|
|
78
|
-
const driver = new
|
|
79
|
-
const definition =
|
|
76
|
+
export function createLocalWorkdirToolRegistry(workdir, options = {}) {
|
|
77
|
+
const toolName = options.toolName ?? "local_workdir";
|
|
78
|
+
const driver = new LocalWorkdirDriver(workdir, { accessMode: options.accessMode });
|
|
79
|
+
const definition = localWorkdirToolDefinition(toolName);
|
|
80
80
|
return {
|
|
81
|
-
|
|
81
|
+
workdir,
|
|
82
82
|
accessMode: driver.accessMode,
|
|
83
83
|
toolName,
|
|
84
84
|
definitions: () => [{ ...definition }],
|
|
85
85
|
handlers: () => ({ [toolName]: (args) => driver.dispatch(args) }),
|
|
86
86
|
execute: async (name, args) => {
|
|
87
87
|
if (name !== toolName) {
|
|
88
|
-
throw new Error(`unknown local
|
|
88
|
+
throw new Error(`unknown local workdir tool: ${name}`);
|
|
89
89
|
}
|
|
90
90
|
return await driver.dispatch(args);
|
|
91
91
|
},
|
|
92
92
|
requiresApproval: (name, args = {}) => name === toolName && driver.requiresApproval(args),
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
|
-
export function
|
|
95
|
+
export function localWorkdirToolDefinition(name = "local_workdir") {
|
|
96
96
|
return {
|
|
97
97
|
type: "function",
|
|
98
98
|
name,
|
|
99
|
-
description:
|
|
100
|
-
parameters:
|
|
99
|
+
description: localWorkdirToolDescription,
|
|
100
|
+
parameters: localWorkdirToolParameters(),
|
|
101
101
|
strict: false,
|
|
102
102
|
};
|
|
103
103
|
}
|
|
104
|
-
export function
|
|
105
|
-
return
|
|
104
|
+
export function localWorkdirToolInstructions() {
|
|
105
|
+
return localWorkdirToolDescription;
|
|
106
106
|
}
|
|
107
|
-
const
|
|
107
|
+
const localWorkdirActions = [
|
|
108
108
|
"summarize",
|
|
109
109
|
"list",
|
|
110
110
|
"search",
|
|
@@ -120,24 +120,24 @@ const localWorkspaceActions = [
|
|
|
120
120
|
"mkdir",
|
|
121
121
|
"delete",
|
|
122
122
|
];
|
|
123
|
-
const
|
|
123
|
+
const mutatingLocalWorkdirActions = new Set([
|
|
124
124
|
"apply_edits",
|
|
125
125
|
"write",
|
|
126
126
|
"mkdir",
|
|
127
127
|
"delete",
|
|
128
128
|
]);
|
|
129
|
-
const
|
|
130
|
-
"Inspect and modify the selected local
|
|
129
|
+
const localWorkdirToolDescription = [
|
|
130
|
+
"Inspect and modify the selected local workdir through one model-facing primitive.",
|
|
131
131
|
"Use action=list/search/grep/summarize/context to discover files, read/read_lines for file content, preview_edits before edits, and apply_edits/write/mkdir/delete only when mutation is intended.",
|
|
132
132
|
"In approval mode, mutating actions return requires_approval with a safe preview instead of changing files. In full mode, mutating actions execute immediately.",
|
|
133
|
-
"Paths are relative to the selected local
|
|
133
|
+
"Paths are relative to the selected local workdir; never use absolute paths.",
|
|
134
134
|
].join(" ");
|
|
135
|
-
function
|
|
135
|
+
function localWorkdirToolParameters() {
|
|
136
136
|
return objectSchema({
|
|
137
137
|
action: {
|
|
138
138
|
type: "string",
|
|
139
|
-
enum:
|
|
140
|
-
description: "
|
|
139
|
+
enum: localWorkdirActions,
|
|
140
|
+
description: "Workdir operation. Prefer summarize/list/search/grep before reading or editing. Prefer read_lines and apply_edits for source changes.",
|
|
141
141
|
},
|
|
142
142
|
path: stringSchema("Relative path. File path for read/write/delete/edit actions; directory base for list/search/grep/summarize/context/snapshot."),
|
|
143
143
|
query: stringSchema("Path/name query for search, or optional context query."),
|
|
@@ -168,17 +168,17 @@ function localWorkspaceToolParameters() {
|
|
|
168
168
|
max_bytes_per_file: integerSchema("Maximum bytes per file."),
|
|
169
169
|
max_previews: integerSchema("Maximum summary previews."),
|
|
170
170
|
include_content: booleanSchema("Include file contents in context packages."),
|
|
171
|
-
include_summary: booleanSchema("Include
|
|
171
|
+
include_summary: booleanSchema("Include workdir summary in context packages."),
|
|
172
172
|
include_search: booleanSchema("Include grep results in context packages when query is set."),
|
|
173
173
|
include_secrets: booleanSchema("Include likely secret file contents in context packages."),
|
|
174
174
|
hash: booleanSchema("Include SHA-256 hashes in snapshots."),
|
|
175
175
|
}),
|
|
176
176
|
}, ["action"]);
|
|
177
177
|
}
|
|
178
|
-
function
|
|
178
|
+
function workdirAction(args) {
|
|
179
179
|
const value = stringArg(args, "action").trim().toLowerCase();
|
|
180
|
-
if (!
|
|
181
|
-
throw new Error(`unsupported
|
|
180
|
+
if (!localWorkdirActions.includes(value)) {
|
|
181
|
+
throw new Error(`unsupported local_workdir action: ${value}`);
|
|
182
182
|
}
|
|
183
183
|
return value;
|
|
184
184
|
}
|
|
@@ -345,7 +345,7 @@ function approvalRequired(action, args, preview) {
|
|
|
345
345
|
requires_approval: true,
|
|
346
346
|
arguments: args,
|
|
347
347
|
preview,
|
|
348
|
-
message: `
|
|
348
|
+
message: `local_workdir action ${action} requires approval`,
|
|
349
349
|
};
|
|
350
350
|
}
|
|
351
351
|
function objectSchema(properties, required = []) {
|
package/dist/node.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export * from "./index.js";
|
|
2
2
|
export { NodeAgentAPI } from "./node-client.js";
|
|
3
3
|
export { NodeSkillsResource } from "./resources/skills-node.js";
|
|
4
|
-
export {
|
|
5
|
-
export type {
|
|
4
|
+
export { createLocalShellToolRegistry, LocalWorkdirDriver, HostLocalShellRunner, LocalShellDriver, localShellToolDefinition, localShellToolInstructions, createLocalWorkdirToolRegistry, localWorkdirToolDefinition, localWorkdirToolInstructions, } from "./local/index.js";
|
|
5
|
+
export type { LocalCommandRunner, LocalShellAccessMode, LocalShellContext, LocalShellRequest, LocalShellResult, LocalShellToolRegistry, LocalShellToolRegistryOptions, LocalWorkdirAction, LocalWorkdirAccessMode, LocalWorkdirToolRegistry, LocalWorkdirToolRegistryOptions, } from "./local/index.js";
|
|
6
6
|
export { localSkillFromDirectory, pendingLocalSkillCalls, runLocalSkillHandlers } from "./local-skills.js";
|
|
7
7
|
export type { LocalSkillDirectoryOptions } from "./local-skills.js";
|
|
8
8
|
export * from "./local/index.js";
|
package/dist/node.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from "./index.js";
|
|
2
2
|
export { NodeAgentAPI } from "./node-client.js";
|
|
3
3
|
export { NodeSkillsResource } from "./resources/skills-node.js";
|
|
4
|
-
export {
|
|
4
|
+
export { createLocalShellToolRegistry, LocalWorkdirDriver, HostLocalShellRunner, LocalShellDriver, localShellToolDefinition, localShellToolInstructions, createLocalWorkdirToolRegistry, localWorkdirToolDefinition, localWorkdirToolInstructions, } from "./local/index.js";
|
|
5
5
|
export { localSkillFromDirectory, pendingLocalSkillCalls, runLocalSkillHandlers } from "./local-skills.js";
|
|
6
6
|
export * from "./local/index.js";
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "1.1
|
|
2
|
-
export declare const USER_AGENT = "@agent-api/sdk/1.1
|
|
1
|
+
export declare const VERSION = "1.2.1";
|
|
2
|
+
export declare const USER_AGENT = "@agent-api/sdk/1.2.1";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const VERSION = "1.1
|
|
1
|
+
export const VERSION = "1.2.1";
|
|
2
2
|
export const USER_AGENT = `@agent-api/sdk/${VERSION}`;
|