@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
|
@@ -5,7 +5,7 @@ exports.summarizeLocalContextSensitivity = summarizeLocalContextSensitivity;
|
|
|
5
5
|
const node_crypto_1 = require("node:crypto");
|
|
6
6
|
const promises_1 = require("node:fs/promises");
|
|
7
7
|
const core_js_1 = require("./core.js");
|
|
8
|
-
async function createLocalContextPackage(
|
|
8
|
+
async function createLocalContextPackage(workdir, params = {}) {
|
|
9
9
|
const basePath = params.path ?? ".";
|
|
10
10
|
const maxFiles = positiveInt(params.maxFiles, 80);
|
|
11
11
|
const maxBytes = positiveInt(params.maxBytes, 256 * 1024);
|
|
@@ -16,7 +16,7 @@ async function createLocalContextPackage(workspace, params = {}) {
|
|
|
16
16
|
const includeSummary = params.includeSummary ?? true;
|
|
17
17
|
const includeSearch = Boolean(params.includeSearch && params.query?.trim());
|
|
18
18
|
const includeSecrets = params.includeSecrets ?? false;
|
|
19
|
-
const stats = await
|
|
19
|
+
const stats = await workdir.list(basePath, { recursive: true });
|
|
20
20
|
const fileStats = stats
|
|
21
21
|
.filter((item) => item.type === "file")
|
|
22
22
|
.filter((item) => matchesPathFilters(item.path, params.include, params.exclude))
|
|
@@ -53,7 +53,7 @@ async function createLocalContextPackage(workspace, params = {}) {
|
|
|
53
53
|
truncated = true;
|
|
54
54
|
continue;
|
|
55
55
|
}
|
|
56
|
-
const delivered = await
|
|
56
|
+
const delivered = await workdir.readFile(item.path, { maxBytes: readBudget });
|
|
57
57
|
includedBytes += delivered.encoding === "text"
|
|
58
58
|
? Buffer.byteLength(delivered.content ?? "", "utf8")
|
|
59
59
|
: Buffer.byteLength(delivered.content_base64 ?? "", "base64");
|
|
@@ -68,7 +68,7 @@ async function createLocalContextPackage(workspace, params = {}) {
|
|
|
68
68
|
packaged.content_base64 = delivered.content_base64;
|
|
69
69
|
}
|
|
70
70
|
if (includeHashes) {
|
|
71
|
-
packaged.sha256 = await sha256File(
|
|
71
|
+
packaged.sha256 = await sha256File(workdir, item.path);
|
|
72
72
|
}
|
|
73
73
|
if (delivered.truncated) {
|
|
74
74
|
truncated = true;
|
|
@@ -76,7 +76,7 @@ async function createLocalContextPackage(workspace, params = {}) {
|
|
|
76
76
|
files.push(packaged);
|
|
77
77
|
}
|
|
78
78
|
const summary = includeSummary
|
|
79
|
-
? await
|
|
79
|
+
? await workdir.summarize({
|
|
80
80
|
path: basePath,
|
|
81
81
|
maxFiles,
|
|
82
82
|
previewBytes,
|
|
@@ -84,7 +84,7 @@ async function createLocalContextPackage(workspace, params = {}) {
|
|
|
84
84
|
})
|
|
85
85
|
: undefined;
|
|
86
86
|
const search = includeSearch
|
|
87
|
-
? await
|
|
87
|
+
? await workdir.grep({
|
|
88
88
|
pattern: params.query,
|
|
89
89
|
path: basePath,
|
|
90
90
|
limit: maxFiles,
|
|
@@ -94,8 +94,8 @@ async function createLocalContextPackage(workspace, params = {}) {
|
|
|
94
94
|
: undefined;
|
|
95
95
|
return {
|
|
96
96
|
object: "local_context_manifest",
|
|
97
|
-
root:
|
|
98
|
-
|
|
97
|
+
root: workdir.root,
|
|
98
|
+
workdir_name: workdir.name,
|
|
99
99
|
generated_at_unix: Math.floor(Date.now() / 1000),
|
|
100
100
|
base_path: basePath,
|
|
101
101
|
file_count: fileStats.length,
|
|
@@ -152,7 +152,7 @@ function positiveInt(value, fallback) {
|
|
|
152
152
|
const parsed = Number(value);
|
|
153
153
|
return Number.isFinite(parsed) && parsed > 0 ? Math.trunc(parsed) : fallback;
|
|
154
154
|
}
|
|
155
|
-
async function sha256File(
|
|
156
|
-
const fullPath =
|
|
155
|
+
async function sha256File(workdir, relativePath) {
|
|
156
|
+
const fullPath = workdir.files.resolvePath(relativePath);
|
|
157
157
|
return (0, node_crypto_1.createHash)("sha256").update(await (0, promises_1.readFile)(fullPath)).digest("hex");
|
|
158
158
|
}
|
package/dist-cjs/local/core.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.LocalSkillStore = exports.
|
|
6
|
+
exports.LocalSkillStore = exports.LocalWorkdir = exports.LocalWorkdirManager = exports.LocalConfigStore = exports.LocalFileStore = exports.LocalConfigError = exports.LocalNotTextFileError = exports.LocalFileTooLargeError = exports.LocalIgnoredPathError = exports.LocalPathError = exports.LocalError = void 0;
|
|
7
7
|
exports.createLocalRuntime = createLocalRuntime;
|
|
8
8
|
exports.localAppDirs = localAppDirs;
|
|
9
9
|
exports.classifyLocalPathSensitivity = classifyLocalPathSensitivity;
|
|
@@ -33,7 +33,7 @@ class LocalPathError extends LocalError {
|
|
|
33
33
|
exports.LocalPathError = LocalPathError;
|
|
34
34
|
class LocalIgnoredPathError extends LocalError {
|
|
35
35
|
constructor(path) {
|
|
36
|
-
super("local_ignored_path", `local
|
|
36
|
+
super("local_ignored_path", `local workdir path is ignored: ${path}`, { path });
|
|
37
37
|
this.name = "LocalIgnoredPathError";
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -77,9 +77,9 @@ function createLocalRuntime(options) {
|
|
|
77
77
|
temp,
|
|
78
78
|
config: new LocalConfigStore(configFiles),
|
|
79
79
|
skills: new LocalSkillStore(data.child("skills")),
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return new
|
|
80
|
+
workdirs: new LocalWorkdirManager(),
|
|
81
|
+
workdir(root, workdirOptions = {}) {
|
|
82
|
+
return new LocalWorkdir(root, workdirOptions);
|
|
83
83
|
},
|
|
84
84
|
async ensure() {
|
|
85
85
|
await Promise.all([data.ensure(), cache.ensure(), logs.ensure(), temp.ensure(), configFiles.ensure()]);
|
|
@@ -473,13 +473,13 @@ class LocalConfigStore {
|
|
|
473
473
|
}
|
|
474
474
|
}
|
|
475
475
|
exports.LocalConfigStore = LocalConfigStore;
|
|
476
|
-
class
|
|
476
|
+
class LocalWorkdirManager {
|
|
477
477
|
open(root, options = {}) {
|
|
478
|
-
return new
|
|
478
|
+
return new LocalWorkdir(root, options);
|
|
479
479
|
}
|
|
480
480
|
}
|
|
481
|
-
exports.
|
|
482
|
-
class
|
|
481
|
+
exports.LocalWorkdirManager = LocalWorkdirManager;
|
|
482
|
+
class LocalWorkdir {
|
|
483
483
|
root;
|
|
484
484
|
name;
|
|
485
485
|
metadata;
|
|
@@ -490,11 +490,11 @@ class LocalWorkspace {
|
|
|
490
490
|
maxFileBytes;
|
|
491
491
|
constructor(root, options = {}) {
|
|
492
492
|
this.root = node_path_1.default.resolve(root);
|
|
493
|
-
this.name = options.name?.trim() || node_path_1.default.basename(this.root) || "
|
|
493
|
+
this.name = options.name?.trim() || node_path_1.default.basename(this.root) || "workdir";
|
|
494
494
|
this.metadata = { ...(options.metadata ?? {}) };
|
|
495
495
|
this.trusted = options.trusted ?? false;
|
|
496
|
-
this.files = new LocalFileStore(this.root, { label: "
|
|
497
|
-
this.ignore = [...
|
|
496
|
+
this.files = new LocalFileStore(this.root, { label: "workdir" });
|
|
497
|
+
this.ignore = [...defaultWorkdirIgnoreRules(), ...(options.ignore ?? [])];
|
|
498
498
|
this.gitignore = options.gitignore ?? true;
|
|
499
499
|
this.maxFileBytes = positiveInt(options.maxFileBytes, 10 * 1024 * 1024);
|
|
500
500
|
}
|
|
@@ -521,7 +521,7 @@ class LocalWorkspace {
|
|
|
521
521
|
return loaded;
|
|
522
522
|
}
|
|
523
523
|
child(relativePath, options = {}) {
|
|
524
|
-
return new
|
|
524
|
+
return new LocalWorkdir(this.files.resolvePath(relativePath), {
|
|
525
525
|
name: options.name,
|
|
526
526
|
metadata: options.metadata ?? this.metadata,
|
|
527
527
|
trusted: options.trusted ?? this.trusted,
|
|
@@ -731,7 +731,7 @@ class LocalWorkspace {
|
|
|
731
731
|
}
|
|
732
732
|
}
|
|
733
733
|
}
|
|
734
|
-
exports.
|
|
734
|
+
exports.LocalWorkdir = LocalWorkdir;
|
|
735
735
|
class LocalSkillStore {
|
|
736
736
|
files;
|
|
737
737
|
constructor(files) {
|
|
@@ -904,7 +904,7 @@ function ignored(relativePath, ignore) {
|
|
|
904
904
|
function ignoredDirectoryName(name) {
|
|
905
905
|
return name === ".git" || name === "node_modules" || name === "__pycache__";
|
|
906
906
|
}
|
|
907
|
-
function
|
|
907
|
+
function defaultWorkdirIgnoreRules() {
|
|
908
908
|
return [
|
|
909
909
|
".git",
|
|
910
910
|
"node_modules",
|
package/dist-cjs/local/index.js
CHANGED
|
@@ -16,4 +16,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./core.js"), exports);
|
|
18
18
|
__exportStar(require("./context.js"), exports);
|
|
19
|
+
__exportStar(require("./shell.js"), exports);
|
|
19
20
|
__exportStar(require("./tools.js"), exports);
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.LocalShellDriver = exports.HostLocalShellRunner = void 0;
|
|
7
|
+
exports.createLocalShellToolRegistry = createLocalShellToolRegistry;
|
|
8
|
+
exports.localShellToolDefinition = localShellToolDefinition;
|
|
9
|
+
exports.localShellToolInstructions = localShellToolInstructions;
|
|
10
|
+
const node_child_process_1 = require("node:child_process");
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
class HostLocalShellRunner {
|
|
13
|
+
cwd;
|
|
14
|
+
shell;
|
|
15
|
+
timeoutMs;
|
|
16
|
+
maxOutputBytes;
|
|
17
|
+
env;
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
this.cwd = node_path_1.default.resolve(options.cwd ?? process.cwd());
|
|
20
|
+
this.shell = options.shell ?? true;
|
|
21
|
+
this.timeoutMs = positiveInteger(options.timeoutMs, 2 * 60 * 1000, "timeoutMs");
|
|
22
|
+
this.maxOutputBytes = positiveInteger(options.maxOutputBytes, 128 * 1024, "maxOutputBytes");
|
|
23
|
+
this.env = { ...process.env, ...(options.env ?? {}) };
|
|
24
|
+
}
|
|
25
|
+
async run(request, context = {}) {
|
|
26
|
+
const command = requiredString(request.command, "command");
|
|
27
|
+
const cwd = resolveContainedPath(this.cwd, request.workdir);
|
|
28
|
+
const timeoutMs = request.timeoutMs == null
|
|
29
|
+
? this.timeoutMs
|
|
30
|
+
: positiveInteger(request.timeoutMs, this.timeoutMs, "timeoutMs");
|
|
31
|
+
const started = Date.now();
|
|
32
|
+
const stdout = new BoundedText(this.maxOutputBytes);
|
|
33
|
+
const stderr = new BoundedText(this.maxOutputBytes);
|
|
34
|
+
let timedOut = false;
|
|
35
|
+
return await new Promise((resolve, reject) => {
|
|
36
|
+
const child = (0, node_child_process_1.spawn)(command, [], {
|
|
37
|
+
cwd,
|
|
38
|
+
env: { ...this.env, ...(request.env ?? {}) },
|
|
39
|
+
shell: this.shell,
|
|
40
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
41
|
+
windowsHide: true,
|
|
42
|
+
});
|
|
43
|
+
let settled = false;
|
|
44
|
+
const finish = (exitCode, signal) => {
|
|
45
|
+
if (settled)
|
|
46
|
+
return;
|
|
47
|
+
settled = true;
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
context.signal?.removeEventListener("abort", abort);
|
|
50
|
+
const stdoutText = stdout.text();
|
|
51
|
+
const stderrText = stderr.text();
|
|
52
|
+
const output = [stdoutText, stderrText].filter(Boolean).join(stderrText ? "\n" : "");
|
|
53
|
+
resolve({
|
|
54
|
+
ok: true,
|
|
55
|
+
command,
|
|
56
|
+
description: request.description,
|
|
57
|
+
cwd,
|
|
58
|
+
exit_code: exitCode,
|
|
59
|
+
signal,
|
|
60
|
+
stdout: stdoutText,
|
|
61
|
+
stderr: stderrText,
|
|
62
|
+
output: output || "(no output)",
|
|
63
|
+
duration_ms: Date.now() - started,
|
|
64
|
+
timed_out: timedOut,
|
|
65
|
+
truncated: stdout.truncated || stderr.truncated,
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
const kill = () => {
|
|
69
|
+
if (child.killed)
|
|
70
|
+
return;
|
|
71
|
+
child.kill(process.platform === "win32" ? undefined : "SIGTERM");
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
if (!child.killed)
|
|
74
|
+
child.kill(process.platform === "win32" ? undefined : "SIGKILL");
|
|
75
|
+
}, 3000).unref();
|
|
76
|
+
};
|
|
77
|
+
const abort = () => {
|
|
78
|
+
kill();
|
|
79
|
+
};
|
|
80
|
+
const timer = setTimeout(() => {
|
|
81
|
+
timedOut = true;
|
|
82
|
+
kill();
|
|
83
|
+
}, timeoutMs);
|
|
84
|
+
timer.unref();
|
|
85
|
+
if (context.signal?.aborted) {
|
|
86
|
+
abort();
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
context.signal?.addEventListener("abort", abort, { once: true });
|
|
90
|
+
}
|
|
91
|
+
child.stdout?.on("data", (chunk) => stdout.append(chunk));
|
|
92
|
+
child.stderr?.on("data", (chunk) => stderr.append(chunk));
|
|
93
|
+
child.on("error", (error) => {
|
|
94
|
+
if (settled)
|
|
95
|
+
return;
|
|
96
|
+
settled = true;
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
context.signal?.removeEventListener("abort", abort);
|
|
99
|
+
reject(error);
|
|
100
|
+
});
|
|
101
|
+
child.on("close", finish);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
exports.HostLocalShellRunner = HostLocalShellRunner;
|
|
106
|
+
class LocalShellDriver {
|
|
107
|
+
accessMode;
|
|
108
|
+
runner;
|
|
109
|
+
constructor(options = {}) {
|
|
110
|
+
const cwd = options.workdir?.root ?? options.cwd;
|
|
111
|
+
this.accessMode = options.accessMode ?? "approval";
|
|
112
|
+
this.runner = options.runner ?? new HostLocalShellRunner({
|
|
113
|
+
cwd,
|
|
114
|
+
shell: options.shell,
|
|
115
|
+
timeoutMs: options.timeoutMs,
|
|
116
|
+
maxOutputBytes: options.maxOutputBytes,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async dispatch(args, context = {}) {
|
|
120
|
+
const request = shellRequest(args);
|
|
121
|
+
if (this.accessMode !== "full") {
|
|
122
|
+
return shellApprovalRequired(request);
|
|
123
|
+
}
|
|
124
|
+
return shellToolResult(await this.runner.run(request, context));
|
|
125
|
+
}
|
|
126
|
+
requiresApproval() {
|
|
127
|
+
return this.accessMode !== "full";
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
exports.LocalShellDriver = LocalShellDriver;
|
|
131
|
+
function createLocalShellToolRegistry(options = {}) {
|
|
132
|
+
const toolName = options.toolName ?? "local_shell";
|
|
133
|
+
const driver = new LocalShellDriver(options);
|
|
134
|
+
const hostRunner = driver.runner instanceof HostLocalShellRunner ? driver.runner : undefined;
|
|
135
|
+
const definition = localShellToolDefinition(toolName, {
|
|
136
|
+
accessMode: driver.accessMode,
|
|
137
|
+
cwd: options.workdir?.root ?? hostRunner?.cwd ?? options.cwd,
|
|
138
|
+
shell: hostRunner?.shell ?? options.shell,
|
|
139
|
+
timeoutMs: hostRunner?.timeoutMs ?? options.timeoutMs,
|
|
140
|
+
maxOutputBytes: hostRunner?.maxOutputBytes ?? options.maxOutputBytes,
|
|
141
|
+
});
|
|
142
|
+
return {
|
|
143
|
+
accessMode: driver.accessMode,
|
|
144
|
+
toolName,
|
|
145
|
+
definitions: () => [{ ...definition }],
|
|
146
|
+
handlers: () => ({ [toolName]: (args) => driver.dispatch(args) }),
|
|
147
|
+
execute: async (name, args, context = {}) => {
|
|
148
|
+
if (name !== toolName) {
|
|
149
|
+
throw new Error(`unknown local shell tool: ${name}`);
|
|
150
|
+
}
|
|
151
|
+
return await driver.dispatch(args, context);
|
|
152
|
+
},
|
|
153
|
+
requiresApproval: (name) => name === toolName && driver.requiresApproval(),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function localShellToolDefinition(name = "local_shell", options = {}) {
|
|
157
|
+
return {
|
|
158
|
+
type: "function",
|
|
159
|
+
name,
|
|
160
|
+
description: localShellToolDescription(options),
|
|
161
|
+
parameters: localShellToolParameters(),
|
|
162
|
+
strict: false,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function localShellToolInstructions(options = {}) {
|
|
166
|
+
return localShellToolDescription(options);
|
|
167
|
+
}
|
|
168
|
+
function localShellToolDescription(options = {}) {
|
|
169
|
+
const platform = options.platform ?? process.platform;
|
|
170
|
+
const shell = shellDisplayName(options.shell);
|
|
171
|
+
const accessMode = options.accessMode ?? "approval";
|
|
172
|
+
const cwd = options.cwd ? node_path_1.default.resolve(options.cwd) : undefined;
|
|
173
|
+
const timeoutMs = options.timeoutMs ?? 2 * 60 * 1000;
|
|
174
|
+
const maxOutputBytes = options.maxOutputBytes ?? 128 * 1024;
|
|
175
|
+
return [
|
|
176
|
+
"Run a local shell command through one model-facing primitive.",
|
|
177
|
+
"Prefer local_workdir for file discovery, reading, and editing. Use local_shell for package managers, tests, build commands, git, and other process-level tasks.",
|
|
178
|
+
`Execution environment: platform=${platform}; shell=${shell}; access_mode=${accessMode}; default_timeout_ms=${timeoutMs}; max_output_bytes=${maxOutputBytes}.`,
|
|
179
|
+
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.",
|
|
180
|
+
"The workdir parameter must be a relative child path of the configured cwd. Use workdir instead of cd when possible.",
|
|
181
|
+
"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.",
|
|
182
|
+
"Captured stdout/stderr may be truncated when output exceeds the advertised max_output_bytes.",
|
|
183
|
+
"In approval mode, calls return requires_approval instead of executing. In full mode, commands execute immediately.",
|
|
184
|
+
shellGuidance(platform, options.shell),
|
|
185
|
+
].filter(Boolean).join(" ");
|
|
186
|
+
}
|
|
187
|
+
function localShellToolParameters() {
|
|
188
|
+
return {
|
|
189
|
+
type: "object",
|
|
190
|
+
properties: {
|
|
191
|
+
command: {
|
|
192
|
+
type: "string",
|
|
193
|
+
description: "Shell command to execute. Keep it focused and quote paths containing spaces.",
|
|
194
|
+
},
|
|
195
|
+
description: {
|
|
196
|
+
type: "string",
|
|
197
|
+
description: "Short human-readable description of why this command is being run.",
|
|
198
|
+
},
|
|
199
|
+
workdir: {
|
|
200
|
+
type: "string",
|
|
201
|
+
description: "Optional relative working directory. Use this instead of cd. Absolute paths are rejected by the default host runner.",
|
|
202
|
+
},
|
|
203
|
+
timeout_ms: {
|
|
204
|
+
type: "integer",
|
|
205
|
+
description: "Optional timeout in milliseconds.",
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
required: ["command"],
|
|
209
|
+
additionalProperties: false,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function shellRequest(args) {
|
|
213
|
+
return {
|
|
214
|
+
command: requiredString(args.command, "command"),
|
|
215
|
+
description: optionalString(args.description, "description"),
|
|
216
|
+
workdir: optionalString(args.workdir, "workdir"),
|
|
217
|
+
timeoutMs: optionalNumber(args.timeoutMs ?? args.timeout_ms, "timeout_ms"),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function shellApprovalRequired(request) {
|
|
221
|
+
return {
|
|
222
|
+
ok: false,
|
|
223
|
+
requires_approval: true,
|
|
224
|
+
action: "run",
|
|
225
|
+
command: request.command,
|
|
226
|
+
description: request.description,
|
|
227
|
+
workdir: request.workdir,
|
|
228
|
+
timeout_ms: request.timeoutMs,
|
|
229
|
+
message: "local_shell command execution requires approval",
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function shellToolResult(result) {
|
|
233
|
+
return {
|
|
234
|
+
...result,
|
|
235
|
+
action: "run",
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function resolveContainedPath(root, child) {
|
|
239
|
+
if (!child)
|
|
240
|
+
return root;
|
|
241
|
+
if (node_path_1.default.isAbsolute(child)) {
|
|
242
|
+
throw new Error("workdir must be relative to the configured local shell cwd");
|
|
243
|
+
}
|
|
244
|
+
const resolved = node_path_1.default.resolve(root, child);
|
|
245
|
+
const relative = node_path_1.default.relative(root, resolved);
|
|
246
|
+
if (relative.startsWith("..") || node_path_1.default.isAbsolute(relative)) {
|
|
247
|
+
throw new Error("workdir must stay inside the configured local shell cwd");
|
|
248
|
+
}
|
|
249
|
+
return resolved;
|
|
250
|
+
}
|
|
251
|
+
function requiredString(value, name) {
|
|
252
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
253
|
+
throw new Error(`${name} must be a non-empty string`);
|
|
254
|
+
}
|
|
255
|
+
return value;
|
|
256
|
+
}
|
|
257
|
+
function optionalString(value, name) {
|
|
258
|
+
if (value == null || value === "")
|
|
259
|
+
return undefined;
|
|
260
|
+
if (typeof value !== "string") {
|
|
261
|
+
throw new Error(`${name} must be a string`);
|
|
262
|
+
}
|
|
263
|
+
return value;
|
|
264
|
+
}
|
|
265
|
+
function optionalNumber(value, name) {
|
|
266
|
+
if (value == null)
|
|
267
|
+
return undefined;
|
|
268
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
269
|
+
throw new Error(`${name} must be a number`);
|
|
270
|
+
}
|
|
271
|
+
return Math.trunc(value);
|
|
272
|
+
}
|
|
273
|
+
function positiveInteger(value, fallback, name) {
|
|
274
|
+
if (value == null)
|
|
275
|
+
return fallback;
|
|
276
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
277
|
+
throw new Error(`${name} must be a positive number`);
|
|
278
|
+
}
|
|
279
|
+
return Math.trunc(value);
|
|
280
|
+
}
|
|
281
|
+
function shellDisplayName(shell) {
|
|
282
|
+
if (typeof shell === "string" && shell.trim())
|
|
283
|
+
return shell;
|
|
284
|
+
if (shell === false)
|
|
285
|
+
return "direct process execution";
|
|
286
|
+
if (process.platform === "win32")
|
|
287
|
+
return "cmd.exe";
|
|
288
|
+
return "/bin/sh";
|
|
289
|
+
}
|
|
290
|
+
function shellGuidance(platform, shell) {
|
|
291
|
+
const name = typeof shell === "string" ? node_path_1.default.basename(shell).toLowerCase() : shellDisplayName(shell).toLowerCase();
|
|
292
|
+
if (platform === "win32" || name.includes("powershell") || name === "pwsh") {
|
|
293
|
+
return "On Windows/PowerShell, prefer PowerShell-compatible commands and quote paths with spaces.";
|
|
294
|
+
}
|
|
295
|
+
if (name.includes("cmd.exe") || name === "cmd") {
|
|
296
|
+
return "On cmd.exe, prefer cmd-compatible syntax and use && only for dependent command chaining.";
|
|
297
|
+
}
|
|
298
|
+
return "On POSIX shells, prefer portable shell syntax, quote paths with spaces, and use && only when later commands depend on earlier success.";
|
|
299
|
+
}
|
|
300
|
+
class BoundedText {
|
|
301
|
+
maxBytes;
|
|
302
|
+
chunks = [];
|
|
303
|
+
size = 0;
|
|
304
|
+
truncated = false;
|
|
305
|
+
constructor(maxBytes) {
|
|
306
|
+
this.maxBytes = maxBytes;
|
|
307
|
+
}
|
|
308
|
+
append(chunk) {
|
|
309
|
+
this.chunks.push(chunk);
|
|
310
|
+
this.size += chunk.byteLength;
|
|
311
|
+
while (this.size > this.maxBytes && this.chunks.length > 1) {
|
|
312
|
+
const removed = this.chunks.shift();
|
|
313
|
+
if (!removed)
|
|
314
|
+
break;
|
|
315
|
+
this.size -= removed.byteLength;
|
|
316
|
+
this.truncated = true;
|
|
317
|
+
}
|
|
318
|
+
if (this.size > this.maxBytes && this.chunks.length === 1) {
|
|
319
|
+
const current = this.chunks[0];
|
|
320
|
+
this.chunks[0] = current.subarray(current.byteLength - this.maxBytes);
|
|
321
|
+
this.size = this.chunks[0].byteLength;
|
|
322
|
+
this.truncated = true;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
text() {
|
|
326
|
+
const value = Buffer.concat(this.chunks, this.size).toString("utf8");
|
|
327
|
+
return this.truncated ? `...output truncated...\n${value}` : value;
|
|
328
|
+
}
|
|
329
|
+
}
|