@ottocode/sdk 0.1.246 → 0.1.248
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/package.json +5 -1
- package/src/core/src/tools/builtin/bash.ts +1 -266
- package/src/core/src/tools/builtin/shell.ts +273 -0
- package/src/core/src/tools/builtin/shell.txt +13 -0
- package/src/core/src/tools/builtin/terminal.txt +9 -6
- package/src/core/src/tools/loader.ts +3 -3
- package/src/index.ts +12 -0
- package/src/prompts/src/agents/build.txt +2 -2
- package/src/prompts/src/modes/guided.txt +2 -2
- package/src/prompts/src/providers/anthropic.txt +2 -2
- package/src/prompts/src/providers/default.txt +1 -1
- package/src/prompts/src/providers/glm.txt +1 -1
- package/src/prompts/src/providers/google.txt +7 -7
- package/src/prompts/src/providers/moonshot.txt +1 -1
- package/src/providers/src/catalog.ts +187 -9
- package/src/providers/src/index.ts +12 -0
- package/src/providers/src/model-catalog-cache.ts +107 -0
- package/src/providers/src/oauth-models.ts +1 -0
- package/src/providers/src/registry.ts +1 -1
- package/src/core/src/tools/builtin/bash.txt +0 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ottocode/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.248",
|
|
4
4
|
"description": "AI agent SDK for building intelligent assistants - tree-shakable and comprehensive",
|
|
5
5
|
"author": "nitishxyz",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,6 +37,10 @@
|
|
|
37
37
|
"import": "./src/core/src/tools/builtin/bash.ts",
|
|
38
38
|
"types": "./src/core/src/tools/builtin/bash.ts"
|
|
39
39
|
},
|
|
40
|
+
"./tools/builtin/shell": {
|
|
41
|
+
"import": "./src/core/src/tools/builtin/shell.ts",
|
|
42
|
+
"types": "./src/core/src/tools/builtin/shell.ts"
|
|
43
|
+
},
|
|
40
44
|
"./tools/builtin/finish": {
|
|
41
45
|
"import": "./src/core/src/tools/builtin/finish.ts",
|
|
42
46
|
"types": "./src/core/src/tools/builtin/finish.ts"
|
|
@@ -1,266 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { spawn } from 'node:child_process';
|
|
3
|
-
import { z } from 'zod/v3';
|
|
4
|
-
import DESCRIPTION from './bash.txt' with { type: 'text' };
|
|
5
|
-
import { getAugmentedPath } from '../bin-manager.ts';
|
|
6
|
-
import { createToolError, type ToolResponse } from '../error.ts';
|
|
7
|
-
import { injectCoAuthorIntoGitCommit } from './git-identity.ts';
|
|
8
|
-
|
|
9
|
-
function normalizePath(p: string) {
|
|
10
|
-
const normalized = p.replace(/\\/g, '/');
|
|
11
|
-
const driveMatch = normalized.match(/^([A-Za-z]):\//);
|
|
12
|
-
const drivePrefix = driveMatch ? `${driveMatch[1]}:` : '';
|
|
13
|
-
const rest = driveMatch ? normalized.slice(2) : normalized;
|
|
14
|
-
const parts = rest.split('/');
|
|
15
|
-
const stack: string[] = [];
|
|
16
|
-
for (const part of parts) {
|
|
17
|
-
if (!part || part === '.') continue;
|
|
18
|
-
if (part === '..') stack.pop();
|
|
19
|
-
else stack.push(part);
|
|
20
|
-
}
|
|
21
|
-
if (drivePrefix) return `${drivePrefix}/${stack.join('/')}`;
|
|
22
|
-
return `/${stack.join('/')}`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function resolveSafePath(projectRoot: string, p: string) {
|
|
26
|
-
const root = normalizePath(projectRoot);
|
|
27
|
-
const abs = normalizePath(`${root}/${p || '.'}`);
|
|
28
|
-
if (!(abs === root || abs.startsWith(`${root}/`))) {
|
|
29
|
-
throw new Error(`cwd escapes project root: ${p}`);
|
|
30
|
-
}
|
|
31
|
-
return abs;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function killProcessTree(pid: number) {
|
|
35
|
-
try {
|
|
36
|
-
process.kill(-pid, 'SIGTERM');
|
|
37
|
-
} catch {
|
|
38
|
-
try {
|
|
39
|
-
process.kill(pid, 'SIGTERM');
|
|
40
|
-
} catch {}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
type BashResult = ToolResponse<{
|
|
45
|
-
exitCode: number;
|
|
46
|
-
stdout: string;
|
|
47
|
-
stderr: string;
|
|
48
|
-
}>;
|
|
49
|
-
|
|
50
|
-
type BashStreamChunk =
|
|
51
|
-
| {
|
|
52
|
-
channel: 'output';
|
|
53
|
-
delta: string;
|
|
54
|
-
}
|
|
55
|
-
| {
|
|
56
|
-
result: BashResult;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
export function buildBashTool(projectRoot: string): {
|
|
60
|
-
name: string;
|
|
61
|
-
tool: Tool;
|
|
62
|
-
} {
|
|
63
|
-
const bash = tool({
|
|
64
|
-
description: DESCRIPTION,
|
|
65
|
-
|
|
66
|
-
inputSchema: z
|
|
67
|
-
.object({
|
|
68
|
-
cmd: z.string().describe('Shell command to run (bash -c <cmd>)'),
|
|
69
|
-
cwd: z
|
|
70
|
-
.string()
|
|
71
|
-
.default('.')
|
|
72
|
-
.describe('Working directory relative to project root'),
|
|
73
|
-
allowNonZeroExit: z
|
|
74
|
-
.boolean()
|
|
75
|
-
.optional()
|
|
76
|
-
.default(false)
|
|
77
|
-
.describe('If true, do not throw on non-zero exit'),
|
|
78
|
-
timeout: z
|
|
79
|
-
.number()
|
|
80
|
-
.optional()
|
|
81
|
-
.default(300000)
|
|
82
|
-
.describe('Timeout in milliseconds (default: 300000 = 5 minutes)'),
|
|
83
|
-
})
|
|
84
|
-
.strict(),
|
|
85
|
-
execute(
|
|
86
|
-
{
|
|
87
|
-
cmd,
|
|
88
|
-
cwd,
|
|
89
|
-
allowNonZeroExit,
|
|
90
|
-
timeout = 300000,
|
|
91
|
-
}: {
|
|
92
|
-
cmd: string;
|
|
93
|
-
cwd?: string;
|
|
94
|
-
allowNonZeroExit?: boolean;
|
|
95
|
-
timeout?: number;
|
|
96
|
-
},
|
|
97
|
-
options?: { abortSignal?: AbortSignal },
|
|
98
|
-
): AsyncIterable<BashStreamChunk> | BashResult {
|
|
99
|
-
const abortSignal = options?.abortSignal;
|
|
100
|
-
|
|
101
|
-
if (abortSignal?.aborted) {
|
|
102
|
-
return createToolError('Command aborted before execution', 'abort', {
|
|
103
|
-
cmd,
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const absCwd = resolveSafePath(projectRoot, cwd || '.');
|
|
108
|
-
const finalCmd = injectCoAuthorIntoGitCommit(cmd);
|
|
109
|
-
|
|
110
|
-
const proc = spawn(finalCmd, {
|
|
111
|
-
cwd: absCwd,
|
|
112
|
-
shell: true,
|
|
113
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
114
|
-
env: { ...process.env, PATH: getAugmentedPath() },
|
|
115
|
-
detached: true,
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
let stdout = '';
|
|
119
|
-
let stderr = '';
|
|
120
|
-
let didTimeout = false;
|
|
121
|
-
let didAbort = false;
|
|
122
|
-
let settled = false;
|
|
123
|
-
let done = false;
|
|
124
|
-
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
125
|
-
const queue: BashStreamChunk[] = [];
|
|
126
|
-
let notify: (() => void) | null = null;
|
|
127
|
-
|
|
128
|
-
const wake = () => {
|
|
129
|
-
if (!notify) return;
|
|
130
|
-
notify();
|
|
131
|
-
notify = null;
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
const pushDelta = (text: string) => {
|
|
135
|
-
if (!text) return;
|
|
136
|
-
queue.push({ channel: 'output', delta: text });
|
|
137
|
-
wake();
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const settle = (result: BashResult) => {
|
|
141
|
-
if (settled) return;
|
|
142
|
-
settled = true;
|
|
143
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
144
|
-
if (abortSignal) {
|
|
145
|
-
abortSignal.removeEventListener('abort', onAbort);
|
|
146
|
-
}
|
|
147
|
-
queue.push({ result });
|
|
148
|
-
done = true;
|
|
149
|
-
wake();
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
const onAbort = () => {
|
|
153
|
-
if (settled) return;
|
|
154
|
-
didAbort = true;
|
|
155
|
-
if (proc.pid) killProcessTree(proc.pid);
|
|
156
|
-
else proc.kill('SIGTERM');
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
if (abortSignal) {
|
|
160
|
-
abortSignal.addEventListener('abort', onAbort, { once: true });
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (timeout > 0) {
|
|
164
|
-
timeoutId = setTimeout(() => {
|
|
165
|
-
didTimeout = true;
|
|
166
|
-
if (proc.pid) killProcessTree(proc.pid);
|
|
167
|
-
else proc.kill();
|
|
168
|
-
}, timeout);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
proc.stdout?.on('data', (chunk) => {
|
|
172
|
-
const text = chunk.toString();
|
|
173
|
-
stdout += text;
|
|
174
|
-
pushDelta(text);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
proc.stderr?.on('data', (chunk) => {
|
|
178
|
-
const text = chunk.toString();
|
|
179
|
-
stderr += text;
|
|
180
|
-
pushDelta(text);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
proc.on('close', (exitCode) => {
|
|
184
|
-
if (didAbort) {
|
|
185
|
-
settle(
|
|
186
|
-
createToolError(`Command aborted by user: ${cmd}`, 'abort', {
|
|
187
|
-
cmd,
|
|
188
|
-
stdout,
|
|
189
|
-
stderr,
|
|
190
|
-
}),
|
|
191
|
-
);
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (didTimeout) {
|
|
196
|
-
settle(
|
|
197
|
-
createToolError(
|
|
198
|
-
`Command timed out after ${timeout}ms: ${cmd}`,
|
|
199
|
-
'timeout',
|
|
200
|
-
{
|
|
201
|
-
parameter: 'timeout',
|
|
202
|
-
value: timeout,
|
|
203
|
-
stdout,
|
|
204
|
-
stderr,
|
|
205
|
-
suggestion: 'Increase timeout or optimize the command',
|
|
206
|
-
},
|
|
207
|
-
),
|
|
208
|
-
);
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (exitCode !== 0 && !allowNonZeroExit) {
|
|
213
|
-
const errorDetail = stderr.trim() || stdout.trim() || '';
|
|
214
|
-
const errorMsg = `Command failed with exit code ${exitCode}${errorDetail ? `\n\n${errorDetail}` : ''}`;
|
|
215
|
-
settle(
|
|
216
|
-
createToolError(errorMsg, 'execution', {
|
|
217
|
-
exitCode,
|
|
218
|
-
stdout,
|
|
219
|
-
stderr,
|
|
220
|
-
cmd,
|
|
221
|
-
suggestion: 'Check command syntax or use allowNonZeroExit: true',
|
|
222
|
-
}),
|
|
223
|
-
);
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
settle({
|
|
228
|
-
ok: true,
|
|
229
|
-
exitCode: exitCode ?? 0,
|
|
230
|
-
stdout,
|
|
231
|
-
stderr,
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
proc.on('error', (err) => {
|
|
236
|
-
settle(
|
|
237
|
-
createToolError(
|
|
238
|
-
`Command execution failed: ${err.message}`,
|
|
239
|
-
'execution',
|
|
240
|
-
{
|
|
241
|
-
cmd,
|
|
242
|
-
originalError: err.message,
|
|
243
|
-
},
|
|
244
|
-
),
|
|
245
|
-
);
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
const stream = async function* (): AsyncGenerator<BashStreamChunk> {
|
|
249
|
-
while (!done || queue.length > 0) {
|
|
250
|
-
if (queue.length === 0) {
|
|
251
|
-
await new Promise<void>((resolve) => {
|
|
252
|
-
notify = resolve;
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
while (queue.length > 0) {
|
|
256
|
-
const chunk = queue.shift();
|
|
257
|
-
if (chunk) yield chunk;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
return stream();
|
|
263
|
-
},
|
|
264
|
-
}) as unknown as Tool;
|
|
265
|
-
return { name: 'bash', tool: bash };
|
|
266
|
-
}
|
|
1
|
+
export { buildShellTool, buildShellTool as buildBashTool } from './shell.ts';
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { tool, type Tool } from 'ai';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { z } from 'zod/v3';
|
|
4
|
+
import DESCRIPTION from './shell.txt' with { type: 'text' };
|
|
5
|
+
import { getAugmentedPath } from '../bin-manager.ts';
|
|
6
|
+
import { createToolError, type ToolResponse } from '../error.ts';
|
|
7
|
+
import { injectCoAuthorIntoGitCommit } from './git-identity.ts';
|
|
8
|
+
|
|
9
|
+
function normalizePath(p: string) {
|
|
10
|
+
const normalized = p.replace(/\\/g, '/');
|
|
11
|
+
const driveMatch = normalized.match(/^([A-Za-z]):\//);
|
|
12
|
+
const drivePrefix = driveMatch ? `${driveMatch[1]}:` : '';
|
|
13
|
+
const rest = driveMatch ? normalized.slice(2) : normalized;
|
|
14
|
+
const parts = rest.split('/');
|
|
15
|
+
const stack: string[] = [];
|
|
16
|
+
for (const part of parts) {
|
|
17
|
+
if (!part || part === '.') continue;
|
|
18
|
+
if (part === '..') stack.pop();
|
|
19
|
+
else stack.push(part);
|
|
20
|
+
}
|
|
21
|
+
if (drivePrefix) return `${drivePrefix}/${stack.join('/')}`;
|
|
22
|
+
return `/${stack.join('/')}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resolveSafePath(projectRoot: string, p: string) {
|
|
26
|
+
const root = normalizePath(projectRoot);
|
|
27
|
+
const abs = normalizePath(`${root}/${p || '.'}`);
|
|
28
|
+
if (!(abs === root || abs.startsWith(`${root}/`))) {
|
|
29
|
+
throw new Error(`cwd escapes project root: ${p}`);
|
|
30
|
+
}
|
|
31
|
+
return abs;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function killProcessTree(pid: number) {
|
|
35
|
+
try {
|
|
36
|
+
process.kill(-pid, 'SIGTERM');
|
|
37
|
+
} catch {
|
|
38
|
+
try {
|
|
39
|
+
process.kill(pid, 'SIGTERM');
|
|
40
|
+
} catch {}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type ShellResult = ToolResponse<{
|
|
45
|
+
exitCode: number;
|
|
46
|
+
stdout: string;
|
|
47
|
+
stderr: string;
|
|
48
|
+
}>;
|
|
49
|
+
|
|
50
|
+
type ShellStreamChunk =
|
|
51
|
+
| {
|
|
52
|
+
channel: 'output';
|
|
53
|
+
delta: string;
|
|
54
|
+
}
|
|
55
|
+
| {
|
|
56
|
+
result: ShellResult;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const shellInputSchema = z
|
|
60
|
+
.object({
|
|
61
|
+
cmd: z
|
|
62
|
+
.string()
|
|
63
|
+
.describe('Non-interactive shell command to run (bash -c <cmd>)'),
|
|
64
|
+
cwd: z
|
|
65
|
+
.string()
|
|
66
|
+
.default('.')
|
|
67
|
+
.describe('Working directory relative to project root'),
|
|
68
|
+
allowNonZeroExit: z
|
|
69
|
+
.boolean()
|
|
70
|
+
.optional()
|
|
71
|
+
.default(false)
|
|
72
|
+
.describe('If true, do not throw on non-zero exit'),
|
|
73
|
+
timeout: z
|
|
74
|
+
.number()
|
|
75
|
+
.optional()
|
|
76
|
+
.default(300000)
|
|
77
|
+
.describe('Timeout in milliseconds (default: 300000 = 5 minutes)'),
|
|
78
|
+
})
|
|
79
|
+
.strict();
|
|
80
|
+
|
|
81
|
+
type ShellInput = z.infer<typeof shellInputSchema>;
|
|
82
|
+
|
|
83
|
+
type ShellToolFactory = (definition: {
|
|
84
|
+
description: string;
|
|
85
|
+
inputSchema: typeof shellInputSchema;
|
|
86
|
+
execute(
|
|
87
|
+
input: ShellInput,
|
|
88
|
+
options?: { abortSignal?: AbortSignal },
|
|
89
|
+
): AsyncIterable<ShellStreamChunk> | ShellResult;
|
|
90
|
+
}) => Tool;
|
|
91
|
+
|
|
92
|
+
export function buildShellTool(projectRoot: string): {
|
|
93
|
+
name: string;
|
|
94
|
+
tool: Tool;
|
|
95
|
+
} {
|
|
96
|
+
const createTool = tool as unknown as ShellToolFactory;
|
|
97
|
+
const shell = createTool({
|
|
98
|
+
description: DESCRIPTION,
|
|
99
|
+
inputSchema: shellInputSchema,
|
|
100
|
+
execute(
|
|
101
|
+
{ cmd, cwd, allowNonZeroExit, timeout = 300000 }: ShellInput,
|
|
102
|
+
options?: { abortSignal?: AbortSignal },
|
|
103
|
+
): AsyncIterable<ShellStreamChunk> | ShellResult {
|
|
104
|
+
const abortSignal = options?.abortSignal;
|
|
105
|
+
|
|
106
|
+
if (abortSignal?.aborted) {
|
|
107
|
+
return createToolError('Command aborted before execution', 'abort', {
|
|
108
|
+
cmd,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const absCwd = resolveSafePath(projectRoot, cwd || '.');
|
|
113
|
+
const finalCmd = injectCoAuthorIntoGitCommit(cmd);
|
|
114
|
+
|
|
115
|
+
const proc = spawn(finalCmd, {
|
|
116
|
+
cwd: absCwd,
|
|
117
|
+
shell: true,
|
|
118
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
119
|
+
env: { ...process.env, PATH: getAugmentedPath() },
|
|
120
|
+
detached: true,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
let stdout = '';
|
|
124
|
+
let stderr = '';
|
|
125
|
+
let didTimeout = false;
|
|
126
|
+
let didAbort = false;
|
|
127
|
+
let settled = false;
|
|
128
|
+
let done = false;
|
|
129
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
130
|
+
const queue: ShellStreamChunk[] = [];
|
|
131
|
+
let notify: (() => void) | null = null;
|
|
132
|
+
|
|
133
|
+
const wake = () => {
|
|
134
|
+
if (!notify) return;
|
|
135
|
+
notify();
|
|
136
|
+
notify = null;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const pushDelta = (text: string) => {
|
|
140
|
+
if (!text) return;
|
|
141
|
+
queue.push({ channel: 'output', delta: text });
|
|
142
|
+
wake();
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const settle = (result: ShellResult) => {
|
|
146
|
+
if (settled) return;
|
|
147
|
+
settled = true;
|
|
148
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
149
|
+
if (abortSignal) {
|
|
150
|
+
abortSignal.removeEventListener('abort', onAbort);
|
|
151
|
+
}
|
|
152
|
+
queue.push({ result });
|
|
153
|
+
done = true;
|
|
154
|
+
wake();
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const onAbort = () => {
|
|
158
|
+
if (settled) return;
|
|
159
|
+
didAbort = true;
|
|
160
|
+
if (proc.pid) killProcessTree(proc.pid);
|
|
161
|
+
else proc.kill('SIGTERM');
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
if (abortSignal) {
|
|
165
|
+
abortSignal.addEventListener('abort', onAbort, { once: true });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (timeout > 0) {
|
|
169
|
+
timeoutId = setTimeout(() => {
|
|
170
|
+
didTimeout = true;
|
|
171
|
+
if (proc.pid) killProcessTree(proc.pid);
|
|
172
|
+
else proc.kill();
|
|
173
|
+
}, timeout);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
proc.stdout?.on('data', (chunk) => {
|
|
177
|
+
const text = chunk.toString();
|
|
178
|
+
stdout += text;
|
|
179
|
+
pushDelta(text);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
proc.stderr?.on('data', (chunk) => {
|
|
183
|
+
const text = chunk.toString();
|
|
184
|
+
stderr += text;
|
|
185
|
+
pushDelta(text);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
proc.on('close', (exitCode) => {
|
|
189
|
+
if (didAbort) {
|
|
190
|
+
settle(
|
|
191
|
+
createToolError(`Command aborted by user: ${cmd}`, 'abort', {
|
|
192
|
+
cmd,
|
|
193
|
+
stdout,
|
|
194
|
+
stderr,
|
|
195
|
+
}),
|
|
196
|
+
);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (didTimeout) {
|
|
201
|
+
settle(
|
|
202
|
+
createToolError(
|
|
203
|
+
`Command timed out after ${timeout}ms: ${cmd}`,
|
|
204
|
+
'timeout',
|
|
205
|
+
{
|
|
206
|
+
parameter: 'timeout',
|
|
207
|
+
value: timeout,
|
|
208
|
+
stdout,
|
|
209
|
+
stderr,
|
|
210
|
+
suggestion: 'Increase timeout or optimize the command',
|
|
211
|
+
},
|
|
212
|
+
),
|
|
213
|
+
);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (exitCode !== 0 && !allowNonZeroExit) {
|
|
218
|
+
const errorDetail = stderr.trim() || stdout.trim() || '';
|
|
219
|
+
const errorMsg = `Command failed with exit code ${exitCode}${errorDetail ? `\n\n${errorDetail}` : ''}`;
|
|
220
|
+
settle(
|
|
221
|
+
createToolError(errorMsg, 'execution', {
|
|
222
|
+
exitCode,
|
|
223
|
+
stdout,
|
|
224
|
+
stderr,
|
|
225
|
+
cmd,
|
|
226
|
+
suggestion: 'Check command syntax or use allowNonZeroExit: true',
|
|
227
|
+
}),
|
|
228
|
+
);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
settle({
|
|
233
|
+
ok: true,
|
|
234
|
+
exitCode: exitCode ?? 0,
|
|
235
|
+
stdout,
|
|
236
|
+
stderr,
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
proc.on('error', (err) => {
|
|
241
|
+
settle(
|
|
242
|
+
createToolError(
|
|
243
|
+
`Command execution failed: ${err.message}`,
|
|
244
|
+
'execution',
|
|
245
|
+
{
|
|
246
|
+
cmd,
|
|
247
|
+
originalError: err.message,
|
|
248
|
+
},
|
|
249
|
+
),
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const stream = async function* (): AsyncGenerator<ShellStreamChunk> {
|
|
254
|
+
while (!done || queue.length > 0) {
|
|
255
|
+
if (queue.length === 0) {
|
|
256
|
+
await new Promise<void>((resolve) => {
|
|
257
|
+
notify = resolve;
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
while (queue.length > 0) {
|
|
261
|
+
const chunk = queue.shift();
|
|
262
|
+
if (chunk) yield chunk;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
return stream();
|
|
268
|
+
},
|
|
269
|
+
}) as unknown as Tool;
|
|
270
|
+
return { name: 'shell', tool: shell };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export const buildBashTool = buildShellTool;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
- Execute a non-interactive 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
|
+
**Use `shell` for one-off, non-interactive commands.** These may be short-lived checks or long-running commands that finish on their own and do not require stdin. For commands that need interactive input, a TTY, or persistence across turns, use the `terminal` tool instead.
|
|
6
|
+
|
|
7
|
+
## Usage tips
|
|
8
|
+
|
|
9
|
+
- Chain commands with `&&` to fail-fast.
|
|
10
|
+
- For long outputs, redirect to a file and `read` it back.
|
|
11
|
+
- For long-running non-interactive commands, set an appropriate `timeout` and ensure the command exits on its own.
|
|
12
|
+
- Batch independent checks (e.g. `git status && git diff`) in parallel tool calls rather than sequential shell chains when you need results separately.
|
|
13
|
+
- Never use `shell` with `sed`/`awk` for programmatic file editing — use the dedicated file-editing tools instead.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
- Manage persistent terminals
|
|
1
|
+
- Manage interactive and persistent terminals (REPLs, prompts, dev servers, watchers)
|
|
2
2
|
- Returns terminal information and output
|
|
3
3
|
- Supports creating, reading, writing, listing, and killing terminals
|
|
4
4
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
### start
|
|
8
8
|
- Spawns a new persistent terminal (interactive shell by default)
|
|
9
9
|
- Returns terminal ID for future operations
|
|
10
|
-
- Use for
|
|
10
|
+
- Use for commands that need stdin, a TTY, interactive input, or persistence across turns
|
|
11
11
|
- Before starting, call `terminal(operation: "list")` to see if a matching service is already running
|
|
12
12
|
- Provide a clear `purpose` or `title` (e.g. "web dev server port 9100") so humans can recognize it
|
|
13
13
|
- Parameters:
|
|
@@ -51,22 +51,25 @@
|
|
|
51
51
|
- Parameters:
|
|
52
52
|
- terminalId (required): Terminal ID to kill
|
|
53
53
|
|
|
54
|
-
## When to Use Terminal vs
|
|
54
|
+
## When to Use Terminal vs Shell
|
|
55
55
|
|
|
56
56
|
### Use terminal for:
|
|
57
|
+
- Interactive commands that need stdin or a TTY: REPLs, prompts, shells, debuggers
|
|
57
58
|
- Dev servers: npm run dev, bun dev
|
|
58
59
|
- File watchers: bun test --watch, nodemon
|
|
59
60
|
- Build watchers: bun build --watch
|
|
60
61
|
- Log tailing: tail -f logs/app.log
|
|
61
62
|
- Background services: docker compose up
|
|
62
63
|
- Any process that needs to stay alive and produce continuous output
|
|
64
|
+
- Any process you need to read from or write to across turns
|
|
63
65
|
|
|
64
|
-
### Use
|
|
66
|
+
### Use shell for:
|
|
65
67
|
- Status checks: git status, ls, ps
|
|
66
68
|
- One-off commands: mkdir, rm, curl
|
|
67
69
|
- Quick scripts: bun run build, git commit
|
|
68
70
|
- File operations: cat, sed, awk
|
|
69
|
-
-
|
|
71
|
+
- Non-interactive long-running commands that finish on their own (set an appropriate timeout)
|
|
72
|
+
- Any command that does not need stdin, a TTY, or follow-up interaction
|
|
70
73
|
|
|
71
74
|
## Example Workflow
|
|
72
75
|
|
|
@@ -83,7 +86,7 @@
|
|
|
83
86
|
|
|
84
87
|
## Notes
|
|
85
88
|
|
|
86
|
-
- Terminals persist across multiple LLM turns (unlike
|
|
89
|
+
- Terminals persist across multiple LLM turns (unlike shell commands)
|
|
87
90
|
- Maximum 10 terminals per session
|
|
88
91
|
- Exited terminals auto-cleanup after 5 minutes
|
|
89
92
|
- Output is buffered (last 500 lines kept)
|
|
@@ -4,7 +4,7 @@ import { finishTool } from './builtin/finish.ts';
|
|
|
4
4
|
import { buildFsTools } from './builtin/fs/index.ts';
|
|
5
5
|
import { buildGitTools } from './builtin/git.ts';
|
|
6
6
|
import { progressUpdateTool } from './builtin/progress.ts';
|
|
7
|
-
import {
|
|
7
|
+
import { buildShellTool } from './builtin/shell.ts';
|
|
8
8
|
import { buildRipgrepTool } from './builtin/ripgrep.ts';
|
|
9
9
|
import { buildGlobTool } from './builtin/glob.ts';
|
|
10
10
|
import { buildApplyPatchTool } from './builtin/patch.ts';
|
|
@@ -147,8 +147,8 @@ async function discoverStaticProjectTools(
|
|
|
147
147
|
// Built-ins
|
|
148
148
|
tools.set('finish', finishTool);
|
|
149
149
|
tools.set('progress_update', progressUpdateTool);
|
|
150
|
-
const
|
|
151
|
-
tools.set(
|
|
150
|
+
const shell = buildShellTool(projectRoot);
|
|
151
|
+
tools.set(shell.name, shell.tool);
|
|
152
152
|
// Search
|
|
153
153
|
const rg = buildRipgrepTool(projectRoot);
|
|
154
154
|
tools.set(rg.name, rg.tool);
|
package/src/index.ts
CHANGED
|
@@ -40,6 +40,18 @@ export type {
|
|
|
40
40
|
// Providers (from internal providers module)
|
|
41
41
|
// =======================
|
|
42
42
|
export { catalog } from './providers/src/index.ts';
|
|
43
|
+
export {
|
|
44
|
+
DEFAULT_REMOTE_MODEL_CATALOG_URL,
|
|
45
|
+
getModelCatalogCachePath,
|
|
46
|
+
mergeCachedModelCatalog,
|
|
47
|
+
normalizeModelCatalogPayload,
|
|
48
|
+
readCachedModelCatalog,
|
|
49
|
+
writeCachedModelCatalog,
|
|
50
|
+
} from './providers/src/index.ts';
|
|
51
|
+
export type {
|
|
52
|
+
CachedModelCatalog,
|
|
53
|
+
CachedProviderCatalogEntry,
|
|
54
|
+
} from './providers/src/index.ts';
|
|
43
55
|
export {
|
|
44
56
|
isProviderId,
|
|
45
57
|
providerIds,
|
|
@@ -18,13 +18,13 @@ Pick the right tool for the job (each tool's description has its full contract):
|
|
|
18
18
|
|
|
19
19
|
After making changes:
|
|
20
20
|
|
|
21
|
-
1. Run project-specific build/lint/test commands with `
|
|
21
|
+
1. Run project-specific build/lint/test commands with `shell` (check `package.json`, `README.md`, or `AGENTS.md` for the right command).
|
|
22
22
|
2. Review diffs with `git_status` / `git_diff`.
|
|
23
23
|
3. Do NOT commit unless the user explicitly asks. It is very important to only commit when asked.
|
|
24
24
|
|
|
25
25
|
## Terminal tool — when to use
|
|
26
26
|
|
|
27
|
-
- Prefer `terminal` over `
|
|
27
|
+
- Prefer `terminal` over `shell` for interactive or persistent processes (dev servers, watchers, log tailing). `shell` is for one-off non-interactive commands.
|
|
28
28
|
- List existing terminals before starting a new one; reuse when possible to avoid duplicate services.
|
|
29
29
|
- Give each terminal a clear `purpose` / `title` (e.g. "web dev server 9100").
|
|
30
30
|
- Mention active terminals (purpose, command, port) in your responses so humans know what's running.
|
|
@@ -4,7 +4,7 @@ GUIDED MODE is active. This OVERRIDES all conciseness and brevity instructions.
|
|
|
4
4
|
The user is NOT a developer. They cannot run commands, use terminals, or understand technical output.
|
|
5
5
|
|
|
6
6
|
YOU MUST:
|
|
7
|
-
- USE YOUR TOOLS to execute every action. Never tell the user to run a command — you run it yourself with
|
|
7
|
+
- USE YOUR TOOLS to execute every action. Never tell the user to run a command — you run it yourself with shell or terminal tools.
|
|
8
8
|
- NEVER ask permission or say "Want me to...?" — just do it immediately.
|
|
9
9
|
- NEVER respond with just instructions or steps. Every response must include actual tool calls that do the work.
|
|
10
10
|
- Check if services are already running before starting them. If not running, start them yourself.
|
|
@@ -12,7 +12,7 @@ YOU MUST:
|
|
|
12
12
|
- Tell the user the exact URL to open and what they'll see there.
|
|
13
13
|
- Explain what you DID (past tense) in simple language — not what they should do.
|
|
14
14
|
- If something fails, fix it yourself silently. Only tell the user once it's resolved.
|
|
15
|
-
- Use the terminal tool (not
|
|
15
|
+
- Use the terminal tool (not shell) for interactive or persistent services so they persist.
|
|
16
16
|
- Verbose, friendly responses are expected in this mode — ignore any "be concise" instructions.
|
|
17
17
|
|
|
18
18
|
WRONG response: "Run `bun dev` in the project root, then open http://localhost:9100"
|
|
@@ -62,7 +62,7 @@ user: which file contains the implementation of foo?
|
|
|
62
62
|
assistant: src/foo.c
|
|
63
63
|
</example>
|
|
64
64
|
|
|
65
|
-
When you run a non-trivial
|
|
65
|
+
When you run a non-trivial shell command — especially one that changes state — briefly explain what it does and why, so the user knows what's happening.
|
|
66
66
|
|
|
67
67
|
# Proactiveness
|
|
68
68
|
|
|
@@ -99,7 +99,7 @@ For software engineering requests (bugs, features, refactors, explanations):
|
|
|
99
99
|
1. Use `update_todos` to plan multi-step work.
|
|
100
100
|
2. Explore the codebase with the search tools (`glob`, `ripgrep`, `tree`, `read`). Batch independent searches in parallel.
|
|
101
101
|
3. Implement the solution.
|
|
102
|
-
4. Verify — run the project's build/lint/test commands with `
|
|
102
|
+
4. Verify — run the project's build/lint/test commands with `shell`. Check `README.md` / `AGENTS.md` to find the right command.
|
|
103
103
|
5. Review diffs with `git_status` / `git_diff`.
|
|
104
104
|
6. NEVER commit unless the user explicitly asks.
|
|
105
105
|
|
|
@@ -27,7 +27,7 @@ You are a coding agent running in otto, a terminal-based coding assistant. Preci
|
|
|
27
27
|
1. Understand — use `glob`, `ripgrep`, `tree`, `read` to map the code. Batch independent searches.
|
|
28
28
|
2. Plan — use `update_todos` for multi-step work. Mark one step `in_progress` at a time.
|
|
29
29
|
3. Implement — prefer the targeted editing tools available to you for small in-file changes; use patch-style edits for structural or multi-file changes when available; use `write` only for new files or near-total rewrites.
|
|
30
|
-
4. Verify — run project-specific build/lint/test commands via `
|
|
30
|
+
4. Verify — run project-specific build/lint/test commands via `shell`. Check `README.md` / `AGENTS.md` for the right command.
|
|
31
31
|
5. Review — `git_status` / `git_diff`. Do NOT commit unless asked.
|
|
32
32
|
|
|
33
33
|
# Direct file references
|
|
@@ -39,7 +39,7 @@ Your reasoning is powerful, but it can override what you actually read. Guard ag
|
|
|
39
39
|
1. Understand — use `glob`, `ripgrep`, `tree`, `read` to map the code. Batch independent searches.
|
|
40
40
|
2. Plan — use `update_todos` for multi-step work. Mark one step `in_progress` at a time.
|
|
41
41
|
3. Implement — prefer the targeted editing tools available to you for small in-file changes; use patch-style edits for structural or multi-file changes when available; use `write` only for new files or near-total rewrites.
|
|
42
|
-
4. Verify — run project-specific build/lint/test commands via `
|
|
42
|
+
4. Verify — run project-specific build/lint/test commands via `shell`. Check `README.md` / `AGENTS.md` for the right command.
|
|
43
43
|
5. Review — `git_status` / `git_diff`. Do NOT commit unless asked.
|
|
44
44
|
|
|
45
45
|
# Direct file references
|
|
@@ -30,7 +30,7 @@ For bug fixes, features, refactors, or explanations:
|
|
|
30
30
|
1. **Understand requirements** — features, UX, platform, constraints. Ask targeted clarification questions if critical info is missing.
|
|
31
31
|
2. **Propose plan.** Present a concise high-level summary (type, core purpose, key technologies, main features, visual/UX approach). For visual assets, describe the strategy for placeholders (geometric shapes, procedural patterns, open-source assets).
|
|
32
32
|
3. **User approval.** Obtain approval before implementing.
|
|
33
|
-
4. **Implement** autonomously. Scaffold with `
|
|
33
|
+
4. **Implement** autonomously. Scaffold with `shell` (`npm init`, `npx create-react-app`, etc.). Create placeholder assets when needed; aim for a visually coherent prototype.
|
|
34
34
|
5. **Verify** against the original request and approved plan. Fix bugs, deviations, placeholders. Ensure the build produces no compile errors.
|
|
35
35
|
6. **Solicit feedback** — provide start-up instructions and ask for feedback.
|
|
36
36
|
|
|
@@ -48,14 +48,14 @@ For bug fixes, features, refactors, or explanations:
|
|
|
48
48
|
|
|
49
49
|
## Security and safety
|
|
50
50
|
|
|
51
|
-
- **Explain critical commands.** Before executing `
|
|
51
|
+
- **Explain critical commands.** Before executing `shell` commands that modify the file system or system state, briefly explain purpose and potential impact. Don't ask permission — the user will confirm via dialog.
|
|
52
52
|
- **Security first.** Apply security best practices. Never expose, log, or commit secrets, API keys, or sensitive information.
|
|
53
53
|
|
|
54
54
|
## Tool usage
|
|
55
55
|
|
|
56
56
|
- **Parallelism.** Execute multiple independent tool calls in parallel when feasible (e.g. codebase searches).
|
|
57
|
-
- **Command execution.** Use `
|
|
58
|
-
- **Background processes.**
|
|
57
|
+
- **Command execution.** Use `shell` for non-interactive shell commands; explain modifying commands first.
|
|
58
|
+
- **Background processes.** Prefer the `terminal` tool for interactive or persistent processes you'll monitor over multiple turns. Use `shell` only for non-interactive commands that finish on their own.
|
|
59
59
|
- **Interactive commands.** Avoid commands that require user interaction (e.g. `git rebase -i`). Prefer non-interactive versions (`npm init -y`).
|
|
60
60
|
- **Confirmations.** If the user cancels a tool call, respect their choice — don't retry unless they ask again.
|
|
61
61
|
|
|
@@ -78,7 +78,7 @@ model: [tool_call: ls for path '/path/to/project']
|
|
|
78
78
|
|
|
79
79
|
<example>
|
|
80
80
|
user: start the server implemented in server.js
|
|
81
|
-
model: [tool_call:
|
|
81
|
+
model: [tool_call: terminal for 'node server.js' because it must stay running]
|
|
82
82
|
</example>
|
|
83
83
|
|
|
84
84
|
<example>
|
|
@@ -105,7 +105,7 @@ user: Yes
|
|
|
105
105
|
model:
|
|
106
106
|
[tool_call: use the appropriate available file-editing tool to apply the refactoring to 'src/auth.py']
|
|
107
107
|
Refactoring complete. Running verification...
|
|
108
|
-
[tool_call:
|
|
108
|
+
[tool_call: shell for 'ruff check src/auth.py && pytest']
|
|
109
109
|
(After verification passes)
|
|
110
110
|
All checks passed. This is a stable checkpoint.
|
|
111
111
|
</example>
|
|
@@ -125,7 +125,7 @@ Now I'll look for existing or related test files to understand current testing c
|
|
|
125
125
|
(After reviewing existing tests and the file content)
|
|
126
126
|
[tool_call: write to create /path/to/someFile.test.ts with the test code]
|
|
127
127
|
I've written the tests. Now I'll run the project's test command to verify them.
|
|
128
|
-
[tool_call:
|
|
128
|
+
[tool_call: shell for 'npm run test']
|
|
129
129
|
</example>
|
|
130
130
|
|
|
131
131
|
<example>
|
|
@@ -39,7 +39,7 @@ Your reasoning is powerful, but it can override what you actually read. Guard ag
|
|
|
39
39
|
1. Understand — use `glob`, `ripgrep`, `tree`, `read` to map the code. Batch independent searches.
|
|
40
40
|
2. Plan — use `update_todos` for multi-step work. Mark one step `in_progress` at a time.
|
|
41
41
|
3. Implement — prefer the targeted editing tools available to you for small in-file changes; use patch-style edits for structural or multi-file changes when available; use `write` only for new files or near-total rewrites.
|
|
42
|
-
4. Verify — run project-specific build/lint/test commands via `
|
|
42
|
+
4. Verify — run project-specific build/lint/test commands via `shell`. Check `README.md` / `AGENTS.md` for the right command.
|
|
43
43
|
5. Review — `git_status` / `git_diff`. Do NOT commit unless asked.
|
|
44
44
|
|
|
45
45
|
# Direct file references
|
|
@@ -881,6 +881,32 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
|
|
|
881
881
|
output: 128000,
|
|
882
882
|
},
|
|
883
883
|
},
|
|
884
|
+
{
|
|
885
|
+
id: 'gpt-5.5',
|
|
886
|
+
ownedBy: 'openai',
|
|
887
|
+
label: 'GPT-5.5',
|
|
888
|
+
modalities: {
|
|
889
|
+
input: ['text', 'image', 'pdf'],
|
|
890
|
+
output: ['text'],
|
|
891
|
+
},
|
|
892
|
+
toolCall: true,
|
|
893
|
+
reasoningText: true,
|
|
894
|
+
attachment: true,
|
|
895
|
+
temperature: false,
|
|
896
|
+
knowledge: '2025-12-01',
|
|
897
|
+
releaseDate: '2026-04-23',
|
|
898
|
+
lastUpdated: '2026-04-23',
|
|
899
|
+
openWeights: false,
|
|
900
|
+
cost: {
|
|
901
|
+
input: 5,
|
|
902
|
+
output: 30,
|
|
903
|
+
cacheRead: 0.5,
|
|
904
|
+
},
|
|
905
|
+
limit: {
|
|
906
|
+
context: 1050000,
|
|
907
|
+
output: 130000,
|
|
908
|
+
},
|
|
909
|
+
},
|
|
884
910
|
{
|
|
885
911
|
id: 'gpt-image-1',
|
|
886
912
|
ownedBy: 'openai',
|
|
@@ -2813,7 +2839,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
|
|
|
2813
2839
|
},
|
|
2814
2840
|
},
|
|
2815
2841
|
{
|
|
2816
|
-
id: 'gemma-4-26b-it',
|
|
2842
|
+
id: 'gemma-4-26b-a4b-it',
|
|
2817
2843
|
ownedBy: 'google',
|
|
2818
2844
|
label: 'Gemma 4 26B',
|
|
2819
2845
|
modalities: {
|
|
@@ -4934,6 +4960,31 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
|
|
|
4934
4960
|
output: 262144,
|
|
4935
4961
|
},
|
|
4936
4962
|
},
|
|
4963
|
+
{
|
|
4964
|
+
id: 'moonshotai/kimi-k2.6',
|
|
4965
|
+
ownedBy: 'moonshot',
|
|
4966
|
+
label: 'Kimi K2.6',
|
|
4967
|
+
modalities: {
|
|
4968
|
+
input: ['text', 'image'],
|
|
4969
|
+
output: ['text'],
|
|
4970
|
+
},
|
|
4971
|
+
toolCall: true,
|
|
4972
|
+
reasoningText: true,
|
|
4973
|
+
attachment: true,
|
|
4974
|
+
temperature: true,
|
|
4975
|
+
releaseDate: '2026-04-20',
|
|
4976
|
+
lastUpdated: '2026-04-20',
|
|
4977
|
+
openWeights: true,
|
|
4978
|
+
cost: {
|
|
4979
|
+
input: 0.95,
|
|
4980
|
+
output: 4,
|
|
4981
|
+
cacheRead: 0.16,
|
|
4982
|
+
},
|
|
4983
|
+
limit: {
|
|
4984
|
+
context: 262144,
|
|
4985
|
+
output: 262144,
|
|
4986
|
+
},
|
|
4987
|
+
},
|
|
4937
4988
|
{
|
|
4938
4989
|
id: 'nousresearch/hermes-3-llama-3.1-405b:free',
|
|
4939
4990
|
label: 'Hermes 3 405B Instruct (free)',
|
|
@@ -5707,9 +5758,9 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
|
|
|
5707
5758
|
lastUpdated: '2026-03-17',
|
|
5708
5759
|
openWeights: false,
|
|
5709
5760
|
cost: {
|
|
5710
|
-
input:
|
|
5711
|
-
output:
|
|
5712
|
-
cacheRead:
|
|
5761
|
+
input: 0.75,
|
|
5762
|
+
output: 4.5,
|
|
5763
|
+
cacheRead: 0.075,
|
|
5713
5764
|
},
|
|
5714
5765
|
limit: {
|
|
5715
5766
|
context: 400000,
|
|
@@ -5733,9 +5784,9 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
|
|
|
5733
5784
|
lastUpdated: '2026-03-17',
|
|
5734
5785
|
openWeights: false,
|
|
5735
5786
|
cost: {
|
|
5736
|
-
input:
|
|
5737
|
-
output:
|
|
5738
|
-
cacheRead:
|
|
5787
|
+
input: 0.2,
|
|
5788
|
+
output: 1.25,
|
|
5789
|
+
cacheRead: 0.02,
|
|
5739
5790
|
},
|
|
5740
5791
|
limit: {
|
|
5741
5792
|
context: 400000,
|
|
@@ -8141,6 +8192,31 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
|
|
|
8141
8192
|
output: 256000,
|
|
8142
8193
|
},
|
|
8143
8194
|
},
|
|
8195
|
+
{
|
|
8196
|
+
id: 'hy3-preview-free',
|
|
8197
|
+
label: 'Hy3 preview Free',
|
|
8198
|
+
modalities: {
|
|
8199
|
+
input: ['text'],
|
|
8200
|
+
output: ['text'],
|
|
8201
|
+
},
|
|
8202
|
+
toolCall: true,
|
|
8203
|
+
reasoningText: true,
|
|
8204
|
+
attachment: false,
|
|
8205
|
+
temperature: true,
|
|
8206
|
+
knowledge: '2025-06',
|
|
8207
|
+
releaseDate: '2026-04-20',
|
|
8208
|
+
lastUpdated: '2026-04-20',
|
|
8209
|
+
openWeights: true,
|
|
8210
|
+
cost: {
|
|
8211
|
+
input: 0,
|
|
8212
|
+
output: 0,
|
|
8213
|
+
cacheRead: 0,
|
|
8214
|
+
},
|
|
8215
|
+
limit: {
|
|
8216
|
+
context: 256000,
|
|
8217
|
+
output: 64000,
|
|
8218
|
+
},
|
|
8219
|
+
},
|
|
8144
8220
|
{
|
|
8145
8221
|
id: 'kimi-k2',
|
|
8146
8222
|
ownedBy: 'moonshot',
|
|
@@ -8245,6 +8321,56 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
|
|
|
8245
8321
|
output: 262144,
|
|
8246
8322
|
},
|
|
8247
8323
|
},
|
|
8324
|
+
{
|
|
8325
|
+
id: 'kimi-k2.6',
|
|
8326
|
+
ownedBy: 'moonshot',
|
|
8327
|
+
label: 'Kimi K2.6',
|
|
8328
|
+
modalities: {
|
|
8329
|
+
input: ['text', 'image', 'video'],
|
|
8330
|
+
output: ['text'],
|
|
8331
|
+
},
|
|
8332
|
+
toolCall: true,
|
|
8333
|
+
reasoningText: true,
|
|
8334
|
+
attachment: true,
|
|
8335
|
+
temperature: true,
|
|
8336
|
+
knowledge: '2024-10',
|
|
8337
|
+
releaseDate: '2026-04-21',
|
|
8338
|
+
lastUpdated: '2026-04-21',
|
|
8339
|
+
openWeights: true,
|
|
8340
|
+
cost: {
|
|
8341
|
+
input: 0.95,
|
|
8342
|
+
output: 4,
|
|
8343
|
+
cacheRead: 0.16,
|
|
8344
|
+
},
|
|
8345
|
+
limit: {
|
|
8346
|
+
context: 262144,
|
|
8347
|
+
output: 65536,
|
|
8348
|
+
},
|
|
8349
|
+
},
|
|
8350
|
+
{
|
|
8351
|
+
id: 'ling-2.6-flash-free',
|
|
8352
|
+
label: 'Ling 2.6 Flash Free',
|
|
8353
|
+
modalities: {
|
|
8354
|
+
input: ['text'],
|
|
8355
|
+
output: ['text'],
|
|
8356
|
+
},
|
|
8357
|
+
toolCall: true,
|
|
8358
|
+
reasoningText: false,
|
|
8359
|
+
attachment: false,
|
|
8360
|
+
temperature: true,
|
|
8361
|
+
knowledge: '2025-06',
|
|
8362
|
+
releaseDate: '2026-04-21',
|
|
8363
|
+
lastUpdated: '2026-04-21',
|
|
8364
|
+
openWeights: true,
|
|
8365
|
+
cost: {
|
|
8366
|
+
input: 0,
|
|
8367
|
+
output: 0,
|
|
8368
|
+
},
|
|
8369
|
+
limit: {
|
|
8370
|
+
context: 262100,
|
|
8371
|
+
output: 32800,
|
|
8372
|
+
},
|
|
8373
|
+
},
|
|
8248
8374
|
{
|
|
8249
8375
|
id: 'mimo-v2-flash-free',
|
|
8250
8376
|
label: 'MiMo V2 Flash Free',
|
|
@@ -8430,6 +8556,32 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
|
|
|
8430
8556
|
npm: '@ai-sdk/anthropic',
|
|
8431
8557
|
},
|
|
8432
8558
|
},
|
|
8559
|
+
{
|
|
8560
|
+
id: 'minimax-m2.7',
|
|
8561
|
+
ownedBy: 'minimax',
|
|
8562
|
+
label: 'MiniMax M2.7',
|
|
8563
|
+
modalities: {
|
|
8564
|
+
input: ['text'],
|
|
8565
|
+
output: ['text'],
|
|
8566
|
+
},
|
|
8567
|
+
toolCall: true,
|
|
8568
|
+
reasoningText: true,
|
|
8569
|
+
attachment: false,
|
|
8570
|
+
temperature: true,
|
|
8571
|
+
knowledge: '2025-01',
|
|
8572
|
+
releaseDate: '2026-03-18',
|
|
8573
|
+
lastUpdated: '2026-03-18',
|
|
8574
|
+
openWeights: true,
|
|
8575
|
+
cost: {
|
|
8576
|
+
input: 0.3,
|
|
8577
|
+
output: 1.2,
|
|
8578
|
+
cacheRead: 0.06,
|
|
8579
|
+
},
|
|
8580
|
+
limit: {
|
|
8581
|
+
context: 204800,
|
|
8582
|
+
output: 131072,
|
|
8583
|
+
},
|
|
8584
|
+
},
|
|
8433
8585
|
{
|
|
8434
8586
|
id: 'nemotron-3-super-free',
|
|
8435
8587
|
label: 'Nemotron 3 Super Free',
|
|
@@ -8505,7 +8657,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
|
|
|
8505
8657
|
output: 65536,
|
|
8506
8658
|
},
|
|
8507
8659
|
provider: {
|
|
8508
|
-
npm: '@ai-sdk/
|
|
8660
|
+
npm: '@ai-sdk/anthropic',
|
|
8509
8661
|
},
|
|
8510
8662
|
},
|
|
8511
8663
|
{
|
|
@@ -8534,7 +8686,7 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
|
|
|
8534
8686
|
output: 65536,
|
|
8535
8687
|
},
|
|
8536
8688
|
provider: {
|
|
8537
|
-
npm: '@ai-sdk/
|
|
8689
|
+
npm: '@ai-sdk/anthropic',
|
|
8538
8690
|
},
|
|
8539
8691
|
},
|
|
8540
8692
|
{
|
|
@@ -9458,6 +9610,32 @@ export const catalog: Partial<Record<BuiltInProviderId, ProviderCatalogEntry>> =
|
|
|
9458
9610
|
output: 262144,
|
|
9459
9611
|
},
|
|
9460
9612
|
},
|
|
9613
|
+
{
|
|
9614
|
+
id: 'kimi-k2.6',
|
|
9615
|
+
ownedBy: 'moonshot',
|
|
9616
|
+
label: 'Kimi K2.6',
|
|
9617
|
+
modalities: {
|
|
9618
|
+
input: ['text', 'image', 'video'],
|
|
9619
|
+
output: ['text'],
|
|
9620
|
+
},
|
|
9621
|
+
toolCall: true,
|
|
9622
|
+
reasoningText: true,
|
|
9623
|
+
attachment: true,
|
|
9624
|
+
temperature: true,
|
|
9625
|
+
knowledge: '2025-01',
|
|
9626
|
+
releaseDate: '2026-04-21',
|
|
9627
|
+
lastUpdated: '2026-04-21',
|
|
9628
|
+
openWeights: true,
|
|
9629
|
+
cost: {
|
|
9630
|
+
input: 0.95,
|
|
9631
|
+
output: 4,
|
|
9632
|
+
cacheRead: 0.16,
|
|
9633
|
+
},
|
|
9634
|
+
limit: {
|
|
9635
|
+
context: 262144,
|
|
9636
|
+
output: 262144,
|
|
9637
|
+
},
|
|
9638
|
+
},
|
|
9461
9639
|
],
|
|
9462
9640
|
label: 'Moonshot AI',
|
|
9463
9641
|
env: ['MOONSHOT_API_KEY'],
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
export { isProviderAuthorized, ensureProviderEnv } from './authorization.ts';
|
|
2
2
|
export { catalog } from './catalog-merged.ts';
|
|
3
|
+
export {
|
|
4
|
+
DEFAULT_REMOTE_MODEL_CATALOG_URL,
|
|
5
|
+
getModelCatalogCachePath,
|
|
6
|
+
mergeCachedModelCatalog,
|
|
7
|
+
normalizeModelCatalogPayload,
|
|
8
|
+
readCachedModelCatalog,
|
|
9
|
+
writeCachedModelCatalog,
|
|
10
|
+
} from './model-catalog-cache.ts';
|
|
11
|
+
export type {
|
|
12
|
+
CachedModelCatalog,
|
|
13
|
+
CachedProviderCatalogEntry,
|
|
14
|
+
} from './model-catalog-cache.ts';
|
|
3
15
|
export type {
|
|
4
16
|
BuiltInProviderId,
|
|
5
17
|
ProviderId,
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { getGlobalConfigDir, joinPath } from '../../config/src/paths.ts';
|
|
2
|
+
import type { ModelInfo, ProviderId } from '../../types/src/index.ts';
|
|
3
|
+
|
|
4
|
+
export type CachedProviderCatalogEntry = {
|
|
5
|
+
id: ProviderId;
|
|
6
|
+
label?: string;
|
|
7
|
+
models: ModelInfo[];
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type CachedModelCatalog = {
|
|
11
|
+
version: 1;
|
|
12
|
+
updatedAt: string;
|
|
13
|
+
providers: Record<string, CachedProviderCatalogEntry>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_REMOTE_MODEL_CATALOG_URL =
|
|
17
|
+
'https://ottocode.io/catalog/models.json';
|
|
18
|
+
|
|
19
|
+
export function getModelCatalogCachePath(): string {
|
|
20
|
+
return joinPath(getGlobalConfigDir(), 'models-catalog.json');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
24
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizeProviderEntry(
|
|
28
|
+
id: string,
|
|
29
|
+
value: unknown,
|
|
30
|
+
): CachedProviderCatalogEntry | null {
|
|
31
|
+
if (!isRecord(value)) return null;
|
|
32
|
+
const models = Array.isArray(value.models)
|
|
33
|
+
? value.models.filter(
|
|
34
|
+
(model): model is ModelInfo =>
|
|
35
|
+
isRecord(model) && typeof model.id === 'string',
|
|
36
|
+
)
|
|
37
|
+
: [];
|
|
38
|
+
return {
|
|
39
|
+
id: (typeof value.id === 'string' ? value.id : id) as ProviderId,
|
|
40
|
+
label: typeof value.label === 'string' ? value.label : undefined,
|
|
41
|
+
models,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function normalizeModelCatalogPayload(
|
|
46
|
+
payload: unknown,
|
|
47
|
+
): Record<string, CachedProviderCatalogEntry> {
|
|
48
|
+
if (!isRecord(payload)) return {};
|
|
49
|
+
const source = isRecord(payload.providers) ? payload.providers : payload;
|
|
50
|
+
const providers: Record<string, CachedProviderCatalogEntry> = {};
|
|
51
|
+
for (const [id, value] of Object.entries(source)) {
|
|
52
|
+
const entry = normalizeProviderEntry(id, value);
|
|
53
|
+
if (entry) providers[id] = entry;
|
|
54
|
+
}
|
|
55
|
+
return providers;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function readCachedModelCatalog(): Promise<CachedModelCatalog | null> {
|
|
59
|
+
try {
|
|
60
|
+
const file = Bun.file(getModelCatalogCachePath());
|
|
61
|
+
if (!(await file.exists())) return null;
|
|
62
|
+
const payload = await file.json();
|
|
63
|
+
const providers = normalizeModelCatalogPayload(payload);
|
|
64
|
+
return {
|
|
65
|
+
version: 1,
|
|
66
|
+
updatedAt:
|
|
67
|
+
isRecord(payload) && typeof payload.updatedAt === 'string'
|
|
68
|
+
? payload.updatedAt
|
|
69
|
+
: new Date(0).toISOString(),
|
|
70
|
+
providers,
|
|
71
|
+
};
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function writeCachedModelCatalog(
|
|
78
|
+
providers: Record<string, CachedProviderCatalogEntry>,
|
|
79
|
+
): Promise<void> {
|
|
80
|
+
const path = getModelCatalogCachePath();
|
|
81
|
+
const dir = path.slice(0, path.lastIndexOf('/'));
|
|
82
|
+
await import('node:fs/promises').then((fs) =>
|
|
83
|
+
fs.mkdir(dir, { recursive: true }),
|
|
84
|
+
);
|
|
85
|
+
await Bun.write(
|
|
86
|
+
path,
|
|
87
|
+
JSON.stringify(
|
|
88
|
+
{
|
|
89
|
+
version: 1,
|
|
90
|
+
updatedAt: new Date().toISOString(),
|
|
91
|
+
providers,
|
|
92
|
+
},
|
|
93
|
+
null,
|
|
94
|
+
2,
|
|
95
|
+
),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function mergeCachedModelCatalog(
|
|
100
|
+
providers: Record<string, CachedProviderCatalogEntry>,
|
|
101
|
+
): Promise<void> {
|
|
102
|
+
const existing = await readCachedModelCatalog();
|
|
103
|
+
await writeCachedModelCatalog({
|
|
104
|
+
...(existing?.providers ?? {}),
|
|
105
|
+
...providers,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
@@ -133,7 +133,7 @@ export function getProviderDefinition(
|
|
|
133
133
|
source: 'built-in',
|
|
134
134
|
compatibility: BUILTIN_COMPATIBILITY[provider],
|
|
135
135
|
family: BUILTIN_FAMILY[provider],
|
|
136
|
-
baseURL: normalizeOptionalText(settings?.baseURL),
|
|
136
|
+
baseURL: normalizeOptionalText(settings?.baseURL) ?? entry.api,
|
|
137
137
|
apiKey: normalizeOptionalText(settings?.apiKey),
|
|
138
138
|
apiKeyEnv:
|
|
139
139
|
normalizeOptionalText(settings?.apiKeyEnv) ?? providerEnvVar(provider),
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
- Execute a one-off 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
|
-
**Use `bash` for short-lived commands.** For long-running processes (dev servers, watchers, log tailing) use the `terminal` tool instead — terminals persist across turns.
|
|
6
|
-
|
|
7
|
-
## Usage tips
|
|
8
|
-
|
|
9
|
-
- Chain commands with `&&` to fail-fast.
|
|
10
|
-
- For long outputs, redirect to a file and `read` it back.
|
|
11
|
-
- Batch independent checks (e.g. `git status && git diff`) in parallel tool calls rather than sequential bash chains when you need results separately.
|
|
12
|
-
- Never use `bash` with `sed`/`awk` for programmatic file editing — use the dedicated file-editing tools instead.
|