@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.
Files changed (49) hide show
  1. package/dist/agent/base.d.ts +2 -1
  2. package/dist/agent/disk.d.ts +8 -7
  3. package/dist/agent/disk.js +16 -7
  4. package/dist/agent/index.d.ts +2 -0
  5. package/dist/agent/index.js +2 -0
  6. package/dist/agent/prompts.d.ts +6 -0
  7. package/dist/agent/prompts.js +100 -0
  8. package/dist/agent/sandbox.d.ts +102 -0
  9. package/dist/agent/sandbox.js +309 -0
  10. package/dist/agent/skill.d.ts +2 -8
  11. package/dist/agent/skill.js +26 -26
  12. package/dist/agent/text-editor.d.ts +44 -0
  13. package/dist/agent/text-editor.js +201 -0
  14. package/dist/client-types.d.ts +1 -0
  15. package/dist/client.d.ts +3 -4
  16. package/dist/client.js +6 -6
  17. package/dist/resources/disks.d.ts +12 -0
  18. package/dist/resources/disks.js +24 -0
  19. package/dist/resources/index.d.ts +1 -2
  20. package/dist/resources/index.js +1 -2
  21. package/dist/resources/sandboxes.d.ts +51 -0
  22. package/dist/resources/sandboxes.js +70 -0
  23. package/dist/resources/sessions.d.ts +1 -17
  24. package/dist/resources/sessions.js +0 -26
  25. package/dist/resources/skills.d.ts +14 -1
  26. package/dist/resources/skills.js +17 -0
  27. package/dist/resources/users.d.ts +2 -2
  28. package/dist/resources/users.js +2 -2
  29. package/dist/types/index.d.ts +1 -2
  30. package/dist/types/index.js +1 -2
  31. package/dist/types/sandbox.d.ts +64 -0
  32. package/dist/types/sandbox.js +41 -0
  33. package/dist/types/session.d.ts +0 -12
  34. package/dist/types/session.js +1 -8
  35. package/dist/types/skill.d.ts +7 -0
  36. package/dist/types/skill.js +7 -1
  37. package/dist/types/tool.d.ts +0 -4
  38. package/dist/types/tool.js +1 -4
  39. package/dist/types/user.d.ts +0 -2
  40. package/dist/types/user.js +0 -1
  41. package/package.json +8 -8
  42. package/dist/resources/blocks.d.ts +0 -33
  43. package/dist/resources/blocks.js +0 -85
  44. package/dist/resources/spaces.d.ts +0 -68
  45. package/dist/resources/spaces.js +0 -109
  46. package/dist/types/block.d.ts +0 -18
  47. package/dist/types/block.js +0 -20
  48. package/dist/types/space.d.ts +0 -67
  49. 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());
@@ -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
  };
@@ -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 = exports.ListSkillsTool = void 0;
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 { client, skills };
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: 'number',
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
+ }
@@ -12,5 +12,6 @@ export interface RequesterProtocol {
12
12
  contentType: string;
13
13
  }>;
14
14
  unwrap?: boolean;
15
+ timeout?: number;
15
16
  }): Promise<T>;
16
17
  }