@ottocode/sdk 0.1.173

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 (125) hide show
  1. package/README.md +338 -0
  2. package/package.json +128 -0
  3. package/src/agent/types.ts +19 -0
  4. package/src/auth/src/copilot-oauth.ts +190 -0
  5. package/src/auth/src/index.ts +100 -0
  6. package/src/auth/src/oauth.ts +234 -0
  7. package/src/auth/src/openai-oauth.ts +394 -0
  8. package/src/auth/src/wallet.ts +51 -0
  9. package/src/browser.ts +32 -0
  10. package/src/config/src/index.ts +110 -0
  11. package/src/config/src/manager.ts +181 -0
  12. package/src/config/src/paths.ts +98 -0
  13. package/src/core/src/errors.ts +102 -0
  14. package/src/core/src/index.ts +108 -0
  15. package/src/core/src/providers/resolver.ts +244 -0
  16. package/src/core/src/streaming/artifacts.ts +41 -0
  17. package/src/core/src/terminals/bun-pty.ts +13 -0
  18. package/src/core/src/terminals/circular-buffer.ts +30 -0
  19. package/src/core/src/terminals/ensure-bun-pty.ts +70 -0
  20. package/src/core/src/terminals/index.ts +8 -0
  21. package/src/core/src/terminals/manager.ts +158 -0
  22. package/src/core/src/terminals/rust-libs.ts +30 -0
  23. package/src/core/src/terminals/terminal.ts +132 -0
  24. package/src/core/src/tools/bin-manager.ts +250 -0
  25. package/src/core/src/tools/builtin/bash.ts +155 -0
  26. package/src/core/src/tools/builtin/bash.txt +7 -0
  27. package/src/core/src/tools/builtin/file-cache.ts +39 -0
  28. package/src/core/src/tools/builtin/finish.ts +12 -0
  29. package/src/core/src/tools/builtin/finish.txt +10 -0
  30. package/src/core/src/tools/builtin/fs/cd.ts +19 -0
  31. package/src/core/src/tools/builtin/fs/cd.txt +5 -0
  32. package/src/core/src/tools/builtin/fs/index.ts +20 -0
  33. package/src/core/src/tools/builtin/fs/ls.ts +72 -0
  34. package/src/core/src/tools/builtin/fs/ls.txt +8 -0
  35. package/src/core/src/tools/builtin/fs/pwd.ts +17 -0
  36. package/src/core/src/tools/builtin/fs/pwd.txt +5 -0
  37. package/src/core/src/tools/builtin/fs/read.ts +119 -0
  38. package/src/core/src/tools/builtin/fs/read.txt +8 -0
  39. package/src/core/src/tools/builtin/fs/tree.ts +149 -0
  40. package/src/core/src/tools/builtin/fs/tree.txt +11 -0
  41. package/src/core/src/tools/builtin/fs/util.ts +95 -0
  42. package/src/core/src/tools/builtin/fs/write.ts +106 -0
  43. package/src/core/src/tools/builtin/fs/write.txt +11 -0
  44. package/src/core/src/tools/builtin/git.commit.txt +6 -0
  45. package/src/core/src/tools/builtin/git.diff.txt +5 -0
  46. package/src/core/src/tools/builtin/git.status.txt +5 -0
  47. package/src/core/src/tools/builtin/git.ts +151 -0
  48. package/src/core/src/tools/builtin/glob.ts +128 -0
  49. package/src/core/src/tools/builtin/glob.txt +10 -0
  50. package/src/core/src/tools/builtin/grep.ts +136 -0
  51. package/src/core/src/tools/builtin/grep.txt +9 -0
  52. package/src/core/src/tools/builtin/ignore.ts +45 -0
  53. package/src/core/src/tools/builtin/patch/apply.ts +546 -0
  54. package/src/core/src/tools/builtin/patch/constants.ts +5 -0
  55. package/src/core/src/tools/builtin/patch/normalize.ts +31 -0
  56. package/src/core/src/tools/builtin/patch/parse-enveloped.ts +209 -0
  57. package/src/core/src/tools/builtin/patch/parse-unified.ts +231 -0
  58. package/src/core/src/tools/builtin/patch/parse.ts +28 -0
  59. package/src/core/src/tools/builtin/patch/text.ts +23 -0
  60. package/src/core/src/tools/builtin/patch/types.ts +82 -0
  61. package/src/core/src/tools/builtin/patch.ts +167 -0
  62. package/src/core/src/tools/builtin/patch.txt +207 -0
  63. package/src/core/src/tools/builtin/progress.ts +55 -0
  64. package/src/core/src/tools/builtin/progress.txt +7 -0
  65. package/src/core/src/tools/builtin/ripgrep.ts +125 -0
  66. package/src/core/src/tools/builtin/ripgrep.txt +7 -0
  67. package/src/core/src/tools/builtin/terminal.ts +300 -0
  68. package/src/core/src/tools/builtin/terminal.txt +93 -0
  69. package/src/core/src/tools/builtin/todos.ts +66 -0
  70. package/src/core/src/tools/builtin/todos.txt +7 -0
  71. package/src/core/src/tools/builtin/websearch.ts +250 -0
  72. package/src/core/src/tools/builtin/websearch.txt +12 -0
  73. package/src/core/src/tools/error.ts +67 -0
  74. package/src/core/src/tools/loader.ts +421 -0
  75. package/src/core/src/types/index.ts +11 -0
  76. package/src/core/src/types/types.ts +4 -0
  77. package/src/core/src/utils/ansi.ts +27 -0
  78. package/src/core/src/utils/debug.ts +40 -0
  79. package/src/core/src/utils/logger.ts +150 -0
  80. package/src/index.ts +313 -0
  81. package/src/prompts/src/agents/build.txt +89 -0
  82. package/src/prompts/src/agents/general.txt +15 -0
  83. package/src/prompts/src/agents/plan.txt +10 -0
  84. package/src/prompts/src/agents/research.txt +50 -0
  85. package/src/prompts/src/base.txt +24 -0
  86. package/src/prompts/src/debug.ts +104 -0
  87. package/src/prompts/src/index.ts +1 -0
  88. package/src/prompts/src/modes/oneshot.txt +9 -0
  89. package/src/prompts/src/providers/anthropic.txt +247 -0
  90. package/src/prompts/src/providers/anthropicSpoof.txt +1 -0
  91. package/src/prompts/src/providers/default.txt +466 -0
  92. package/src/prompts/src/providers/google.txt +230 -0
  93. package/src/prompts/src/providers/moonshot.txt +24 -0
  94. package/src/prompts/src/providers/openai.txt +414 -0
  95. package/src/prompts/src/providers.ts +143 -0
  96. package/src/providers/src/anthropic-caching.ts +202 -0
  97. package/src/providers/src/anthropic-oauth-client.ts +157 -0
  98. package/src/providers/src/authorization.ts +17 -0
  99. package/src/providers/src/catalog-manual.ts +135 -0
  100. package/src/providers/src/catalog-merged.ts +9 -0
  101. package/src/providers/src/catalog.ts +8329 -0
  102. package/src/providers/src/copilot-client.ts +39 -0
  103. package/src/providers/src/env.ts +31 -0
  104. package/src/providers/src/google-client.ts +16 -0
  105. package/src/providers/src/index.ts +75 -0
  106. package/src/providers/src/moonshot-client.ts +25 -0
  107. package/src/providers/src/oauth-models.ts +39 -0
  108. package/src/providers/src/openai-oauth-client.ts +108 -0
  109. package/src/providers/src/opencode-client.ts +64 -0
  110. package/src/providers/src/openrouter-client.ts +31 -0
  111. package/src/providers/src/pricing.ts +178 -0
  112. package/src/providers/src/setu-client.ts +643 -0
  113. package/src/providers/src/utils.ts +210 -0
  114. package/src/providers/src/validate.ts +39 -0
  115. package/src/providers/src/zai-client.ts +47 -0
  116. package/src/skills/index.ts +34 -0
  117. package/src/skills/loader.ts +152 -0
  118. package/src/skills/parser.ts +108 -0
  119. package/src/skills/tool.ts +87 -0
  120. package/src/skills/types.ts +41 -0
  121. package/src/skills/validator.ts +110 -0
  122. package/src/types/src/auth.ts +33 -0
  123. package/src/types/src/config.ts +36 -0
  124. package/src/types/src/index.ts +20 -0
  125. package/src/types/src/provider.ts +71 -0
@@ -0,0 +1,132 @@
1
+ import type { IPty } from './bun-pty.ts';
2
+ import { EventEmitter } from 'node:events';
3
+ import { CircularBuffer } from './circular-buffer.ts';
4
+
5
+ export type TerminalStatus = 'running' | 'exited';
6
+ export type TerminalCreator = 'user' | 'llm';
7
+
8
+ export interface TerminalOptions {
9
+ command: string;
10
+ args?: string[];
11
+ cwd: string;
12
+ purpose: string;
13
+ createdBy: TerminalCreator;
14
+ title?: string;
15
+ }
16
+
17
+ export class Terminal {
18
+ readonly id: string;
19
+ readonly pty: IPty;
20
+ readonly command: string;
21
+ readonly args: string[];
22
+ readonly cwd: string;
23
+ readonly purpose: string;
24
+ readonly createdBy: TerminalCreator;
25
+ readonly createdAt: Date;
26
+
27
+ private buffer: CircularBuffer;
28
+ private _status: TerminalStatus = 'running';
29
+ private _exitCode?: number;
30
+ private _title?: string;
31
+ private dataEmitter = new EventEmitter();
32
+ private exitEmitter = new EventEmitter();
33
+
34
+ constructor(id: string, pty: IPty, options: TerminalOptions) {
35
+ this.id = id;
36
+ this.pty = pty;
37
+ this.command = options.command;
38
+ this.args = options.args || [];
39
+ this.cwd = options.cwd;
40
+ this.purpose = options.purpose;
41
+ this.createdBy = options.createdBy;
42
+ this._title = options.title;
43
+ this.createdAt = new Date();
44
+ this.buffer = new CircularBuffer(500);
45
+
46
+ this.pty.onData((data) => {
47
+ // Store in buffer for history
48
+ this.buffer.push(data);
49
+ // Emit raw data - terminals need control chars, ANSI codes, etc.
50
+ this.dataEmitter.emit('data', data);
51
+ });
52
+
53
+ this.pty.onExit(({ exitCode }) => {
54
+ this._status = 'exited';
55
+ this._exitCode = exitCode;
56
+ this.exitEmitter.emit('exit', exitCode);
57
+ });
58
+ }
59
+
60
+ get pid(): number {
61
+ return this.pty.pid;
62
+ }
63
+
64
+ get status(): TerminalStatus {
65
+ return this._status;
66
+ }
67
+
68
+ get exitCode(): number | undefined {
69
+ return this._exitCode;
70
+ }
71
+
72
+ get title(): string {
73
+ return this._title || this.purpose;
74
+ }
75
+
76
+ set title(value: string) {
77
+ this._title = value;
78
+ }
79
+
80
+ read(lines?: number): string[] {
81
+ return this.buffer.read(lines);
82
+ }
83
+
84
+ clearBuffer(): void {
85
+ this.buffer.clear();
86
+ }
87
+
88
+ write(input: string): void {
89
+ this.pty.write(input);
90
+ }
91
+
92
+ kill(signal?: string): void {
93
+ this.pty.kill(signal);
94
+ }
95
+
96
+ resize(cols: number, rows: number): void {
97
+ this.pty.resize(cols, rows);
98
+ }
99
+
100
+ onData(callback: (line: string) => void): void {
101
+ this.dataEmitter.on('data', callback);
102
+ }
103
+
104
+ onExit(callback: (exitCode: number) => void): void {
105
+ this.exitEmitter.on('exit', callback);
106
+ }
107
+
108
+ removeDataListener(callback: (line: string) => void): void {
109
+ this.dataEmitter.off('data', callback);
110
+ }
111
+
112
+ removeExitListener(callback: (exitCode: number) => void): void {
113
+ this.exitEmitter.off('exit', callback);
114
+ }
115
+
116
+ toJSON() {
117
+ return {
118
+ id: this.id,
119
+ pid: this.pid,
120
+ command: this.command,
121
+ args: this.args,
122
+ cwd: this.cwd,
123
+ purpose: this.purpose,
124
+ createdBy: this.createdBy,
125
+ title: this.title,
126
+ status: this.status,
127
+ exitCode: this.exitCode,
128
+ createdAt: this.createdAt,
129
+ uptime: Date.now() - this.createdAt.getTime(),
130
+ };
131
+ }
132
+ }
@@ -0,0 +1,250 @@
1
+ import { join } from 'node:path';
2
+ import { promises as fs } from 'node:fs';
3
+ import { spawn, execSync } from 'node:child_process';
4
+ import { homedir } from 'node:os';
5
+
6
+ const OTTO_BIN_DIR_NAME = 'bin';
7
+
8
+ let cachedBinDir: string | null = null;
9
+ const resolvedPaths = new Map<string, string>();
10
+ let cachedLoginPath: string | null = null;
11
+
12
+ function getConfigHome(): string {
13
+ const cfgHome = process.env.XDG_CONFIG_HOME;
14
+ if (cfgHome?.trim()) return cfgHome.replace(/\\/g, '/');
15
+ const home = process.env.HOME || process.env.USERPROFILE || '';
16
+ return join(home, '.config');
17
+ }
18
+
19
+ export function getAgiBinDir(): string {
20
+ if (cachedBinDir) return cachedBinDir;
21
+ cachedBinDir = join(getConfigHome(), 'otto', OTTO_BIN_DIR_NAME);
22
+ return cachedBinDir;
23
+ }
24
+
25
+ function getPlatformKey(): string {
26
+ const platform = process.platform;
27
+ const arch = process.arch;
28
+ const os =
29
+ platform === 'darwin'
30
+ ? 'darwin'
31
+ : platform === 'win32'
32
+ ? 'windows'
33
+ : 'linux';
34
+ const cpu = arch === 'arm64' ? 'arm64' : 'x64';
35
+ return `${os}-${cpu}`;
36
+ }
37
+
38
+ function getBinaryFileName(name: string): string {
39
+ if (process.platform === 'win32') return `${name}.exe`;
40
+ return name;
41
+ }
42
+
43
+ async function ensureDir(dir: string): Promise<void> {
44
+ await fs.mkdir(dir, { recursive: true });
45
+ }
46
+
47
+ async function fileExists(p: string): Promise<boolean> {
48
+ try {
49
+ await fs.access(p);
50
+ return true;
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ async function isExecutable(p: string): Promise<boolean> {
57
+ try {
58
+ await fs.access(p, 0o1);
59
+ return true;
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+
65
+ async function makeExecutable(p: string): Promise<void> {
66
+ if (process.platform === 'win32') return;
67
+ try {
68
+ await fs.chmod(p, 0o755);
69
+ } catch {}
70
+ }
71
+
72
+ async function whichBinary(name: string): Promise<string | null> {
73
+ const cmd = process.platform === 'win32' ? 'where' : 'which';
74
+ return new Promise((resolve) => {
75
+ const proc = spawn(cmd, [name], { stdio: ['ignore', 'pipe', 'ignore'] });
76
+ let stdout = '';
77
+ proc.stdout.on('data', (d) => {
78
+ stdout += d.toString();
79
+ });
80
+ proc.on('close', (code) => {
81
+ if (code === 0 && stdout.trim()) resolve(stdout.trim().split('\n')[0]);
82
+ else resolve(null);
83
+ });
84
+ proc.on('error', () => resolve(null));
85
+ });
86
+ }
87
+
88
+ function getVendorSearchPaths(binaryName: string): string[] {
89
+ const platformKey = getPlatformKey();
90
+ const paths: string[] = [];
91
+
92
+ const tauriResource = process.env.TAURI_RESOURCE_DIR;
93
+ if (tauriResource) {
94
+ paths.push(join(tauriResource, 'vendor', 'bin', platformKey, binaryName));
95
+ paths.push(join(tauriResource, 'vendor', 'bin', binaryName));
96
+ }
97
+
98
+ try {
99
+ const exePath = process.execPath;
100
+ if (exePath) {
101
+ const exeDir = join(exePath, '..');
102
+ paths.push(join(exeDir, 'vendor', 'bin', platformKey, binaryName));
103
+ paths.push(
104
+ join(
105
+ exeDir,
106
+ '..',
107
+ 'Resources',
108
+ 'vendor',
109
+ 'bin',
110
+ platformKey,
111
+ binaryName,
112
+ ),
113
+ );
114
+ }
115
+ } catch {}
116
+
117
+ if (process.env.CARGO_MANIFEST_DIR) {
118
+ paths.push(
119
+ join(
120
+ process.env.CARGO_MANIFEST_DIR,
121
+ 'resources',
122
+ 'vendor',
123
+ 'bin',
124
+ platformKey,
125
+ binaryName,
126
+ ),
127
+ );
128
+ }
129
+
130
+ const cwd = process.cwd();
131
+ paths.push(join(cwd, 'vendor', 'bin', platformKey, binaryName));
132
+
133
+ return paths;
134
+ }
135
+
136
+ async function extractFromVendor(name: string): Promise<string | null> {
137
+ const binaryName = getBinaryFileName(name);
138
+ const binDir = getAgiBinDir();
139
+ const targetPath = join(binDir, binaryName);
140
+
141
+ if ((await fileExists(targetPath)) && (await isExecutable(targetPath))) {
142
+ return targetPath;
143
+ }
144
+
145
+ const searchPaths = getVendorSearchPaths(binaryName);
146
+
147
+ for (const src of searchPaths) {
148
+ if (await fileExists(src)) {
149
+ await ensureDir(binDir);
150
+ await fs.copyFile(src, targetPath);
151
+ await makeExecutable(targetPath);
152
+ return targetPath;
153
+ }
154
+ }
155
+
156
+ return null;
157
+ }
158
+
159
+ export async function resolveBinary(name: string): Promise<string> {
160
+ const cached = resolvedPaths.get(name);
161
+ if (cached) return cached;
162
+
163
+ const binaryName = getBinaryFileName(name);
164
+ const binDir = getAgiBinDir();
165
+ const installedPath = join(binDir, binaryName);
166
+ if (
167
+ (await fileExists(installedPath)) &&
168
+ (await isExecutable(installedPath))
169
+ ) {
170
+ resolvedPaths.set(name, installedPath);
171
+ return installedPath;
172
+ }
173
+
174
+ const vendorPath = await extractFromVendor(name);
175
+ if (vendorPath) {
176
+ resolvedPaths.set(name, vendorPath);
177
+ return vendorPath;
178
+ }
179
+
180
+ const systemPath = await whichBinary(binaryName);
181
+ if (systemPath) {
182
+ resolvedPaths.set(name, systemPath);
183
+ return systemPath;
184
+ }
185
+
186
+ return binaryName;
187
+ }
188
+
189
+ export function clearBinaryCache(): void {
190
+ resolvedPaths.clear();
191
+ }
192
+
193
+ function getLoginShellPath(): string | null {
194
+ if (cachedLoginPath !== null) return cachedLoginPath;
195
+
196
+ if (process.platform === 'win32') {
197
+ cachedLoginPath = process.env.PATH || '';
198
+ return cachedLoginPath;
199
+ }
200
+
201
+ const home = process.env.HOME || homedir();
202
+ const shellCandidates = [
203
+ process.env.SHELL,
204
+ '/bin/zsh',
205
+ '/bin/bash',
206
+ '/bin/sh',
207
+ ].filter(Boolean) as string[];
208
+
209
+ for (const shell of shellCandidates) {
210
+ try {
211
+ const result = execSync(`${shell} -ilc 'echo "___PATH___:$PATH"'`, {
212
+ timeout: 5000,
213
+ stdio: ['ignore', 'pipe', 'ignore'],
214
+ env: { HOME: home, USER: process.env.USER || '', SHELL: shell },
215
+ });
216
+ const output = result.toString();
217
+ const match = output.match(/___PATH___:(.*)/);
218
+ if (match?.[1]?.trim()) {
219
+ cachedLoginPath = match[1].trim();
220
+ return cachedLoginPath;
221
+ }
222
+ } catch {}
223
+ }
224
+
225
+ cachedLoginPath = null;
226
+ return null;
227
+ }
228
+
229
+ export function getAugmentedPath(): string {
230
+ const sep = process.platform === 'win32' ? ';' : ':';
231
+ const binDir = getAgiBinDir();
232
+ const current = process.env.PATH || '';
233
+ const loginPath = getLoginShellPath();
234
+
235
+ const seen = new Set<string>();
236
+ const parts: string[] = [];
237
+
238
+ for (const p of [
239
+ binDir,
240
+ ...(loginPath ? loginPath.split(sep) : []),
241
+ ...current.split(sep),
242
+ ]) {
243
+ if (p && !seen.has(p)) {
244
+ seen.add(p);
245
+ parts.push(p);
246
+ }
247
+ }
248
+
249
+ return parts.join(sep);
250
+ }
@@ -0,0 +1,155 @@
1
+ import { tool, type Tool } from 'ai';
2
+ import { z } from 'zod/v3';
3
+ import { spawn } from 'node:child_process';
4
+ import DESCRIPTION from './bash.txt' with { type: 'text' };
5
+ import { createToolError, type ToolResponse } from '../error.ts';
6
+ import { getAugmentedPath } from '../bin-manager.ts';
7
+
8
+ function normalizePath(p: string) {
9
+ const parts = p.replace(/\\/g, '/').split('/');
10
+ const stack: string[] = [];
11
+ for (const part of parts) {
12
+ if (!part || part === '.') continue;
13
+ if (part === '..') stack.pop();
14
+ else stack.push(part);
15
+ }
16
+ return `/${stack.join('/')}`;
17
+ }
18
+
19
+ function resolveSafePath(projectRoot: string, p: string) {
20
+ const root = normalizePath(projectRoot);
21
+ const abs = normalizePath(`${root}/${p || '.'}`);
22
+ if (!(abs === root || abs.startsWith(`${root}/`))) {
23
+ throw new Error(`cwd escapes project root: ${p}`);
24
+ }
25
+ return abs;
26
+ }
27
+
28
+ export function buildBashTool(projectRoot: string): {
29
+ name: string;
30
+ tool: Tool;
31
+ } {
32
+ const bash = tool({
33
+ description: DESCRIPTION,
34
+ inputSchema: z
35
+ .object({
36
+ cmd: z.string().describe('Shell command to run (bash -c <cmd>)'),
37
+ cwd: z
38
+ .string()
39
+ .default('.')
40
+ .describe('Working directory relative to project root'),
41
+ allowNonZeroExit: z
42
+ .boolean()
43
+ .optional()
44
+ .default(false)
45
+ .describe('If true, do not throw on non-zero exit'),
46
+ timeout: z
47
+ .number()
48
+ .optional()
49
+ .default(300000)
50
+ .describe('Timeout in milliseconds (default: 300000 = 5 minutes)'),
51
+ })
52
+ .strict(),
53
+ async execute({
54
+ cmd,
55
+ cwd,
56
+ allowNonZeroExit,
57
+ timeout = 300000,
58
+ }: {
59
+ cmd: string;
60
+ cwd?: string;
61
+ allowNonZeroExit?: boolean;
62
+ timeout?: number;
63
+ }): Promise<
64
+ ToolResponse<{
65
+ exitCode: number;
66
+ stdout: string;
67
+ stderr: string;
68
+ }>
69
+ > {
70
+ const absCwd = resolveSafePath(projectRoot, cwd || '.');
71
+
72
+ return new Promise((resolve) => {
73
+ const proc = spawn(cmd, {
74
+ cwd: absCwd,
75
+ shell: true,
76
+ stdio: ['ignore', 'pipe', 'pipe'],
77
+ env: { ...process.env, PATH: getAugmentedPath() },
78
+ });
79
+
80
+ let stdout = '';
81
+ let stderr = '';
82
+ let didTimeout = false;
83
+ let timeoutId: ReturnType<typeof setTimeout> | null = null;
84
+
85
+ if (timeout > 0) {
86
+ timeoutId = setTimeout(() => {
87
+ didTimeout = true;
88
+ proc.kill();
89
+ }, timeout);
90
+ }
91
+
92
+ proc.stdout?.on('data', (chunk) => {
93
+ stdout += chunk.toString();
94
+ });
95
+
96
+ proc.stderr?.on('data', (chunk) => {
97
+ stderr += chunk.toString();
98
+ });
99
+
100
+ proc.on('close', (exitCode) => {
101
+ if (timeoutId) clearTimeout(timeoutId);
102
+
103
+ if (didTimeout) {
104
+ resolve(
105
+ createToolError(
106
+ `Command timed out after ${timeout}ms: ${cmd}`,
107
+ 'timeout',
108
+ {
109
+ parameter: 'timeout',
110
+ value: timeout,
111
+ suggestion: 'Increase timeout or optimize the command',
112
+ },
113
+ ),
114
+ );
115
+ } else if (exitCode !== 0 && !allowNonZeroExit) {
116
+ const errorDetail = stderr.trim() || stdout.trim() || '';
117
+ const errorMsg = `Command failed with exit code ${exitCode}${errorDetail ? `\n\n${errorDetail}` : ''}`;
118
+ resolve(
119
+ createToolError(errorMsg, 'execution', {
120
+ exitCode,
121
+ stdout,
122
+ stderr,
123
+ cmd,
124
+ suggestion:
125
+ 'Check command syntax or use allowNonZeroExit: true',
126
+ }),
127
+ );
128
+ } else {
129
+ resolve({
130
+ ok: true,
131
+ exitCode: exitCode ?? 0,
132
+ stdout,
133
+ stderr,
134
+ });
135
+ }
136
+ });
137
+
138
+ proc.on('error', (err) => {
139
+ if (timeoutId) clearTimeout(timeoutId);
140
+ resolve(
141
+ createToolError(
142
+ `Command execution failed: ${err.message}`,
143
+ 'execution',
144
+ {
145
+ cmd,
146
+ originalError: err.message,
147
+ },
148
+ ),
149
+ );
150
+ });
151
+ });
152
+ },
153
+ });
154
+ return { name: 'bash', tool: bash };
155
+ }
@@ -0,0 +1,7 @@
1
+ - Execute a shell command using `bash -lc`
2
+ - Returns `stdout`, `stderr`, and `exitCode`
3
+ - `cwd` is relative to the project root and sandboxed within it
4
+
5
+ Usage tips:
6
+ - Chain multiple commands with `&&` to fail-fast
7
+ - For long outputs, consider redirecting to a file and reading it
@@ -0,0 +1,39 @@
1
+ /**
2
+ * File content cache to track modifications during a session/step
3
+ * This helps ensure tools always work with the latest file content
4
+ */
5
+
6
+ const fileContentCache = new Map<string, Map<string, string>>();
7
+
8
+ export function getFileCache(sessionId: string): Map<string, string> {
9
+ if (!fileContentCache.has(sessionId)) {
10
+ fileContentCache.set(sessionId, new Map());
11
+ }
12
+ return fileContentCache.get(sessionId) as Map<string, string>;
13
+ }
14
+
15
+ export function updateFileCache(
16
+ sessionId: string,
17
+ filePath: string,
18
+ content: string,
19
+ ): void {
20
+ const cache = getFileCache(sessionId);
21
+ cache.set(filePath, content);
22
+ }
23
+
24
+ export function getCachedContent(
25
+ sessionId: string,
26
+ filePath: string,
27
+ ): string | undefined {
28
+ const cache = getFileCache(sessionId);
29
+ return cache.get(filePath);
30
+ }
31
+
32
+ export function clearFileCache(sessionId: string): void {
33
+ fileContentCache.delete(sessionId);
34
+ }
35
+
36
+ export function clearFileCacheEntry(sessionId: string, filePath: string): void {
37
+ const cache = getFileCache(sessionId);
38
+ cache.delete(filePath);
39
+ }
@@ -0,0 +1,12 @@
1
+ import { z } from 'zod/v3';
2
+ import { tool } from 'ai';
3
+ import DESCRIPTION from './finish.txt' with { type: 'text' };
4
+ import type { ToolResponse } from '../error.ts';
5
+
6
+ export const finishTool = tool({
7
+ description: DESCRIPTION,
8
+ inputSchema: z.object({}),
9
+ async execute(): Promise<ToolResponse<{ done: true }>> {
10
+ return { ok: true, done: true };
11
+ },
12
+ });
@@ -0,0 +1,10 @@
1
+ Call this tool AFTER you have completed all your work AND streamed your final summary/response to the user.
2
+
3
+ This signals the end of your turn and that you are done with the current request.
4
+
5
+ **CRITICAL**: You MUST always call this tool when you are completely finished. The workflow is:
6
+ 1. Perform all necessary tool calls (read files, make changes, etc.)
7
+ 2. Stream your final text response/summary to the user
8
+ 3. Call the finish tool to signal completion
9
+
10
+ Do NOT call finish before streaming your response. Do NOT forget to call finish after responding.
@@ -0,0 +1,19 @@
1
+ import { tool, type Tool } from 'ai';
2
+ import { z } from 'zod/v3';
3
+ import DESCRIPTION from './cd.txt' with { type: 'text' };
4
+
5
+ // description imported above
6
+
7
+ export function buildCdTool(): { name: string; tool: Tool } {
8
+ const cd = tool({
9
+ description: DESCRIPTION,
10
+ inputSchema: z.object({
11
+ path: z.string().describe('Relative directory path'),
12
+ }),
13
+ async execute({ path }: { path: string }) {
14
+ // Actual cwd update is handled in the adapter; this is a placeholder schema
15
+ return { cwd: path };
16
+ },
17
+ });
18
+ return { name: 'cd', tool: cd };
19
+ }
@@ -0,0 +1,5 @@
1
+ - Change the current working directory (relative to the project root)
2
+ - The runner enforces boundaries; this tool only requests a new cwd
3
+
4
+ Usage tip:
5
+ - Use PWD to verify the current directory
@@ -0,0 +1,20 @@
1
+ import type { Tool } from 'ai';
2
+ import { buildReadTool } from './read.ts';
3
+ import { buildWriteTool } from './write.ts';
4
+ import { buildLsTool } from './ls.ts';
5
+ import { buildTreeTool } from './tree.ts';
6
+ import { buildPwdTool } from './pwd.ts';
7
+ import { buildCdTool } from './cd.ts';
8
+
9
+ export function buildFsTools(
10
+ projectRoot: string,
11
+ ): Array<{ name: string; tool: Tool }> {
12
+ const out: Array<{ name: string; tool: Tool }> = [];
13
+ out.push(buildReadTool(projectRoot));
14
+ out.push(buildWriteTool(projectRoot));
15
+ out.push(buildLsTool(projectRoot));
16
+ out.push(buildTreeTool(projectRoot));
17
+ out.push(buildPwdTool());
18
+ out.push(buildCdTool());
19
+ return out;
20
+ }