@ottocode/sdk 0.1.228 → 0.1.230
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 +1 -1
- package/src/config/src/index.ts +1 -0
- package/src/config/src/manager.ts +1 -0
- package/src/core/src/terminals/rust-libs.ts +0 -5
- package/src/core/src/tools/builtin/bash.ts +154 -108
- package/src/index.ts +1 -0
- package/src/prompts/src/providers.ts +40 -12
- package/src/tunnel/qr.ts +0 -1
- package/src/types/src/config.ts +8 -0
- package/src/types/src/index.ts +1 -0
package/package.json
CHANGED
package/src/config/src/index.ts
CHANGED
|
@@ -1,20 +1,15 @@
|
|
|
1
|
-
// @ts-expect-error Bun file asset import
|
|
2
1
|
import darwinArm64 from 'bun-pty/rust-pty/target/release/librust_pty_arm64.dylib' with {
|
|
3
2
|
type: 'file',
|
|
4
3
|
};
|
|
5
|
-
// @ts-expect-error Bun file asset import
|
|
6
4
|
import darwinX64 from 'bun-pty/rust-pty/target/release/librust_pty.dylib' with {
|
|
7
5
|
type: 'file',
|
|
8
6
|
};
|
|
9
|
-
// @ts-expect-error Bun file asset import
|
|
10
7
|
import linuxArm64 from 'bun-pty/rust-pty/target/release/librust_pty_arm64.so' with {
|
|
11
8
|
type: 'file',
|
|
12
9
|
};
|
|
13
|
-
// @ts-expect-error Bun file asset import
|
|
14
10
|
import linuxX64 from 'bun-pty/rust-pty/target/release/librust_pty.so' with {
|
|
15
11
|
type: 'file',
|
|
16
12
|
};
|
|
17
|
-
// @ts-expect-error Bun file asset import
|
|
18
13
|
import windowsDll from 'bun-pty/rust-pty/target/release/rust_pty.dll' with {
|
|
19
14
|
type: 'file',
|
|
20
15
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { tool, type Tool } from 'ai';
|
|
2
|
-
import { z } from 'zod/v3';
|
|
3
2
|
import { spawn } from 'node:child_process';
|
|
3
|
+
import { z } from 'zod/v3';
|
|
4
4
|
import DESCRIPTION from './bash.txt' with { type: 'text' };
|
|
5
|
-
import { createToolError, type ToolResponse } from '../error.ts';
|
|
6
5
|
import { getAugmentedPath } from '../bin-manager.ts';
|
|
6
|
+
import { createToolError, type ToolResponse } from '../error.ts';
|
|
7
7
|
import { injectCoAuthorIntoGitCommit } from './git-identity.ts';
|
|
8
8
|
|
|
9
9
|
function normalizePath(p: string) {
|
|
@@ -41,10 +41,26 @@ function killProcessTree(pid: number) {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
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
|
+
|
|
44
59
|
export function buildBashTool(projectRoot: string): {
|
|
45
60
|
name: string;
|
|
46
61
|
tool: Tool;
|
|
47
62
|
} {
|
|
63
|
+
// biome-ignore lint/suspicious/noExplicitAny: AI SDK tool typings do not model async-iterable execute results yet.
|
|
48
64
|
const bash = tool({
|
|
49
65
|
description: DESCRIPTION,
|
|
50
66
|
inputSchema: z
|
|
@@ -66,7 +82,7 @@ export function buildBashTool(projectRoot: string): {
|
|
|
66
82
|
.describe('Timeout in milliseconds (default: 300000 = 5 minutes)'),
|
|
67
83
|
})
|
|
68
84
|
.strict(),
|
|
69
|
-
|
|
85
|
+
execute(
|
|
70
86
|
{
|
|
71
87
|
cmd,
|
|
72
88
|
cwd,
|
|
@@ -79,13 +95,7 @@ export function buildBashTool(projectRoot: string): {
|
|
|
79
95
|
timeout?: number;
|
|
80
96
|
},
|
|
81
97
|
options?: { abortSignal?: AbortSignal },
|
|
82
|
-
):
|
|
83
|
-
ToolResponse<{
|
|
84
|
-
exitCode: number;
|
|
85
|
-
stdout: string;
|
|
86
|
-
stderr: string;
|
|
87
|
-
}>
|
|
88
|
-
> {
|
|
98
|
+
): AsyncIterable<BashStreamChunk> | BashResult {
|
|
89
99
|
const abortSignal = options?.abortSignal;
|
|
90
100
|
|
|
91
101
|
if (abortSignal?.aborted) {
|
|
@@ -95,126 +105,162 @@ export function buildBashTool(projectRoot: string): {
|
|
|
95
105
|
}
|
|
96
106
|
|
|
97
107
|
const absCwd = resolveSafePath(projectRoot, cwd || '.');
|
|
98
|
-
|
|
99
108
|
const finalCmd = injectCoAuthorIntoGitCommit(cmd);
|
|
100
109
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
});
|
|
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
|
+
});
|
|
109
117
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
exitCode: number;
|
|
120
|
-
stdout: string;
|
|
121
|
-
stderr: string;
|
|
122
|
-
}>,
|
|
123
|
-
) => {
|
|
124
|
-
if (settled) return;
|
|
125
|
-
settled = true;
|
|
126
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
127
|
-
if (abortSignal) {
|
|
128
|
-
abortSignal.removeEventListener('abort', onAbort);
|
|
129
|
-
}
|
|
130
|
-
resolve(result);
|
|
131
|
-
};
|
|
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;
|
|
132
127
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
139
|
|
|
140
|
+
const settle = (result: BashResult) => {
|
|
141
|
+
if (settled) return;
|
|
142
|
+
settled = true;
|
|
143
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
140
144
|
if (abortSignal) {
|
|
141
|
-
abortSignal.
|
|
145
|
+
abortSignal.removeEventListener('abort', onAbort);
|
|
142
146
|
}
|
|
147
|
+
queue.push({ result });
|
|
148
|
+
done = true;
|
|
149
|
+
wake();
|
|
150
|
+
};
|
|
143
151
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
152
|
+
const onAbort = () => {
|
|
153
|
+
if (settled) return;
|
|
154
|
+
didAbort = true;
|
|
155
|
+
if (proc.pid) killProcessTree(proc.pid);
|
|
156
|
+
else proc.kill('SIGTERM');
|
|
157
|
+
};
|
|
151
158
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
159
|
+
if (abortSignal) {
|
|
160
|
+
abortSignal.addEventListener('abort', onAbort, { once: true });
|
|
161
|
+
}
|
|
155
162
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
163
|
+
if (timeout > 0) {
|
|
164
|
+
timeoutId = setTimeout(() => {
|
|
165
|
+
didTimeout = true;
|
|
166
|
+
if (proc.pid) killProcessTree(proc.pid);
|
|
167
|
+
else proc.kill();
|
|
168
|
+
}, timeout);
|
|
169
|
+
}
|
|
159
170
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
suggestion: 'Increase timeout or optimize the command',
|
|
178
|
-
},
|
|
179
|
-
),
|
|
180
|
-
);
|
|
181
|
-
} else if (exitCode !== 0 && !allowNonZeroExit) {
|
|
182
|
-
const errorDetail = stderr.trim() || stdout.trim() || '';
|
|
183
|
-
const errorMsg = `Command failed with exit code ${exitCode}${errorDetail ? `\n\n${errorDetail}` : ''}`;
|
|
184
|
-
settle(
|
|
185
|
-
createToolError(errorMsg, 'execution', {
|
|
186
|
-
exitCode,
|
|
187
|
-
stdout,
|
|
188
|
-
stderr,
|
|
189
|
-
cmd,
|
|
190
|
-
suggestion:
|
|
191
|
-
'Check command syntax or use allowNonZeroExit: true',
|
|
192
|
-
}),
|
|
193
|
-
);
|
|
194
|
-
} else {
|
|
195
|
-
settle({
|
|
196
|
-
ok: true,
|
|
197
|
-
exitCode: exitCode ?? 0,
|
|
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,
|
|
198
188
|
stdout,
|
|
199
189
|
stderr,
|
|
200
|
-
})
|
|
201
|
-
|
|
202
|
-
|
|
190
|
+
}),
|
|
191
|
+
);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
203
194
|
|
|
204
|
-
|
|
195
|
+
if (didTimeout) {
|
|
205
196
|
settle(
|
|
206
197
|
createToolError(
|
|
207
|
-
`Command
|
|
208
|
-
'
|
|
198
|
+
`Command timed out after ${timeout}ms: ${cmd}`,
|
|
199
|
+
'timeout',
|
|
209
200
|
{
|
|
210
|
-
|
|
211
|
-
|
|
201
|
+
parameter: 'timeout',
|
|
202
|
+
value: timeout,
|
|
203
|
+
stdout,
|
|
204
|
+
stderr,
|
|
205
|
+
suggestion: 'Increase timeout or optimize the command',
|
|
212
206
|
},
|
|
213
207
|
),
|
|
214
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,
|
|
215
232
|
});
|
|
216
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();
|
|
217
263
|
},
|
|
218
|
-
});
|
|
264
|
+
} as any);
|
|
219
265
|
return { name: 'bash', tool: bash };
|
|
220
266
|
}
|
package/src/index.ts
CHANGED
|
@@ -49,6 +49,25 @@ async function readIfExists(path: string): Promise<string | undefined> {
|
|
|
49
49
|
return undefined;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
function getPromptOverridePaths(args: {
|
|
53
|
+
projectRoot: string;
|
|
54
|
+
provider: string;
|
|
55
|
+
modelId?: string;
|
|
56
|
+
}): { modelPaths: string[]; providerPaths: string[] } {
|
|
57
|
+
const { projectRoot, provider, modelId } = args;
|
|
58
|
+
const modelPaths: string[] = [];
|
|
59
|
+
const providerPaths: string[] = [];
|
|
60
|
+
|
|
61
|
+
if (modelId) {
|
|
62
|
+
const sanitized = sanitizeModelId(modelId);
|
|
63
|
+
modelPaths.push(`${projectRoot}/.otto/prompts/models/${sanitized}.txt`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
providerPaths.push(`${projectRoot}/.otto/prompts/providers/${provider}.txt`);
|
|
67
|
+
|
|
68
|
+
return { modelPaths, providerPaths };
|
|
69
|
+
}
|
|
70
|
+
|
|
52
71
|
export type ProviderPromptResult = {
|
|
53
72
|
prompt: string;
|
|
54
73
|
resolvedType: string;
|
|
@@ -57,21 +76,37 @@ export type ProviderPromptResult = {
|
|
|
57
76
|
export async function providerBasePrompt(
|
|
58
77
|
provider: string,
|
|
59
78
|
modelId: string | undefined,
|
|
60
|
-
|
|
79
|
+
projectRoot: string,
|
|
61
80
|
): Promise<ProviderPromptResult> {
|
|
62
81
|
const id = String(provider || '').toLowerCase();
|
|
82
|
+
const { modelPaths, providerPaths } = getPromptOverridePaths({
|
|
83
|
+
projectRoot,
|
|
84
|
+
provider: id,
|
|
85
|
+
modelId,
|
|
86
|
+
});
|
|
63
87
|
|
|
64
88
|
if (modelId) {
|
|
65
89
|
const sanitized = sanitizeModelId(modelId);
|
|
66
|
-
const modelPath
|
|
67
|
-
|
|
68
|
-
|
|
90
|
+
for (const modelPath of modelPaths) {
|
|
91
|
+
const modelText = await readIfExists(modelPath);
|
|
92
|
+
if (!modelText) continue;
|
|
69
93
|
const promptType = `model:${sanitized}`;
|
|
70
|
-
debugLog(
|
|
94
|
+
debugLog(
|
|
95
|
+
`[provider] prompt: ${promptType} (${modelText.length} chars) from ${modelPath}`,
|
|
96
|
+
);
|
|
71
97
|
return { prompt: modelText, resolvedType: promptType };
|
|
72
98
|
}
|
|
73
99
|
}
|
|
74
100
|
|
|
101
|
+
for (const providerPath of providerPaths) {
|
|
102
|
+
const providerText = await readIfExists(providerPath);
|
|
103
|
+
if (!providerText) continue;
|
|
104
|
+
debugLog(
|
|
105
|
+
`[provider] prompt: custom:${id} (${providerText.length} chars) from ${providerPath}`,
|
|
106
|
+
);
|
|
107
|
+
return { prompt: providerText, resolvedType: `custom:${id}` };
|
|
108
|
+
}
|
|
109
|
+
|
|
75
110
|
if (isProviderId(id) && modelId) {
|
|
76
111
|
const info = getModelInfo(id, modelId);
|
|
77
112
|
if (info?.ownedBy) {
|
|
@@ -119,13 +154,6 @@ export async function providerBasePrompt(
|
|
|
119
154
|
return { prompt: result, resolvedType: 'glm' };
|
|
120
155
|
}
|
|
121
156
|
|
|
122
|
-
const providerPath = `src/prompts/providers/${id}.txt`;
|
|
123
|
-
const providerText = await readIfExists(providerPath);
|
|
124
|
-
if (providerText) {
|
|
125
|
-
debugLog(`[provider] prompt: custom:${id} (${providerText.length} chars)`);
|
|
126
|
-
return { prompt: providerText, resolvedType: `custom:${id}` };
|
|
127
|
-
}
|
|
128
|
-
|
|
129
157
|
const result = PROVIDER_DEFAULT.trim();
|
|
130
158
|
debugLog(`[provider] prompt: default (${result.length} chars)`);
|
|
131
159
|
return { prompt: result, resolvedType: 'default' };
|
package/src/tunnel/qr.ts
CHANGED
package/src/types/src/config.ts
CHANGED
|
@@ -9,6 +9,13 @@ export type Scope = 'global' | 'local';
|
|
|
9
9
|
* Default settings for the CLI
|
|
10
10
|
*/
|
|
11
11
|
export type ToolApprovalMode = 'auto' | 'dangerous' | 'all';
|
|
12
|
+
export type ReasoningLevel =
|
|
13
|
+
| 'minimal'
|
|
14
|
+
| 'low'
|
|
15
|
+
| 'medium'
|
|
16
|
+
| 'high'
|
|
17
|
+
| 'max'
|
|
18
|
+
| 'xhigh';
|
|
12
19
|
|
|
13
20
|
export type DefaultConfig = {
|
|
14
21
|
agent: string;
|
|
@@ -17,6 +24,7 @@ export type DefaultConfig = {
|
|
|
17
24
|
toolApproval?: ToolApprovalMode;
|
|
18
25
|
guidedMode?: boolean;
|
|
19
26
|
reasoningText?: boolean;
|
|
27
|
+
reasoningLevel?: ReasoningLevel;
|
|
20
28
|
theme?: string;
|
|
21
29
|
};
|
|
22
30
|
|