@ottocode/sdk 0.1.315 → 0.1.316
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/core/src/providers/resolver.ts +2 -0
- package/src/core/src/tools/bin-manager.ts +88 -33
- package/src/core/src/tools/builtin/shell.ts +115 -12
- package/src/core/src/tools/builtin/shell.txt +10 -1
- package/src/providers/src/catalog-manual.ts +60 -0
- package/src/providers/src/zai-client.ts +3 -0
package/package.json
CHANGED
|
@@ -211,6 +211,7 @@ export async function resolveModel(
|
|
|
211
211
|
return createZaiModel(model, {
|
|
212
212
|
apiKey: config.apiKey,
|
|
213
213
|
baseURL: config.baseURL,
|
|
214
|
+
fetch: config.customFetch,
|
|
214
215
|
});
|
|
215
216
|
}
|
|
216
217
|
|
|
@@ -218,6 +219,7 @@ export async function resolveModel(
|
|
|
218
219
|
return createZaiCodingModel(model, {
|
|
219
220
|
apiKey: config.apiKey,
|
|
220
221
|
baseURL: config.baseURL,
|
|
222
|
+
fetch: config.customFetch,
|
|
221
223
|
});
|
|
222
224
|
}
|
|
223
225
|
|
|
@@ -15,6 +15,16 @@ let cachedLoginPath: {
|
|
|
15
15
|
path: string | null;
|
|
16
16
|
} | null = null;
|
|
17
17
|
|
|
18
|
+
let cachedLoginEnv: {
|
|
19
|
+
key: string;
|
|
20
|
+
env: NodeJS.ProcessEnv | null;
|
|
21
|
+
} | null = null;
|
|
22
|
+
|
|
23
|
+
export type ShellEnvMode = 'fast' | 'login-cache' | 'login-fresh';
|
|
24
|
+
|
|
25
|
+
const ENV_JSON_START = '___OTTO_ENV_JSON_START___';
|
|
26
|
+
const ENV_JSON_END = '___OTTO_ENV_JSON_END___';
|
|
27
|
+
|
|
18
28
|
export { getAgiBinDir } from './bin-manager/paths.ts';
|
|
19
29
|
|
|
20
30
|
async function whichBinary(name: string): Promise<string | null> {
|
|
@@ -72,29 +82,35 @@ export function getUserShell(): string {
|
|
|
72
82
|
return process.env.SHELL || '/bin/bash';
|
|
73
83
|
}
|
|
74
84
|
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const shellName = shell.split('/').pop() || '';
|
|
88
|
-
if (shellName.includes('bash')) return '-ic';
|
|
89
|
-
return '-ilc';
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function getShellExecutionConfig(cmd: string): {
|
|
85
|
+
export function getShellExecutionConfig(
|
|
86
|
+
cmd: string,
|
|
87
|
+
options?: { envMode?: ShellEnvMode },
|
|
88
|
+
): {
|
|
89
|
+
command: string;
|
|
90
|
+
args: string[];
|
|
91
|
+
env: NodeJS.ProcessEnv;
|
|
92
|
+
};
|
|
93
|
+
export function getShellExecutionConfig(
|
|
94
|
+
cmd: string,
|
|
95
|
+
options: { envMode?: ShellEnvMode } = {},
|
|
96
|
+
): {
|
|
93
97
|
command: string;
|
|
94
98
|
args: string[];
|
|
95
99
|
env: NodeJS.ProcessEnv;
|
|
96
100
|
} {
|
|
97
|
-
const
|
|
101
|
+
const envMode = options.envMode ?? 'fast';
|
|
102
|
+
const loginEnv =
|
|
103
|
+
envMode === 'fast' ? null : getLoginShellEnv(envMode === 'login-fresh');
|
|
104
|
+
const env = {
|
|
105
|
+
...process.env,
|
|
106
|
+
...(loginEnv ?? {}),
|
|
107
|
+
PATH: mergePaths([
|
|
108
|
+
getAgiBinDir(),
|
|
109
|
+
loginEnv?.PATH,
|
|
110
|
+
getLoginShellPath(),
|
|
111
|
+
process.env.PATH,
|
|
112
|
+
]),
|
|
113
|
+
};
|
|
98
114
|
if (process.platform === 'win32') {
|
|
99
115
|
return {
|
|
100
116
|
command: getUserShell(),
|
|
@@ -106,11 +122,56 @@ export function getShellExecutionConfig(cmd: string): {
|
|
|
106
122
|
const command = getUserShell();
|
|
107
123
|
return {
|
|
108
124
|
command,
|
|
109
|
-
args: [
|
|
125
|
+
args: ['-c', 'eval "$OTTO_SHELL_COMMAND"'],
|
|
110
126
|
env: { ...env, OTTO_SHELL_COMMAND: cmd },
|
|
111
127
|
};
|
|
112
128
|
}
|
|
113
129
|
|
|
130
|
+
function getLoginShellEnv(refresh: boolean): NodeJS.ProcessEnv | null {
|
|
131
|
+
const home = process.env.HOME || homedir();
|
|
132
|
+
const userShell = getUserShell();
|
|
133
|
+
const cacheKey = [home, userShell, process.env.PATH || ''].join('\0');
|
|
134
|
+
if (!refresh && cachedLoginEnv?.key === cacheKey) return cachedLoginEnv.env;
|
|
135
|
+
|
|
136
|
+
if (process.platform === 'win32') {
|
|
137
|
+
cachedLoginEnv = { key: cacheKey, env: { ...process.env } };
|
|
138
|
+
return cachedLoginEnv.env;
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
const output = execFileSync(
|
|
142
|
+
userShell,
|
|
143
|
+
[
|
|
144
|
+
'-ic',
|
|
145
|
+
`printf '%s\n' ${JSON.stringify(ENV_JSON_START)}; env; printf '%s\n' ${JSON.stringify(ENV_JSON_END)}`,
|
|
146
|
+
],
|
|
147
|
+
{
|
|
148
|
+
timeout: 10000,
|
|
149
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
150
|
+
env: {
|
|
151
|
+
...process.env,
|
|
152
|
+
HOME: home,
|
|
153
|
+
USER: process.env.USER || '',
|
|
154
|
+
SHELL: userShell,
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
).toString();
|
|
158
|
+
const start = output.indexOf(ENV_JSON_START);
|
|
159
|
+
const end = output.indexOf(ENV_JSON_END, start + ENV_JSON_START.length);
|
|
160
|
+
if (start >= 0 && end > start) {
|
|
161
|
+
const env: NodeJS.ProcessEnv = {};
|
|
162
|
+
const body = output.slice(start + ENV_JSON_START.length, end).trim();
|
|
163
|
+
for (const line of body.split('\n')) {
|
|
164
|
+
const separator = line.indexOf('=');
|
|
165
|
+
if (separator <= 0) continue;
|
|
166
|
+
env[line.slice(0, separator)] = line.slice(separator + 1);
|
|
167
|
+
}
|
|
168
|
+
cachedLoginEnv = { key: cacheKey, env };
|
|
169
|
+
return env;
|
|
170
|
+
}
|
|
171
|
+
} catch {}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
114
175
|
function getLoginShellPath(): string | null {
|
|
115
176
|
const home = process.env.HOME || homedir();
|
|
116
177
|
const userShell = getUserShell();
|
|
@@ -131,10 +192,8 @@ function getLoginShellPath(): string | null {
|
|
|
131
192
|
|
|
132
193
|
for (const shell of shellCandidates) {
|
|
133
194
|
try {
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
const result = execFileSync(shell, ['-ilc', pathCommand], {
|
|
137
|
-
timeout: 5000,
|
|
195
|
+
const result = execFileSync(shell, ['-lc', 'echo "___PATH___:$PATH"'], {
|
|
196
|
+
timeout: 1500,
|
|
138
197
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
139
198
|
env: {
|
|
140
199
|
...process.env,
|
|
@@ -157,19 +216,15 @@ function getLoginShellPath(): string | null {
|
|
|
157
216
|
}
|
|
158
217
|
|
|
159
218
|
export function getAugmentedPath(): string {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const current = process.env.PATH || '';
|
|
163
|
-
const loginPath = getLoginShellPath();
|
|
219
|
+
return mergePaths([getAgiBinDir(), getLoginShellPath(), process.env.PATH]);
|
|
220
|
+
}
|
|
164
221
|
|
|
222
|
+
function mergePaths(paths: Array<string | null | undefined>): string {
|
|
223
|
+
const sep = process.platform === 'win32' ? ';' : ':';
|
|
165
224
|
const seen = new Set<string>();
|
|
166
225
|
const parts: string[] = [];
|
|
167
226
|
|
|
168
|
-
for (const p of [
|
|
169
|
-
binDir,
|
|
170
|
-
...(loginPath ? loginPath.split(sep) : []),
|
|
171
|
-
...current.split(sep),
|
|
172
|
-
]) {
|
|
227
|
+
for (const p of paths.flatMap((path) => (path ? path.split(sep) : []))) {
|
|
173
228
|
if (p && !seen.has(p)) {
|
|
174
229
|
seen.add(p);
|
|
175
230
|
parts.push(p);
|
|
@@ -3,7 +3,7 @@ import { AsyncLocalStorage } from 'node:async_hooks';
|
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import { z } from 'zod/v3';
|
|
5
5
|
import DESCRIPTION from './shell.txt' with { type: 'text' };
|
|
6
|
-
import { getShellExecutionConfig } from '../bin-manager.ts';
|
|
6
|
+
import { getShellExecutionConfig, type ShellEnvMode } from '../bin-manager.ts';
|
|
7
7
|
import { createToolError, type ToolResponse } from '../error.ts';
|
|
8
8
|
import {
|
|
9
9
|
injectCoAuthorIntoGitCommit,
|
|
@@ -45,6 +45,16 @@ function killProcessTree(pid: number) {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
function forceKillProcessTree(pid: number) {
|
|
49
|
+
try {
|
|
50
|
+
process.kill(-pid, 'SIGKILL');
|
|
51
|
+
} catch {
|
|
52
|
+
try {
|
|
53
|
+
process.kill(pid, 'SIGKILL');
|
|
54
|
+
} catch {}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
48
58
|
const REDIRECTED_SEARCH_COMMANDS = new Set(['grep', 'egrep', 'fgrep', 'rg']);
|
|
49
59
|
const REDIRECTED_GLOB_COMMANDS = new Set(['find', 'fd']);
|
|
50
60
|
|
|
@@ -103,6 +113,29 @@ function repositoryDiscoveryHint(cmd: string): string | undefined {
|
|
|
103
113
|
: `Tip: For repository file discovery, prefer the glob tool instead of shelling out to ${discovery.command}. It returns structured paths and skips common build/cache folders.`;
|
|
104
114
|
}
|
|
105
115
|
|
|
116
|
+
const SHELL_ENV_HINT =
|
|
117
|
+
'This command may require environment from your login/interactive shell. If appropriate, retry with envMode: "login-cache" (or "login-fresh" after changing shell config).';
|
|
118
|
+
|
|
119
|
+
export function detectShellEnvHint(args: {
|
|
120
|
+
stdout: string;
|
|
121
|
+
stderr: string;
|
|
122
|
+
exitCode: number;
|
|
123
|
+
envMode?: ShellEnvMode;
|
|
124
|
+
}): string | undefined {
|
|
125
|
+
if (args.envMode && args.envMode !== 'fast') return undefined;
|
|
126
|
+
if (args.exitCode === 0) return undefined;
|
|
127
|
+
const text = `${args.stderr}\n${args.stdout}`;
|
|
128
|
+
const patterns = [
|
|
129
|
+
/\b[A-Z][A-Z0-9_]{2,}\b[^\n]*(?:not set|not defined|required|missing|must be set)/i,
|
|
130
|
+
/(?:missing|required|could not find)[^\n]*(?:api key|token|credential|credentials|secret|environment variable|env var)/i,
|
|
131
|
+
/(?:no credentials|credentials[^\n]*not found|not authenticated|authentication required|please log in|please login)/i,
|
|
132
|
+
/(?:asdf|nvm|mise|direnv|op|doppler)[^\n]*(?:not found|not loaded|command not found)/i,
|
|
133
|
+
];
|
|
134
|
+
return patterns.some((pattern) => pattern.test(text))
|
|
135
|
+
? SHELL_ENV_HINT
|
|
136
|
+
: undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
106
139
|
export type ShellOutputMode = 'auto' | 'full' | 'tail';
|
|
107
140
|
|
|
108
141
|
const DEFAULT_TAIL_LINES = 100;
|
|
@@ -170,6 +203,8 @@ type ShellResult = ToolResponse<{
|
|
|
170
203
|
stderrOriginalBytes?: number;
|
|
171
204
|
stderrShownBytes?: number;
|
|
172
205
|
discoveryHint?: string;
|
|
206
|
+
envMode?: ShellEnvMode;
|
|
207
|
+
envHint?: string;
|
|
173
208
|
}>;
|
|
174
209
|
|
|
175
210
|
type ShellStreamChunk =
|
|
@@ -199,7 +234,7 @@ const shellInputSchema = z
|
|
|
199
234
|
cmd: z
|
|
200
235
|
.string()
|
|
201
236
|
.describe(
|
|
202
|
-
'Non-interactive shell command to run using the user shell
|
|
237
|
+
'Non-interactive shell command to run using the user shell. Login PATH is loaded, but interactive startup files are not sourced per command.',
|
|
203
238
|
),
|
|
204
239
|
cwd: z
|
|
205
240
|
.string()
|
|
@@ -215,6 +250,13 @@ const shellInputSchema = z
|
|
|
215
250
|
.optional()
|
|
216
251
|
.default(300000)
|
|
217
252
|
.describe('Timeout in milliseconds (default: 300000 = 5 minutes)'),
|
|
253
|
+
envMode: z
|
|
254
|
+
.enum(['fast', 'login-cache', 'login-fresh'])
|
|
255
|
+
.optional()
|
|
256
|
+
.default('fast')
|
|
257
|
+
.describe(
|
|
258
|
+
'Environment loading mode. "fast" is the default one-off shell env. "login-cache" reuses a cached environment captured from the user login/interactive shell for commands that need shell-managed credentials. "login-fresh" refreshes that cache.',
|
|
259
|
+
),
|
|
218
260
|
outputMode: z
|
|
219
261
|
.enum(['auto', 'full', 'tail'])
|
|
220
262
|
.optional()
|
|
@@ -270,6 +312,7 @@ export function buildShellTool(projectRoot: string): {
|
|
|
270
312
|
cwd,
|
|
271
313
|
allowNonZeroExit,
|
|
272
314
|
timeout = 300000,
|
|
315
|
+
envMode = 'fast',
|
|
273
316
|
outputMode = 'auto',
|
|
274
317
|
tailLines = DEFAULT_TAIL_LINES,
|
|
275
318
|
maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES,
|
|
@@ -297,6 +340,7 @@ export function buildShellTool(projectRoot: string): {
|
|
|
297
340
|
cwd: absCwd,
|
|
298
341
|
allowNonZeroExit,
|
|
299
342
|
timeout,
|
|
343
|
+
envMode,
|
|
300
344
|
outputMode,
|
|
301
345
|
tailLines,
|
|
302
346
|
maxOutputBytes,
|
|
@@ -305,7 +349,7 @@ export function buildShellTool(projectRoot: string): {
|
|
|
305
349
|
) as AsyncIterable<ShellStreamChunk> | ShellResult;
|
|
306
350
|
}
|
|
307
351
|
|
|
308
|
-
const shellConfig = getShellExecutionConfig(finalCmd);
|
|
352
|
+
const shellConfig = getShellExecutionConfig(finalCmd, { envMode });
|
|
309
353
|
const proc = spawn(shellConfig.command, shellConfig.args, {
|
|
310
354
|
cwd: absCwd,
|
|
311
355
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -318,8 +362,11 @@ export function buildShellTool(projectRoot: string): {
|
|
|
318
362
|
let didTimeout = false;
|
|
319
363
|
let didAbort = false;
|
|
320
364
|
let settled = false;
|
|
365
|
+
let terminating = false;
|
|
321
366
|
let done = false;
|
|
322
367
|
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
368
|
+
let killEscalationId: ReturnType<typeof setTimeout> | null = null;
|
|
369
|
+
let fallbackSettleId: ReturnType<typeof setTimeout> | null = null;
|
|
323
370
|
const queue: ShellStreamChunk[] = [];
|
|
324
371
|
let notify: (() => void) | null = null;
|
|
325
372
|
|
|
@@ -382,6 +429,8 @@ export function buildShellTool(projectRoot: string): {
|
|
|
382
429
|
details.maxOutputBytes = maxOutputBytes;
|
|
383
430
|
}
|
|
384
431
|
if (timeoutId) clearTimeout(timeoutId);
|
|
432
|
+
if (killEscalationId) clearTimeout(killEscalationId);
|
|
433
|
+
if (fallbackSettleId) clearTimeout(fallbackSettleId);
|
|
385
434
|
if (abortSignal) {
|
|
386
435
|
abortSignal.removeEventListener('abort', onAbort);
|
|
387
436
|
}
|
|
@@ -390,11 +439,54 @@ export function buildShellTool(projectRoot: string): {
|
|
|
390
439
|
wake();
|
|
391
440
|
};
|
|
392
441
|
|
|
442
|
+
const abortResult = () =>
|
|
443
|
+
createToolError(`Command aborted by user: ${cmd}`, 'abort', {
|
|
444
|
+
cmd,
|
|
445
|
+
stdout,
|
|
446
|
+
stderr,
|
|
447
|
+
envMode,
|
|
448
|
+
...(outputMode === 'tail' || outputMode === 'auto'
|
|
449
|
+
? { outputMode, tailLines, maxOutputBytes }
|
|
450
|
+
: { outputMode, maxOutputBytes }),
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
const timeoutResult = () =>
|
|
454
|
+
createToolError(
|
|
455
|
+
`Command timed out after ${timeout}ms: ${cmd}`,
|
|
456
|
+
'timeout',
|
|
457
|
+
{
|
|
458
|
+
parameter: 'timeout',
|
|
459
|
+
value: timeout,
|
|
460
|
+
stdout,
|
|
461
|
+
stderr,
|
|
462
|
+
envMode,
|
|
463
|
+
...(outputMode === 'tail' || outputMode === 'auto'
|
|
464
|
+
? { outputMode, tailLines, maxOutputBytes }
|
|
465
|
+
: { outputMode, maxOutputBytes }),
|
|
466
|
+
suggestion: 'Increase timeout or optimize the command',
|
|
467
|
+
},
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
const terminate = (fallbackResult: () => ShellResult) => {
|
|
471
|
+
if (terminating) return;
|
|
472
|
+
terminating = true;
|
|
473
|
+
if (proc.pid) {
|
|
474
|
+
killProcessTree(proc.pid);
|
|
475
|
+
killEscalationId = setTimeout(() => {
|
|
476
|
+
if (proc.pid) forceKillProcessTree(proc.pid);
|
|
477
|
+
}, 1000);
|
|
478
|
+
} else {
|
|
479
|
+
proc.kill('SIGTERM');
|
|
480
|
+
}
|
|
481
|
+
fallbackSettleId = setTimeout(() => {
|
|
482
|
+
settle(fallbackResult());
|
|
483
|
+
}, 2000);
|
|
484
|
+
};
|
|
485
|
+
|
|
393
486
|
const onAbort = () => {
|
|
394
487
|
if (settled) return;
|
|
395
488
|
didAbort = true;
|
|
396
|
-
|
|
397
|
-
else proc.kill('SIGTERM');
|
|
489
|
+
terminate(abortResult);
|
|
398
490
|
};
|
|
399
491
|
|
|
400
492
|
if (abortSignal) {
|
|
@@ -404,8 +496,7 @@ export function buildShellTool(projectRoot: string): {
|
|
|
404
496
|
if (timeout > 0) {
|
|
405
497
|
timeoutId = setTimeout(() => {
|
|
406
498
|
didTimeout = true;
|
|
407
|
-
|
|
408
|
-
else proc.kill();
|
|
499
|
+
terminate(timeoutResult);
|
|
409
500
|
}, timeout);
|
|
410
501
|
}
|
|
411
502
|
|
|
@@ -428,12 +519,20 @@ export function buildShellTool(projectRoot: string): {
|
|
|
428
519
|
});
|
|
429
520
|
|
|
430
521
|
proc.on('close', (exitCode) => {
|
|
522
|
+
const resolvedExitCode = exitCode ?? 0;
|
|
523
|
+
const envHint = detectShellEnvHint({
|
|
524
|
+
stdout,
|
|
525
|
+
stderr,
|
|
526
|
+
exitCode: resolvedExitCode,
|
|
527
|
+
envMode,
|
|
528
|
+
});
|
|
431
529
|
if (didAbort) {
|
|
432
530
|
settle(
|
|
433
531
|
createToolError(`Command aborted by user: ${cmd}`, 'abort', {
|
|
434
532
|
cmd,
|
|
435
533
|
stdout,
|
|
436
534
|
stderr,
|
|
535
|
+
envMode,
|
|
437
536
|
...(outputMode === 'tail' || outputMode === 'auto'
|
|
438
537
|
? { outputMode, tailLines, maxOutputBytes }
|
|
439
538
|
: { outputMode, maxOutputBytes }),
|
|
@@ -452,6 +551,7 @@ export function buildShellTool(projectRoot: string): {
|
|
|
452
551
|
value: timeout,
|
|
453
552
|
stdout,
|
|
454
553
|
stderr,
|
|
554
|
+
envMode,
|
|
455
555
|
...(outputMode === 'tail' || outputMode === 'auto'
|
|
456
556
|
? { outputMode, tailLines, maxOutputBytes }
|
|
457
557
|
: { outputMode, maxOutputBytes }),
|
|
@@ -462,15 +562,17 @@ export function buildShellTool(projectRoot: string): {
|
|
|
462
562
|
return;
|
|
463
563
|
}
|
|
464
564
|
|
|
465
|
-
if (
|
|
565
|
+
if (resolvedExitCode !== 0 && !allowNonZeroExit) {
|
|
466
566
|
const errorDetail = stderr.trim() || stdout.trim() || '';
|
|
467
|
-
const errorMsg = `Command failed with exit code ${
|
|
567
|
+
const errorMsg = `Command failed with exit code ${resolvedExitCode}${errorDetail ? `\n\n${errorDetail}` : ''}`;
|
|
468
568
|
settle(
|
|
469
569
|
createToolError(errorMsg, 'execution', {
|
|
470
|
-
exitCode,
|
|
570
|
+
exitCode: resolvedExitCode,
|
|
471
571
|
stdout,
|
|
472
572
|
stderr,
|
|
473
573
|
cmd,
|
|
574
|
+
envMode,
|
|
575
|
+
...(envHint ? { envHint } : {}),
|
|
474
576
|
...(outputMode === 'tail' || outputMode === 'auto'
|
|
475
577
|
? { outputMode, tailLines, maxOutputBytes }
|
|
476
578
|
: { outputMode, maxOutputBytes }),
|
|
@@ -483,16 +585,17 @@ export function buildShellTool(projectRoot: string): {
|
|
|
483
585
|
const discoveryHint = repositoryDiscoveryHint(finalCmd);
|
|
484
586
|
settle({
|
|
485
587
|
ok: true,
|
|
486
|
-
exitCode:
|
|
588
|
+
exitCode: resolvedExitCode,
|
|
487
589
|
stdout,
|
|
488
590
|
stderr,
|
|
591
|
+
envMode,
|
|
489
592
|
...(discoveryHint ? { discoveryHint } : {}),
|
|
593
|
+
...(envHint ? { envHint } : {}),
|
|
490
594
|
...(outputMode === 'tail' || outputMode === 'auto'
|
|
491
595
|
? { outputMode, tailLines, maxOutputBytes }
|
|
492
596
|
: { outputMode, maxOutputBytes }),
|
|
493
597
|
});
|
|
494
598
|
});
|
|
495
|
-
|
|
496
599
|
proc.on('error', (err) => {
|
|
497
600
|
settle(
|
|
498
601
|
createToolError(
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
- Execute a non-interactive shell command using the user's shell
|
|
1
|
+
- Execute a non-interactive shell command using the user's shell
|
|
2
2
|
- Returns `stdout`, `stderr`, and `exitCode`
|
|
3
3
|
- `cwd` is relative to the project root and sandboxed within it
|
|
4
4
|
|
|
5
|
+
The shell tool loads the user's login PATH once for command lookup, but it does not source interactive startup files for every command. Use `terminal` when you need an interactive shell, a TTY, or a persistent process.
|
|
6
|
+
|
|
7
|
+
`envMode` controls extra environment loading:
|
|
8
|
+
- `fast` (default): fastest one-off shell environment plus cached login PATH.
|
|
9
|
+
- `login-cache`: reuses a cached environment captured from the user's login/interactive shell. Use this only when a prior fast command reports missing credentials/environment, or when the user explicitly asks to load shell startup environment.
|
|
10
|
+
- `login-fresh`: refreshes that login/interactive shell environment cache before running the command.
|
|
11
|
+
|
|
12
|
+
If a fast command fails with an `envHint`, retry once with `envMode: "login-cache"` when appropriate. Do not use login env modes for normal repository discovery, simple build/test commands, or as a blanket default.
|
|
13
|
+
|
|
5
14
|
**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
15
|
|
|
7
16
|
For repository discovery, use `search` for content/code search and `glob` for filename/path discovery. Reserve `shell` for execution, builds, tests, diagnostics, and other command-line tasks.
|
|
@@ -13,6 +13,35 @@ type CatalogMap = Partial<Record<BuiltInProviderId, ProviderCatalogEntry>>;
|
|
|
13
13
|
|
|
14
14
|
const OLLAMA_CLOUD_ID: BuiltInProviderId = 'ollama-cloud';
|
|
15
15
|
const OTTOROUTER_ID: BuiltInProviderId = 'ottorouter';
|
|
16
|
+
const ZAI_CODING_ID: BuiltInProviderId = 'zai-coding';
|
|
17
|
+
|
|
18
|
+
const ZAI_CODING_MODEL_ORDER = [
|
|
19
|
+
'glm-5.2',
|
|
20
|
+
'glm-5.1',
|
|
21
|
+
'glm-5-turbo',
|
|
22
|
+
'glm-5',
|
|
23
|
+
'glm-4.7',
|
|
24
|
+
'glm-4.5-air',
|
|
25
|
+
'glm-5v-turbo',
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const ZAI_CODING_MANUAL_MODELS: ModelInfo[] = [
|
|
29
|
+
{
|
|
30
|
+
id: 'glm-5',
|
|
31
|
+
ownedBy: 'zai',
|
|
32
|
+
label: 'GLM-5',
|
|
33
|
+
modalities: { input: ['text'], output: ['text'] },
|
|
34
|
+
toolCall: true,
|
|
35
|
+
reasoningText: true,
|
|
36
|
+
attachment: false,
|
|
37
|
+
temperature: true,
|
|
38
|
+
releaseDate: '2026-02-11',
|
|
39
|
+
lastUpdated: '2026-02-11',
|
|
40
|
+
openWeights: true,
|
|
41
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
42
|
+
limit: { context: 204_800, output: 131_072 },
|
|
43
|
+
},
|
|
44
|
+
];
|
|
16
45
|
|
|
17
46
|
const XAI_GROK_CLI_MODELS: ModelInfo[] = [
|
|
18
47
|
{
|
|
@@ -214,6 +243,33 @@ export function applyOfficialKimiCatalogMetadata<
|
|
|
214
243
|
};
|
|
215
244
|
}
|
|
216
245
|
|
|
246
|
+
export function applyZaiCodingCatalogMetadata<T extends ProviderCatalogEntry>(
|
|
247
|
+
entry: T | undefined,
|
|
248
|
+
): T | undefined {
|
|
249
|
+
if (!entry) return undefined;
|
|
250
|
+
const order = new Map(
|
|
251
|
+
ZAI_CODING_MODEL_ORDER.map((modelId, index) => [modelId, index]),
|
|
252
|
+
);
|
|
253
|
+
const modelById = new Map(entry.models.map((model) => [model.id, model]));
|
|
254
|
+
for (const model of ZAI_CODING_MANUAL_MODELS) {
|
|
255
|
+
if (!modelById.has(model.id)) modelById.set(model.id, model);
|
|
256
|
+
}
|
|
257
|
+
const models = Array.from(modelById.values()).sort((a, b) => {
|
|
258
|
+
const orderA = order.get(a.id) ?? Number.MAX_SAFE_INTEGER;
|
|
259
|
+
const orderB = order.get(b.id) ?? Number.MAX_SAFE_INTEGER;
|
|
260
|
+
if (orderA !== orderB) return orderA - orderB;
|
|
261
|
+
return a.id.localeCompare(b.id);
|
|
262
|
+
});
|
|
263
|
+
return {
|
|
264
|
+
...entry,
|
|
265
|
+
models,
|
|
266
|
+
label: 'Z.AI Coding Plan',
|
|
267
|
+
env: ['ZAI_CODING_API_KEY'],
|
|
268
|
+
api: 'https://api.z.ai/api/coding/paas/v4',
|
|
269
|
+
doc: 'https://docs.z.ai/devpack/overview',
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
217
273
|
export function mergeManualCatalog(
|
|
218
274
|
base: CatalogMap,
|
|
219
275
|
): Record<BuiltInProviderId, ProviderCatalogEntry> {
|
|
@@ -231,6 +287,10 @@ export function mergeManualCatalog(
|
|
|
231
287
|
if (kimiEntry) {
|
|
232
288
|
merged.kimi = kimiEntry;
|
|
233
289
|
}
|
|
290
|
+
const zaiCodingEntry = applyZaiCodingCatalogMetadata(merged[ZAI_CODING_ID]);
|
|
291
|
+
if (zaiCodingEntry) {
|
|
292
|
+
merged[ZAI_CODING_ID] = zaiCodingEntry;
|
|
293
|
+
}
|
|
234
294
|
if (manualEntry) {
|
|
235
295
|
merged[OTTOROUTER_ID] = manualEntry;
|
|
236
296
|
}
|
|
@@ -4,6 +4,7 @@ import { catalog } from './catalog-merged.ts';
|
|
|
4
4
|
export type ZaiProviderConfig = {
|
|
5
5
|
apiKey?: string;
|
|
6
6
|
baseURL?: string;
|
|
7
|
+
fetch?: typeof fetch;
|
|
7
8
|
};
|
|
8
9
|
|
|
9
10
|
export function createZaiModel(model: string, config?: ZaiProviderConfig) {
|
|
@@ -21,6 +22,7 @@ export function createZaiModel(model: string, config?: ZaiProviderConfig) {
|
|
|
21
22
|
name: entry?.label ?? 'Z.AI',
|
|
22
23
|
baseURL,
|
|
23
24
|
headers,
|
|
25
|
+
fetch: config?.fetch,
|
|
24
26
|
});
|
|
25
27
|
|
|
26
28
|
return instance(model);
|
|
@@ -44,6 +46,7 @@ export function createZaiCodingModel(
|
|
|
44
46
|
name: entry?.label ?? 'Z.AI Coding',
|
|
45
47
|
baseURL,
|
|
46
48
|
headers,
|
|
49
|
+
fetch: config?.fetch,
|
|
47
50
|
});
|
|
48
51
|
|
|
49
52
|
return instance(model);
|