@acontext/acontext 0.0.21 → 0.1.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/dist/agent/base.d.ts +2 -1
- package/dist/agent/disk.d.ts +8 -7
- package/dist/agent/disk.js +16 -7
- package/dist/agent/index.d.ts +2 -0
- package/dist/agent/index.js +2 -0
- package/dist/agent/prompts.d.ts +6 -0
- package/dist/agent/prompts.js +100 -0
- package/dist/agent/sandbox.d.ts +102 -0
- package/dist/agent/sandbox.js +309 -0
- package/dist/agent/skill.d.ts +2 -8
- package/dist/agent/skill.js +26 -26
- package/dist/agent/text-editor.d.ts +44 -0
- package/dist/agent/text-editor.js +201 -0
- package/dist/client-types.d.ts +1 -0
- package/dist/client.d.ts +3 -4
- package/dist/client.js +6 -6
- package/dist/resources/disks.d.ts +12 -0
- package/dist/resources/disks.js +24 -0
- package/dist/resources/index.d.ts +1 -2
- package/dist/resources/index.js +1 -2
- package/dist/resources/sandboxes.d.ts +51 -0
- package/dist/resources/sandboxes.js +70 -0
- package/dist/resources/sessions.d.ts +1 -17
- package/dist/resources/sessions.js +0 -26
- package/dist/resources/skills.d.ts +14 -1
- package/dist/resources/skills.js +17 -0
- package/dist/resources/users.d.ts +2 -2
- package/dist/resources/users.js +2 -2
- package/dist/types/index.d.ts +1 -2
- package/dist/types/index.js +1 -2
- package/dist/types/sandbox.d.ts +64 -0
- package/dist/types/sandbox.js +41 -0
- package/dist/types/session.d.ts +0 -12
- package/dist/types/session.js +1 -8
- package/dist/types/skill.d.ts +7 -0
- package/dist/types/skill.js +7 -1
- package/dist/types/tool.d.ts +0 -4
- package/dist/types/tool.js +1 -4
- package/dist/types/user.d.ts +0 -2
- package/dist/types/user.js +0 -1
- package/package.json +8 -8
- package/dist/resources/blocks.d.ts +0 -33
- package/dist/resources/blocks.js +0 -85
- package/dist/resources/spaces.d.ts +0 -68
- package/dist/resources/spaces.js +0 -109
- package/dist/types/block.d.ts +0 -18
- package/dist/types/block.js +0 -20
- package/dist/types/space.d.ts +0 -67
- package/dist/types/space.js +0 -44
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Agent tools for sandbox operations using the Acontext Sandbox API.
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.SANDBOX_TOOLS = exports.SandboxToolPool = exports.ExportSandboxFileTool = exports.TextEditorTool = exports.BashTool = void 0;
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const base_1 = require("./base");
|
|
42
|
+
const prompts_1 = require("./prompts");
|
|
43
|
+
const text_editor_1 = require("./text-editor");
|
|
44
|
+
function formatMountedSkills(mountedSkillPaths) {
|
|
45
|
+
if (mountedSkillPaths.size === 0) {
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
// Sort by skill name
|
|
49
|
+
const sortedSkills = Array.from(mountedSkillPaths.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
50
|
+
const skillEntries = sortedSkills.map((skill) => {
|
|
51
|
+
const location = path.posix.join(skill.basePath, 'SKILL.md');
|
|
52
|
+
return `<skill>
|
|
53
|
+
<name>${skill.name}</name>
|
|
54
|
+
<description>${skill.description}</description>
|
|
55
|
+
<location>${location}</location>
|
|
56
|
+
</skill>`;
|
|
57
|
+
});
|
|
58
|
+
return skillEntries.join('\n');
|
|
59
|
+
}
|
|
60
|
+
function getSandboxContextPrompt(mountedSkillPaths) {
|
|
61
|
+
let baseBody = `<text_editor_sandbox>
|
|
62
|
+
${prompts_1.SANDBOX_TEXT_EDITOR_REMINDER}
|
|
63
|
+
</text_editor_sandbox>
|
|
64
|
+
<bash_execution_sandbox>
|
|
65
|
+
${prompts_1.SANDBOX_BASH_REMINDER}
|
|
66
|
+
</bash_execution_sandbox>`;
|
|
67
|
+
if (mountedSkillPaths.size > 0) {
|
|
68
|
+
const formattedSkills = formatMountedSkills(mountedSkillPaths);
|
|
69
|
+
baseBody += `
|
|
70
|
+
<skills>
|
|
71
|
+
${prompts_1.SKILL_REMINDER}
|
|
72
|
+
<available_skills>
|
|
73
|
+
${formattedSkills}
|
|
74
|
+
</available_skills>
|
|
75
|
+
</skills>`;
|
|
76
|
+
}
|
|
77
|
+
return `<sandbox>
|
|
78
|
+
By default, you are in \`/workspace\`.
|
|
79
|
+
${baseBody}
|
|
80
|
+
</sandbox>
|
|
81
|
+
`;
|
|
82
|
+
}
|
|
83
|
+
function normalizePath(path) {
|
|
84
|
+
if (!path) {
|
|
85
|
+
return '/';
|
|
86
|
+
}
|
|
87
|
+
let normalized = path.startsWith('/') ? path : `/${path}`;
|
|
88
|
+
if (!normalized.endsWith('/')) {
|
|
89
|
+
normalized += '/';
|
|
90
|
+
}
|
|
91
|
+
return normalized;
|
|
92
|
+
}
|
|
93
|
+
class BashTool extends base_1.AbstractBaseTool {
|
|
94
|
+
constructor(timeout) {
|
|
95
|
+
super();
|
|
96
|
+
this.name = 'bash_execution_sandbox';
|
|
97
|
+
this.description = 'The bash_execution_sandbox tool enables execution of bash scripts in a secure sandboxed container environment.';
|
|
98
|
+
this.arguments = {
|
|
99
|
+
command: {
|
|
100
|
+
type: 'string',
|
|
101
|
+
description: "The bash command to execute. Examples: 'ls -la', 'python3 script.py', 'sed -i 's/old_string/new_string/g' file.py'",
|
|
102
|
+
},
|
|
103
|
+
timeout: {
|
|
104
|
+
type: ['number', 'null'],
|
|
105
|
+
description: 'Optional timeout in seconds for this command. Use for long-running commands that may exceed the default timeout.',
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
this.requiredArguments = ['command'];
|
|
109
|
+
this._timeout = timeout;
|
|
110
|
+
}
|
|
111
|
+
async execute(ctx, llmArguments) {
|
|
112
|
+
const command = llmArguments.command;
|
|
113
|
+
const timeout = llmArguments.timeout ?? this._timeout;
|
|
114
|
+
if (!command) {
|
|
115
|
+
throw new Error('command is required');
|
|
116
|
+
}
|
|
117
|
+
const result = await ctx.client.sandboxes.execCommand({
|
|
118
|
+
sandboxId: ctx.sandboxId,
|
|
119
|
+
command,
|
|
120
|
+
timeout,
|
|
121
|
+
});
|
|
122
|
+
return JSON.stringify({
|
|
123
|
+
stdout: result.stdout,
|
|
124
|
+
stderr: result.stderr,
|
|
125
|
+
exit_code: result.exit_code,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
exports.BashTool = BashTool;
|
|
130
|
+
class TextEditorTool extends base_1.AbstractBaseTool {
|
|
131
|
+
constructor(timeout) {
|
|
132
|
+
super();
|
|
133
|
+
this.name = 'text_editor_sandbox';
|
|
134
|
+
this.description = 'A tool for viewing, creating, and editing text files in the sandbox.';
|
|
135
|
+
this.arguments = {
|
|
136
|
+
command: {
|
|
137
|
+
type: 'string',
|
|
138
|
+
enum: ['view', 'create', 'str_replace'],
|
|
139
|
+
description: "The operation to perform: 'view', 'create', or 'str_replace'",
|
|
140
|
+
},
|
|
141
|
+
path: {
|
|
142
|
+
type: 'string',
|
|
143
|
+
description: "The file path in the sandbox (e.g., '/workspace/script.py')",
|
|
144
|
+
},
|
|
145
|
+
file_text: {
|
|
146
|
+
type: ['string', 'null'],
|
|
147
|
+
description: "For 'create' command: the content to write to the file",
|
|
148
|
+
},
|
|
149
|
+
old_str: {
|
|
150
|
+
type: ['string', 'null'],
|
|
151
|
+
description: "For 'str_replace' command: the exact string to find and replace",
|
|
152
|
+
},
|
|
153
|
+
new_str: {
|
|
154
|
+
type: ['string', 'null'],
|
|
155
|
+
description: "For 'str_replace' command: the string to replace old_str with",
|
|
156
|
+
},
|
|
157
|
+
view_range: {
|
|
158
|
+
type: ['array', 'null'],
|
|
159
|
+
description: "For 'view' command: optional [start_line, end_line] to view specific lines",
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
this.requiredArguments = ['command', 'path'];
|
|
163
|
+
this._timeout = timeout;
|
|
164
|
+
}
|
|
165
|
+
async execute(ctx, llmArguments) {
|
|
166
|
+
const command = llmArguments.command;
|
|
167
|
+
const path = llmArguments.path;
|
|
168
|
+
if (!command) {
|
|
169
|
+
throw new Error('command is required');
|
|
170
|
+
}
|
|
171
|
+
if (!path) {
|
|
172
|
+
throw new Error('path is required');
|
|
173
|
+
}
|
|
174
|
+
if (command === 'view') {
|
|
175
|
+
const viewRange = llmArguments.view_range;
|
|
176
|
+
const result = await (0, text_editor_1.viewFile)(ctx, path, viewRange, this._timeout);
|
|
177
|
+
return JSON.stringify(result);
|
|
178
|
+
}
|
|
179
|
+
else if (command === 'create') {
|
|
180
|
+
const fileText = llmArguments.file_text;
|
|
181
|
+
if (fileText === null || fileText === undefined) {
|
|
182
|
+
throw new Error('file_text is required for create command');
|
|
183
|
+
}
|
|
184
|
+
const result = await (0, text_editor_1.createFile)(ctx, path, fileText, this._timeout);
|
|
185
|
+
return JSON.stringify(result);
|
|
186
|
+
}
|
|
187
|
+
else if (command === 'str_replace') {
|
|
188
|
+
const oldStr = llmArguments.old_str;
|
|
189
|
+
const newStr = llmArguments.new_str;
|
|
190
|
+
if (oldStr === null || oldStr === undefined) {
|
|
191
|
+
throw new Error('old_str is required for str_replace command');
|
|
192
|
+
}
|
|
193
|
+
if (newStr === null || newStr === undefined) {
|
|
194
|
+
throw new Error('new_str is required for str_replace command');
|
|
195
|
+
}
|
|
196
|
+
const result = await (0, text_editor_1.strReplace)(ctx, path, oldStr, newStr, this._timeout);
|
|
197
|
+
return JSON.stringify(result);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
throw new Error(`Unknown command: ${command}. Must be 'view', 'create', or 'str_replace'`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
exports.TextEditorTool = TextEditorTool;
|
|
205
|
+
class ExportSandboxFileTool extends base_1.AbstractBaseTool {
|
|
206
|
+
constructor() {
|
|
207
|
+
super(...arguments);
|
|
208
|
+
this.name = 'export_file_sandbox';
|
|
209
|
+
this.description = `Export a file from the sandbox to persistent, shared disk storage, and return you a public download URL.
|
|
210
|
+
If the sandbox file is changed, the disk file won't be updated unless you export the file again.`;
|
|
211
|
+
this.arguments = {
|
|
212
|
+
sandbox_path: {
|
|
213
|
+
type: 'string',
|
|
214
|
+
description: "The directory path in the sandbox where the file is located. Must end with '/'. Examples: '/workspace/', '/home/user/output/'",
|
|
215
|
+
},
|
|
216
|
+
sandbox_filename: {
|
|
217
|
+
type: 'string',
|
|
218
|
+
description: 'The name of the file to export from the sandbox.',
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
this.requiredArguments = ['sandbox_path', 'sandbox_filename'];
|
|
222
|
+
}
|
|
223
|
+
async execute(ctx, llmArguments) {
|
|
224
|
+
const sandboxPath = llmArguments.sandbox_path;
|
|
225
|
+
const sandboxFilename = llmArguments.sandbox_filename;
|
|
226
|
+
const diskPath = '/artifacts/';
|
|
227
|
+
if (!sandboxPath) {
|
|
228
|
+
throw new Error('sandbox_path is required');
|
|
229
|
+
}
|
|
230
|
+
if (!sandboxFilename) {
|
|
231
|
+
throw new Error('sandbox_filename is required');
|
|
232
|
+
}
|
|
233
|
+
const normalizedSandboxPath = normalizePath(sandboxPath);
|
|
234
|
+
const normalizedDiskPath = normalizePath(diskPath);
|
|
235
|
+
const artifact = await ctx.client.disks.artifacts.uploadFromSandbox(ctx.diskId, {
|
|
236
|
+
sandboxId: ctx.sandboxId,
|
|
237
|
+
sandboxPath: normalizedSandboxPath,
|
|
238
|
+
sandboxFilename,
|
|
239
|
+
filePath: normalizedDiskPath,
|
|
240
|
+
});
|
|
241
|
+
// Get the public URL for the uploaded artifact
|
|
242
|
+
const artifactInfo = await ctx.client.disks.artifacts.get(ctx.diskId, {
|
|
243
|
+
filePath: artifact.path,
|
|
244
|
+
filename: artifact.filename,
|
|
245
|
+
withPublicUrl: true,
|
|
246
|
+
withContent: false,
|
|
247
|
+
});
|
|
248
|
+
return JSON.stringify({
|
|
249
|
+
message: 'successfully exported file to disk',
|
|
250
|
+
public_url: artifactInfo.public_url,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
exports.ExportSandboxFileTool = ExportSandboxFileTool;
|
|
255
|
+
class SandboxToolPool extends base_1.BaseToolPool {
|
|
256
|
+
/**
|
|
257
|
+
* Create a sandbox context.
|
|
258
|
+
*
|
|
259
|
+
* @param client - The Acontext client instance.
|
|
260
|
+
* @param sandboxId - The UUID of the sandbox.
|
|
261
|
+
* @param diskId - The UUID of the disk for file exports.
|
|
262
|
+
* @param mountSkills - Optional list of skill IDs to download to the sandbox.
|
|
263
|
+
* Skills are downloaded to /skills/{skill_name}/ in the sandbox.
|
|
264
|
+
* @returns Promise resolving to SandboxContext for use with sandbox tools.
|
|
265
|
+
*/
|
|
266
|
+
async formatContext(client, sandboxId, diskId, mountSkills) {
|
|
267
|
+
const mountedSkillPaths = new Map();
|
|
268
|
+
const ctx = {
|
|
269
|
+
client,
|
|
270
|
+
sandboxId,
|
|
271
|
+
diskId,
|
|
272
|
+
mountedSkillPaths,
|
|
273
|
+
getContextPrompt() {
|
|
274
|
+
return getSandboxContextPrompt(mountedSkillPaths);
|
|
275
|
+
},
|
|
276
|
+
formatMountedSkills() {
|
|
277
|
+
return formatMountedSkills(mountedSkillPaths);
|
|
278
|
+
},
|
|
279
|
+
async mountSkills(skillIds) {
|
|
280
|
+
for (const skillId of skillIds) {
|
|
281
|
+
if (mountedSkillPaths.has(skillId)) {
|
|
282
|
+
// Skip already mounted skills
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
const result = await client.skills.downloadToSandbox(skillId, {
|
|
286
|
+
sandboxId,
|
|
287
|
+
});
|
|
288
|
+
if (result.success) {
|
|
289
|
+
mountedSkillPaths.set(skillId, {
|
|
290
|
+
basePath: result.dir_path,
|
|
291
|
+
name: result.name,
|
|
292
|
+
description: result.description,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
if (mountSkills && mountSkills.length > 0) {
|
|
299
|
+
await ctx.mountSkills(mountSkills);
|
|
300
|
+
}
|
|
301
|
+
return ctx;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
exports.SandboxToolPool = SandboxToolPool;
|
|
305
|
+
// Pre-configured tool pool with sandbox tools
|
|
306
|
+
exports.SANDBOX_TOOLS = new SandboxToolPool();
|
|
307
|
+
exports.SANDBOX_TOOLS.addTool(new BashTool());
|
|
308
|
+
exports.SANDBOX_TOOLS.addTool(new TextEditorTool());
|
|
309
|
+
exports.SANDBOX_TOOLS.addTool(new ExportSandboxFileTool());
|
package/dist/agent/skill.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { AbstractBaseTool, BaseContext, BaseToolPool } from './base';
|
|
|
10
10
|
export interface SkillContext extends BaseContext {
|
|
11
11
|
client: AcontextClient;
|
|
12
12
|
skills: Map<string, Skill>;
|
|
13
|
+
getContextPrompt(): string;
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* Create a SkillContext by preloading skills from a list of skill IDs.
|
|
@@ -33,13 +34,6 @@ export declare function getSkillFromContext(ctx: SkillContext, skillName: string
|
|
|
33
34
|
* Return list of available skill names in this context.
|
|
34
35
|
*/
|
|
35
36
|
export declare function listSkillNamesFromContext(ctx: SkillContext): string[];
|
|
36
|
-
export declare class ListSkillsTool extends AbstractBaseTool {
|
|
37
|
-
readonly name = "list_skills";
|
|
38
|
-
readonly description = "List all available skills in the current context with their names and descriptions.";
|
|
39
|
-
readonly arguments: {};
|
|
40
|
-
readonly requiredArguments: string[];
|
|
41
|
-
execute(ctx: SkillContext, _llmArguments: Record<string, unknown>): Promise<string>;
|
|
42
|
-
}
|
|
43
37
|
export declare class GetSkillTool extends AbstractBaseTool {
|
|
44
38
|
readonly name = "get_skill";
|
|
45
39
|
readonly description = "Get a skill by its name. Returns the skill information including the relative paths of the files and their mime type categories.";
|
|
@@ -65,7 +59,7 @@ export declare class GetSkillFileTool extends AbstractBaseTool {
|
|
|
65
59
|
description: string;
|
|
66
60
|
};
|
|
67
61
|
expire: {
|
|
68
|
-
type: string;
|
|
62
|
+
type: string[];
|
|
69
63
|
description: string;
|
|
70
64
|
};
|
|
71
65
|
};
|
package/dist/agent/skill.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Skill tools for agent operations.
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.SKILL_TOOLS = exports.SkillToolPool = exports.GetSkillFileTool = exports.GetSkillTool =
|
|
6
|
+
exports.SKILL_TOOLS = exports.SkillToolPool = exports.GetSkillFileTool = exports.GetSkillTool = void 0;
|
|
7
7
|
exports.createSkillContext = createSkillContext;
|
|
8
8
|
exports.getSkillFromContext = getSkillFromContext;
|
|
9
9
|
exports.listSkillNamesFromContext = listSkillNamesFromContext;
|
|
@@ -27,7 +27,30 @@ async function createSkillContext(client, skillIds) {
|
|
|
27
27
|
}
|
|
28
28
|
skills.set(skill.name, skill);
|
|
29
29
|
}
|
|
30
|
-
return {
|
|
30
|
+
return {
|
|
31
|
+
client,
|
|
32
|
+
skills,
|
|
33
|
+
getContextPrompt() {
|
|
34
|
+
if (skills.size === 0) {
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
const lines = ['<available_skills>'];
|
|
38
|
+
for (const [skillName, skill] of skills.entries()) {
|
|
39
|
+
lines.push('<skill>');
|
|
40
|
+
lines.push(`<name>${skillName}</name>`);
|
|
41
|
+
lines.push(`<description>${skill.description}</description>`);
|
|
42
|
+
lines.push('</skill>');
|
|
43
|
+
}
|
|
44
|
+
lines.push('</available_skills>');
|
|
45
|
+
const skillSection = lines.join('\n');
|
|
46
|
+
return `<skill_view>
|
|
47
|
+
Use get_skill and get_skill_file to view the available skills and their contexts.
|
|
48
|
+
Below is the list of available skills:
|
|
49
|
+
${skillSection}
|
|
50
|
+
</skill_view>
|
|
51
|
+
`;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
31
54
|
}
|
|
32
55
|
/**
|
|
33
56
|
* Get a skill by name from the preloaded skills.
|
|
@@ -51,28 +74,6 @@ function getSkillFromContext(ctx, skillName) {
|
|
|
51
74
|
function listSkillNamesFromContext(ctx) {
|
|
52
75
|
return Array.from(ctx.skills.keys());
|
|
53
76
|
}
|
|
54
|
-
class ListSkillsTool extends base_1.AbstractBaseTool {
|
|
55
|
-
constructor() {
|
|
56
|
-
super(...arguments);
|
|
57
|
-
this.name = 'list_skills';
|
|
58
|
-
this.description = 'List all available skills in the current context with their names and descriptions.';
|
|
59
|
-
this.arguments = {};
|
|
60
|
-
this.requiredArguments = [];
|
|
61
|
-
}
|
|
62
|
-
async execute(ctx,
|
|
63
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
64
|
-
_llmArguments) {
|
|
65
|
-
if (ctx.skills.size === 0) {
|
|
66
|
-
return 'No skills available in the current context.';
|
|
67
|
-
}
|
|
68
|
-
const skillList = [];
|
|
69
|
-
for (const [skillName, skill] of ctx.skills.entries()) {
|
|
70
|
-
skillList.push(`- ${skillName}: ${skill.description}`);
|
|
71
|
-
}
|
|
72
|
-
return `Available skills (${ctx.skills.size}):\n${skillList.join('\n')}`;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
exports.ListSkillsTool = ListSkillsTool;
|
|
76
77
|
class GetSkillTool extends base_1.AbstractBaseTool {
|
|
77
78
|
constructor() {
|
|
78
79
|
super(...arguments);
|
|
@@ -126,7 +127,7 @@ class GetSkillFileTool extends base_1.AbstractBaseTool {
|
|
|
126
127
|
description: "Relative path to the file within the skill (e.g., 'scripts/extract_text.json').",
|
|
127
128
|
},
|
|
128
129
|
expire: {
|
|
129
|
-
type: '
|
|
130
|
+
type: ['integer', 'null'],
|
|
130
131
|
description: 'URL expiration time in seconds (only used for non-parseable files). Defaults to 900 (15 minutes).',
|
|
131
132
|
},
|
|
132
133
|
};
|
|
@@ -181,6 +182,5 @@ class SkillToolPool extends base_1.BaseToolPool {
|
|
|
181
182
|
}
|
|
182
183
|
exports.SkillToolPool = SkillToolPool;
|
|
183
184
|
exports.SKILL_TOOLS = new SkillToolPool();
|
|
184
|
-
exports.SKILL_TOOLS.addTool(new ListSkillsTool());
|
|
185
185
|
exports.SKILL_TOOLS.addTool(new GetSkillTool());
|
|
186
186
|
exports.SKILL_TOOLS.addTool(new GetSkillFileTool());
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text editor file operations for sandbox environments.
|
|
3
|
+
*/
|
|
4
|
+
import type { SandboxContext } from './sandbox';
|
|
5
|
+
/**
|
|
6
|
+
* Escape a string for safe use in shell commands.
|
|
7
|
+
*/
|
|
8
|
+
export declare function escapeForShell(s: string): string;
|
|
9
|
+
export interface ViewFileResult {
|
|
10
|
+
file_type?: string;
|
|
11
|
+
content?: string;
|
|
12
|
+
numLines?: number;
|
|
13
|
+
startLine?: number;
|
|
14
|
+
totalLines?: number;
|
|
15
|
+
error?: string;
|
|
16
|
+
stderr?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface CreateFileResult {
|
|
19
|
+
is_file_update?: boolean;
|
|
20
|
+
message?: string;
|
|
21
|
+
error?: string;
|
|
22
|
+
stderr?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface StrReplaceResult {
|
|
25
|
+
oldStart?: number;
|
|
26
|
+
oldLines?: number;
|
|
27
|
+
newStart?: number;
|
|
28
|
+
newLines?: number;
|
|
29
|
+
lines?: string[];
|
|
30
|
+
error?: string;
|
|
31
|
+
stderr?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* View file content with line numbers.
|
|
35
|
+
*/
|
|
36
|
+
export declare function viewFile(ctx: SandboxContext, path: string, viewRange: number[] | null, timeout?: number): Promise<ViewFileResult>;
|
|
37
|
+
/**
|
|
38
|
+
* Create a new file with content.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createFile(ctx: SandboxContext, path: string, fileText: string, timeout?: number): Promise<CreateFileResult>;
|
|
41
|
+
/**
|
|
42
|
+
* Replace a string in a file.
|
|
43
|
+
*/
|
|
44
|
+
export declare function strReplace(ctx: SandboxContext, path: string, oldStr: string, newStr: string, timeout?: number): Promise<StrReplaceResult>;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Text editor file operations for sandbox environments.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.escapeForShell = escapeForShell;
|
|
7
|
+
exports.viewFile = viewFile;
|
|
8
|
+
exports.createFile = createFile;
|
|
9
|
+
exports.strReplace = strReplace;
|
|
10
|
+
/**
|
|
11
|
+
* Escape a string for safe use in shell commands.
|
|
12
|
+
*/
|
|
13
|
+
function escapeForShell(s) {
|
|
14
|
+
// Use single quotes and escape any single quotes in the string
|
|
15
|
+
return "'" + s.replace(/'/g, "'\"'\"'") + "'";
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* View file content with line numbers.
|
|
19
|
+
*/
|
|
20
|
+
async function viewFile(ctx, path, viewRange, timeout) {
|
|
21
|
+
// First check if file exists and get total lines
|
|
22
|
+
const checkCmd = `wc -l < ${escapeForShell(path)} 2>/dev/null || echo 'FILE_NOT_FOUND'`;
|
|
23
|
+
const checkResult = await ctx.client.sandboxes.execCommand({
|
|
24
|
+
sandboxId: ctx.sandboxId,
|
|
25
|
+
command: checkCmd,
|
|
26
|
+
timeout,
|
|
27
|
+
});
|
|
28
|
+
if (checkResult.stdout.includes('FILE_NOT_FOUND') || checkResult.exit_code !== 0) {
|
|
29
|
+
return {
|
|
30
|
+
error: `File not found: ${path}`,
|
|
31
|
+
stderr: checkResult.stderr,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const totalLines = /^\d+$/.test(checkResult.stdout.trim())
|
|
35
|
+
? parseInt(checkResult.stdout.trim(), 10)
|
|
36
|
+
: 0;
|
|
37
|
+
// Build the view command with line numbers
|
|
38
|
+
let cmd;
|
|
39
|
+
let startLine;
|
|
40
|
+
if (viewRange && viewRange.length === 2) {
|
|
41
|
+
const [rangeStart, rangeEnd] = viewRange;
|
|
42
|
+
cmd = `sed -n '${rangeStart},${rangeEnd}p' ${escapeForShell(path)} | nl -ba -v ${rangeStart}`;
|
|
43
|
+
startLine = rangeStart;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
cmd = `nl -ba ${escapeForShell(path)}`;
|
|
47
|
+
startLine = 1;
|
|
48
|
+
}
|
|
49
|
+
const result = await ctx.client.sandboxes.execCommand({
|
|
50
|
+
sandboxId: ctx.sandboxId,
|
|
51
|
+
command: cmd,
|
|
52
|
+
timeout,
|
|
53
|
+
});
|
|
54
|
+
if (result.exit_code !== 0) {
|
|
55
|
+
return {
|
|
56
|
+
error: `Failed to view file: ${path}`,
|
|
57
|
+
stderr: result.stderr,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
// Count lines in output
|
|
61
|
+
const contentLines = result.stdout.trim()
|
|
62
|
+
? result.stdout.trimEnd().split('\n')
|
|
63
|
+
: [];
|
|
64
|
+
const numLines = contentLines.length;
|
|
65
|
+
return {
|
|
66
|
+
file_type: 'text',
|
|
67
|
+
content: result.stdout,
|
|
68
|
+
numLines,
|
|
69
|
+
startLine: viewRange ? startLine : 1,
|
|
70
|
+
totalLines: totalLines + 1, // wc -l doesn't count last line without newline
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create a new file with content.
|
|
75
|
+
*/
|
|
76
|
+
async function createFile(ctx, path, fileText, timeout) {
|
|
77
|
+
// Check if file already exists
|
|
78
|
+
const checkCmd = `test -f ${escapeForShell(path)} && echo 'EXISTS' || echo 'NEW'`;
|
|
79
|
+
const checkResult = await ctx.client.sandboxes.execCommand({
|
|
80
|
+
sandboxId: ctx.sandboxId,
|
|
81
|
+
command: checkCmd,
|
|
82
|
+
timeout,
|
|
83
|
+
});
|
|
84
|
+
const isUpdate = checkResult.stdout.includes('EXISTS');
|
|
85
|
+
// Create directory if needed
|
|
86
|
+
const parts = path.split('/');
|
|
87
|
+
parts.pop();
|
|
88
|
+
const dirPath = parts.join('/');
|
|
89
|
+
if (dirPath) {
|
|
90
|
+
const mkdirCmd = `mkdir -p ${escapeForShell(dirPath)}`;
|
|
91
|
+
await ctx.client.sandboxes.execCommand({
|
|
92
|
+
sandboxId: ctx.sandboxId,
|
|
93
|
+
command: mkdirCmd,
|
|
94
|
+
timeout,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
// Write file using base64 encoding to safely transfer content
|
|
98
|
+
const encodedContent = Buffer.from(fileText, 'utf-8').toString('base64');
|
|
99
|
+
const writeCmd = `echo ${escapeForShell(encodedContent)} | base64 -d > ${escapeForShell(path)}`;
|
|
100
|
+
const result = await ctx.client.sandboxes.execCommand({
|
|
101
|
+
sandboxId: ctx.sandboxId,
|
|
102
|
+
command: writeCmd,
|
|
103
|
+
timeout,
|
|
104
|
+
});
|
|
105
|
+
if (result.exit_code !== 0) {
|
|
106
|
+
return {
|
|
107
|
+
error: `Failed to create file: ${path}`,
|
|
108
|
+
stderr: result.stderr,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
is_file_update: isUpdate,
|
|
113
|
+
message: `File ${isUpdate ? 'updated' : 'created'}: ${path}`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Replace a string in a file.
|
|
118
|
+
*/
|
|
119
|
+
async function strReplace(ctx, path, oldStr, newStr, timeout) {
|
|
120
|
+
// First read the file content
|
|
121
|
+
const readCmd = `cat ${escapeForShell(path)}`;
|
|
122
|
+
const result = await ctx.client.sandboxes.execCommand({
|
|
123
|
+
sandboxId: ctx.sandboxId,
|
|
124
|
+
command: readCmd,
|
|
125
|
+
timeout,
|
|
126
|
+
});
|
|
127
|
+
if (result.exit_code !== 0) {
|
|
128
|
+
return {
|
|
129
|
+
error: `File not found: ${path}`,
|
|
130
|
+
stderr: result.stderr,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const originalContent = result.stdout;
|
|
134
|
+
// Check if oldStr exists in the file
|
|
135
|
+
if (!originalContent.includes(oldStr)) {
|
|
136
|
+
return {
|
|
137
|
+
error: `String not found in file: ${oldStr.substring(0, 50)}...`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// Count occurrences
|
|
141
|
+
let occurrences = 0;
|
|
142
|
+
let searchIndex = 0;
|
|
143
|
+
while (searchIndex < originalContent.length) {
|
|
144
|
+
const foundIndex = originalContent.indexOf(oldStr, searchIndex);
|
|
145
|
+
if (foundIndex === -1)
|
|
146
|
+
break;
|
|
147
|
+
occurrences++;
|
|
148
|
+
searchIndex = foundIndex + oldStr.length;
|
|
149
|
+
}
|
|
150
|
+
if (occurrences > 1) {
|
|
151
|
+
return {
|
|
152
|
+
error: `Multiple occurrences (${occurrences}) of the string found. Please provide more context to make the match unique.`,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
// Perform the replacement
|
|
156
|
+
const newContent = originalContent.replace(oldStr, newStr);
|
|
157
|
+
// Find the line numbers affected
|
|
158
|
+
const oldLines = originalContent.split('\n');
|
|
159
|
+
const newLines = newContent.split('\n');
|
|
160
|
+
// Find where the change starts
|
|
161
|
+
let oldStart = 1;
|
|
162
|
+
const minLen = Math.min(oldLines.length, newLines.length);
|
|
163
|
+
for (let i = 0; i < minLen; i++) {
|
|
164
|
+
if (oldLines[i] !== newLines[i]) {
|
|
165
|
+
oldStart = i + 1;
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Write the new content
|
|
170
|
+
const encodedContent = Buffer.from(newContent, 'utf-8').toString('base64');
|
|
171
|
+
const writeCmd = `echo ${escapeForShell(encodedContent)} | base64 -d > ${escapeForShell(path)}`;
|
|
172
|
+
const writeResult = await ctx.client.sandboxes.execCommand({
|
|
173
|
+
sandboxId: ctx.sandboxId,
|
|
174
|
+
command: writeCmd,
|
|
175
|
+
timeout,
|
|
176
|
+
});
|
|
177
|
+
if (writeResult.exit_code !== 0) {
|
|
178
|
+
return {
|
|
179
|
+
error: `Failed to write file: ${path}`,
|
|
180
|
+
stderr: writeResult.stderr,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
// Calculate diff info
|
|
184
|
+
const oldStrLines = oldStr.split('\n').length;
|
|
185
|
+
const newStrLines = newStr.split('\n').length;
|
|
186
|
+
// Build diff lines
|
|
187
|
+
const diffLines = [];
|
|
188
|
+
for (const line of oldStr.split('\n')) {
|
|
189
|
+
diffLines.push(`-${line}`);
|
|
190
|
+
}
|
|
191
|
+
for (const line of newStr.split('\n')) {
|
|
192
|
+
diffLines.push(`+${line}`);
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
oldStart,
|
|
196
|
+
oldLines: oldStrLines,
|
|
197
|
+
newStart: oldStart,
|
|
198
|
+
newLines: newStrLines,
|
|
199
|
+
lines: diffLines,
|
|
200
|
+
};
|
|
201
|
+
}
|