@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ottocode/sdk",
3
- "version": "0.1.228",
3
+ "version": "0.1.230",
4
4
  "description": "AI agent SDK for building intelligent assistants - tree-shakable and comprehensive",
5
5
  "author": "nitishxyz",
6
6
  "license": "MIT",
@@ -34,6 +34,7 @@ const DEFAULTS: {
34
34
  toolApproval: 'auto',
35
35
  guidedMode: false,
36
36
  reasoningText: true,
37
+ reasoningLevel: 'high',
37
38
  },
38
39
  providers: DEFAULT_PROVIDER_SETTINGS,
39
40
  };
@@ -66,6 +66,7 @@ export async function writeDefaults(
66
66
  toolApproval: 'auto' | 'dangerous' | 'all';
67
67
  guidedMode: boolean;
68
68
  reasoningText: boolean;
69
+ reasoningLevel: 'minimal' | 'low' | 'medium' | 'high' | 'max' | 'xhigh';
69
70
  theme: string;
70
71
  }>,
71
72
  projectRoot?: string,
@@ -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
- async execute(
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
- ): Promise<
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
- return new Promise((resolve) => {
102
- const proc = spawn(finalCmd, {
103
- cwd: absCwd,
104
- shell: true,
105
- stdio: ['ignore', 'pipe', 'pipe'],
106
- env: { ...process.env, PATH: getAugmentedPath() },
107
- detached: true,
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
- let stdout = '';
111
- let stderr = '';
112
- let didTimeout = false;
113
- let didAbort = false;
114
- let settled = false;
115
- let timeoutId: ReturnType<typeof setTimeout> | null = null;
116
-
117
- const settle = (
118
- result: ToolResponse<{
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
- const onAbort = () => {
134
- if (settled) return;
135
- didAbort = true;
136
- if (proc.pid) killProcessTree(proc.pid);
137
- else proc.kill('SIGTERM');
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.addEventListener('abort', onAbort, { once: true });
145
+ abortSignal.removeEventListener('abort', onAbort);
142
146
  }
147
+ queue.push({ result });
148
+ done = true;
149
+ wake();
150
+ };
143
151
 
144
- if (timeout > 0) {
145
- timeoutId = setTimeout(() => {
146
- didTimeout = true;
147
- if (proc.pid) killProcessTree(proc.pid);
148
- else proc.kill();
149
- }, timeout);
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
- proc.stdout?.on('data', (chunk) => {
153
- stdout += chunk.toString();
154
- });
159
+ if (abortSignal) {
160
+ abortSignal.addEventListener('abort', onAbort, { once: true });
161
+ }
155
162
 
156
- proc.stderr?.on('data', (chunk) => {
157
- stderr += chunk.toString();
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
- proc.on('close', (exitCode) => {
161
- if (didAbort) {
162
- settle(
163
- createToolError(`Command aborted by user: ${cmd}`, 'abort', {
164
- cmd,
165
- stdout,
166
- stderr,
167
- }),
168
- );
169
- } else if (didTimeout) {
170
- settle(
171
- createToolError(
172
- `Command timed out after ${timeout}ms: ${cmd}`,
173
- 'timeout',
174
- {
175
- parameter: 'timeout',
176
- value: timeout,
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
- proc.on('error', (err) => {
195
+ if (didTimeout) {
205
196
  settle(
206
197
  createToolError(
207
- `Command execution failed: ${err.message}`,
208
- 'execution',
198
+ `Command timed out after ${timeout}ms: ${cmd}`,
199
+ 'timeout',
209
200
  {
210
- cmd,
211
- originalError: err.message,
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
@@ -29,6 +29,7 @@ export type {
29
29
  DefaultConfig,
30
30
  PathConfig,
31
31
  OttoConfig,
32
+ ReasoningLevel,
32
33
  } from './types/src/index.ts';
33
34
 
34
35
  // =======================
@@ -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
- _projectRoot: string,
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 = `src/prompts/models/${sanitized}.txt`;
67
- const modelText = await readIfExists(modelPath);
68
- if (modelText) {
90
+ for (const modelPath of modelPaths) {
91
+ const modelText = await readIfExists(modelPath);
92
+ if (!modelText) continue;
69
93
  const promptType = `model:${sanitized}`;
70
- debugLog(`[provider] prompt: ${promptType} (${modelText.length} chars)`);
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
@@ -1,4 +1,3 @@
1
- // @ts-expect-error No bundled types for qrcode-terminal in all workspace builds
2
1
  import qrcode from 'qrcode-terminal';
3
2
 
4
3
  export function generateQRCode(data: string): Promise<string> {
@@ -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
 
@@ -19,4 +19,5 @@ export type {
19
19
  ProviderSettings,
20
20
  OttoConfig,
21
21
  ToolApprovalMode,
22
+ ReasoningLevel,
22
23
  } from './config';