@ottocode/sdk 0.1.314 → 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/config/src/index.ts +2 -1
- package/src/config/src/manager.ts +1 -0
- package/src/core/src/providers/resolver.ts +3 -2
- package/src/core/src/tools/bin-manager.ts +88 -33
- package/src/core/src/tools/builtin/fs/edit.txt +1 -1
- package/src/core/src/tools/builtin/fs/index.ts +2 -0
- package/src/core/src/tools/builtin/fs/multiedit.txt +1 -1
- package/src/core/src/tools/builtin/glob.txt +4 -0
- package/src/core/src/tools/builtin/patch/indentation.ts +8 -1
- package/src/core/src/tools/builtin/patch/normalize.ts +4 -0
- package/src/core/src/tools/builtin/patch/repair.ts +42 -0
- package/src/core/src/tools/builtin/patch.txt +2 -0
- package/src/core/src/tools/builtin/search.txt +4 -0
- package/src/core/src/tools/builtin/shell.ts +161 -22
- package/src/core/src/tools/builtin/shell.txt +15 -1
- package/src/core/src/tools/loader.ts +8 -4
- package/src/index.ts +2 -5
- package/src/prompts/src/agents/build.txt +2 -2
- package/src/prompts/src/providers/{moonshot.txt → kimi.txt} +1 -1
- package/src/prompts/src/providers.ts +5 -5
- package/src/providers/src/catalog-manual.ts +101 -9
- package/src/providers/src/catalog.ts +74 -34
- package/src/providers/src/env.ts +4 -7
- package/src/providers/src/index.ts +3 -9
- package/src/providers/src/{moonshot-client.ts → kimi-client.ts} +131 -15
- package/src/providers/src/model-merge.ts +7 -1
- package/src/providers/src/pricing.ts +1 -1
- package/src/providers/src/registry.ts +8 -19
- package/src/providers/src/utils.ts +7 -8
- package/src/providers/src/zai-client.ts +3 -0
- package/src/types/src/config.ts +1 -0
- package/src/types/src/provider.ts +4 -4
|
@@ -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,7 +45,18 @@ 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']);
|
|
59
|
+
const REDIRECTED_GLOB_COMMANDS = new Set(['find', 'fd']);
|
|
49
60
|
|
|
50
61
|
/**
|
|
51
62
|
* Detect commands that start with a standalone grep-style search binary.
|
|
@@ -53,23 +64,78 @@ const REDIRECTED_SEARCH_COMMANDS = new Set(['grep', 'egrep', 'fgrep', 'rg']);
|
|
|
53
64
|
* with grep/rg are redirected to the dedicated `search` tool.
|
|
54
65
|
*/
|
|
55
66
|
export function findRedirectedSearchCommand(cmd: string): string | null {
|
|
67
|
+
return findRepositoryDiscoveryCommand(cmd, 'search')?.command ?? null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function commandTokens(segment: string): string[] {
|
|
71
|
+
const tokens = segment.trim().split(/\s+/).filter(Boolean);
|
|
72
|
+
let index = 0;
|
|
73
|
+
while (
|
|
74
|
+
index < tokens.length &&
|
|
75
|
+
/^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[index] ?? '')
|
|
76
|
+
) {
|
|
77
|
+
index++;
|
|
78
|
+
}
|
|
79
|
+
if (tokens[index] === 'command') index++;
|
|
80
|
+
return tokens.slice(index);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function findRepositoryDiscoveryCommand(
|
|
84
|
+
cmd: string,
|
|
85
|
+
kind?: 'search' | 'glob',
|
|
86
|
+
): { command: string; tool: 'search' | 'glob' } | null {
|
|
56
87
|
const segments = cmd.split(/&&|\|\||;|\n/);
|
|
57
88
|
for (const segment of segments) {
|
|
58
|
-
const tokens = segment
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
89
|
+
const tokens = commandTokens(segment);
|
|
90
|
+
const bin = tokens[0]?.split('/').pop() ?? '';
|
|
91
|
+
const second = tokens[1] ?? '';
|
|
92
|
+
if ((!kind || kind === 'search') && bin === 'git' && second === 'grep') {
|
|
93
|
+
return { command: 'git grep', tool: 'search' };
|
|
94
|
+
}
|
|
95
|
+
if ((!kind || kind === 'search') && REDIRECTED_SEARCH_COMMANDS.has(bin)) {
|
|
96
|
+
return { command: bin, tool: 'search' };
|
|
97
|
+
}
|
|
98
|
+
if ((!kind || kind === 'glob') && REDIRECTED_GLOB_COMMANDS.has(bin)) {
|
|
99
|
+
return { command: bin, tool: 'glob' };
|
|
100
|
+
}
|
|
101
|
+
if ((!kind || kind === 'glob') && bin === 'ls' && segment.includes('**')) {
|
|
102
|
+
return { command: 'ls **', tool: 'glob' };
|
|
65
103
|
}
|
|
66
|
-
if (tokens[index] === 'command') index++;
|
|
67
|
-
const bin = tokens[index]?.split('/').pop() ?? '';
|
|
68
|
-
if (REDIRECTED_SEARCH_COMMANDS.has(bin)) return bin;
|
|
69
104
|
}
|
|
70
105
|
return null;
|
|
71
106
|
}
|
|
72
107
|
|
|
108
|
+
function repositoryDiscoveryHint(cmd: string): string | undefined {
|
|
109
|
+
const discovery = findRepositoryDiscoveryCommand(cmd);
|
|
110
|
+
if (!discovery) return undefined;
|
|
111
|
+
return discovery.tool === 'search'
|
|
112
|
+
? `Tip: For repository content search, prefer the search tool instead of shelling out to ${discovery.command}. It is indexed, faster, and returns structured file:line matches.`
|
|
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.`;
|
|
114
|
+
}
|
|
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
|
+
|
|
73
139
|
export type ShellOutputMode = 'auto' | 'full' | 'tail';
|
|
74
140
|
|
|
75
141
|
const DEFAULT_TAIL_LINES = 100;
|
|
@@ -136,6 +202,9 @@ type ShellResult = ToolResponse<{
|
|
|
136
202
|
stderrTruncated?: boolean;
|
|
137
203
|
stderrOriginalBytes?: number;
|
|
138
204
|
stderrShownBytes?: number;
|
|
205
|
+
discoveryHint?: string;
|
|
206
|
+
envMode?: ShellEnvMode;
|
|
207
|
+
envHint?: string;
|
|
139
208
|
}>;
|
|
140
209
|
|
|
141
210
|
type ShellStreamChunk =
|
|
@@ -165,7 +234,7 @@ const shellInputSchema = z
|
|
|
165
234
|
cmd: z
|
|
166
235
|
.string()
|
|
167
236
|
.describe(
|
|
168
|
-
'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.',
|
|
169
238
|
),
|
|
170
239
|
cwd: z
|
|
171
240
|
.string()
|
|
@@ -181,6 +250,13 @@ const shellInputSchema = z
|
|
|
181
250
|
.optional()
|
|
182
251
|
.default(300000)
|
|
183
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
|
+
),
|
|
184
260
|
outputMode: z
|
|
185
261
|
.enum(['auto', 'full', 'tail'])
|
|
186
262
|
.optional()
|
|
@@ -236,6 +312,7 @@ export function buildShellTool(projectRoot: string): {
|
|
|
236
312
|
cwd,
|
|
237
313
|
allowNonZeroExit,
|
|
238
314
|
timeout = 300000,
|
|
315
|
+
envMode = 'fast',
|
|
239
316
|
outputMode = 'auto',
|
|
240
317
|
tailLines = DEFAULT_TAIL_LINES,
|
|
241
318
|
maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES,
|
|
@@ -263,6 +340,7 @@ export function buildShellTool(projectRoot: string): {
|
|
|
263
340
|
cwd: absCwd,
|
|
264
341
|
allowNonZeroExit,
|
|
265
342
|
timeout,
|
|
343
|
+
envMode,
|
|
266
344
|
outputMode,
|
|
267
345
|
tailLines,
|
|
268
346
|
maxOutputBytes,
|
|
@@ -271,7 +349,7 @@ export function buildShellTool(projectRoot: string): {
|
|
|
271
349
|
) as AsyncIterable<ShellStreamChunk> | ShellResult;
|
|
272
350
|
}
|
|
273
351
|
|
|
274
|
-
const shellConfig = getShellExecutionConfig(finalCmd);
|
|
352
|
+
const shellConfig = getShellExecutionConfig(finalCmd, { envMode });
|
|
275
353
|
const proc = spawn(shellConfig.command, shellConfig.args, {
|
|
276
354
|
cwd: absCwd,
|
|
277
355
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -284,8 +362,11 @@ export function buildShellTool(projectRoot: string): {
|
|
|
284
362
|
let didTimeout = false;
|
|
285
363
|
let didAbort = false;
|
|
286
364
|
let settled = false;
|
|
365
|
+
let terminating = false;
|
|
287
366
|
let done = false;
|
|
288
367
|
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
368
|
+
let killEscalationId: ReturnType<typeof setTimeout> | null = null;
|
|
369
|
+
let fallbackSettleId: ReturnType<typeof setTimeout> | null = null;
|
|
289
370
|
const queue: ShellStreamChunk[] = [];
|
|
290
371
|
let notify: (() => void) | null = null;
|
|
291
372
|
|
|
@@ -348,6 +429,8 @@ export function buildShellTool(projectRoot: string): {
|
|
|
348
429
|
details.maxOutputBytes = maxOutputBytes;
|
|
349
430
|
}
|
|
350
431
|
if (timeoutId) clearTimeout(timeoutId);
|
|
432
|
+
if (killEscalationId) clearTimeout(killEscalationId);
|
|
433
|
+
if (fallbackSettleId) clearTimeout(fallbackSettleId);
|
|
351
434
|
if (abortSignal) {
|
|
352
435
|
abortSignal.removeEventListener('abort', onAbort);
|
|
353
436
|
}
|
|
@@ -356,11 +439,54 @@ export function buildShellTool(projectRoot: string): {
|
|
|
356
439
|
wake();
|
|
357
440
|
};
|
|
358
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
|
+
|
|
359
486
|
const onAbort = () => {
|
|
360
487
|
if (settled) return;
|
|
361
488
|
didAbort = true;
|
|
362
|
-
|
|
363
|
-
else proc.kill('SIGTERM');
|
|
489
|
+
terminate(abortResult);
|
|
364
490
|
};
|
|
365
491
|
|
|
366
492
|
if (abortSignal) {
|
|
@@ -370,8 +496,7 @@ export function buildShellTool(projectRoot: string): {
|
|
|
370
496
|
if (timeout > 0) {
|
|
371
497
|
timeoutId = setTimeout(() => {
|
|
372
498
|
didTimeout = true;
|
|
373
|
-
|
|
374
|
-
else proc.kill();
|
|
499
|
+
terminate(timeoutResult);
|
|
375
500
|
}, timeout);
|
|
376
501
|
}
|
|
377
502
|
|
|
@@ -394,12 +519,20 @@ export function buildShellTool(projectRoot: string): {
|
|
|
394
519
|
});
|
|
395
520
|
|
|
396
521
|
proc.on('close', (exitCode) => {
|
|
522
|
+
const resolvedExitCode = exitCode ?? 0;
|
|
523
|
+
const envHint = detectShellEnvHint({
|
|
524
|
+
stdout,
|
|
525
|
+
stderr,
|
|
526
|
+
exitCode: resolvedExitCode,
|
|
527
|
+
envMode,
|
|
528
|
+
});
|
|
397
529
|
if (didAbort) {
|
|
398
530
|
settle(
|
|
399
531
|
createToolError(`Command aborted by user: ${cmd}`, 'abort', {
|
|
400
532
|
cmd,
|
|
401
533
|
stdout,
|
|
402
534
|
stderr,
|
|
535
|
+
envMode,
|
|
403
536
|
...(outputMode === 'tail' || outputMode === 'auto'
|
|
404
537
|
? { outputMode, tailLines, maxOutputBytes }
|
|
405
538
|
: { outputMode, maxOutputBytes }),
|
|
@@ -418,6 +551,7 @@ export function buildShellTool(projectRoot: string): {
|
|
|
418
551
|
value: timeout,
|
|
419
552
|
stdout,
|
|
420
553
|
stderr,
|
|
554
|
+
envMode,
|
|
421
555
|
...(outputMode === 'tail' || outputMode === 'auto'
|
|
422
556
|
? { outputMode, tailLines, maxOutputBytes }
|
|
423
557
|
: { outputMode, maxOutputBytes }),
|
|
@@ -428,15 +562,17 @@ export function buildShellTool(projectRoot: string): {
|
|
|
428
562
|
return;
|
|
429
563
|
}
|
|
430
564
|
|
|
431
|
-
if (
|
|
565
|
+
if (resolvedExitCode !== 0 && !allowNonZeroExit) {
|
|
432
566
|
const errorDetail = stderr.trim() || stdout.trim() || '';
|
|
433
|
-
const errorMsg = `Command failed with exit code ${
|
|
567
|
+
const errorMsg = `Command failed with exit code ${resolvedExitCode}${errorDetail ? `\n\n${errorDetail}` : ''}`;
|
|
434
568
|
settle(
|
|
435
569
|
createToolError(errorMsg, 'execution', {
|
|
436
|
-
exitCode,
|
|
570
|
+
exitCode: resolvedExitCode,
|
|
437
571
|
stdout,
|
|
438
572
|
stderr,
|
|
439
573
|
cmd,
|
|
574
|
+
envMode,
|
|
575
|
+
...(envHint ? { envHint } : {}),
|
|
440
576
|
...(outputMode === 'tail' || outputMode === 'auto'
|
|
441
577
|
? { outputMode, tailLines, maxOutputBytes }
|
|
442
578
|
: { outputMode, maxOutputBytes }),
|
|
@@ -446,17 +582,20 @@ export function buildShellTool(projectRoot: string): {
|
|
|
446
582
|
return;
|
|
447
583
|
}
|
|
448
584
|
|
|
585
|
+
const discoveryHint = repositoryDiscoveryHint(finalCmd);
|
|
449
586
|
settle({
|
|
450
587
|
ok: true,
|
|
451
|
-
exitCode:
|
|
588
|
+
exitCode: resolvedExitCode,
|
|
452
589
|
stdout,
|
|
453
590
|
stderr,
|
|
591
|
+
envMode,
|
|
592
|
+
...(discoveryHint ? { discoveryHint } : {}),
|
|
593
|
+
...(envHint ? { envHint } : {}),
|
|
454
594
|
...(outputMode === 'tail' || outputMode === 'auto'
|
|
455
595
|
? { outputMode, tailLines, maxOutputBytes }
|
|
456
596
|
: { outputMode, maxOutputBytes }),
|
|
457
597
|
});
|
|
458
598
|
});
|
|
459
|
-
|
|
460
599
|
proc.on('error', (err) => {
|
|
461
600
|
settle(
|
|
462
601
|
createToolError(
|
|
@@ -1,13 +1,27 @@
|
|
|
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.
|
|
8
17
|
|
|
9
18
|
**Strongly prefer the `search` tool over `grep`/`rg`, and `glob` over `find`.** The `search` tool is indexed and faster, returns structured `file:line` matches, and supports regex, `glob` includes, `path` scoping, and `ignoreCase` — use it for all repository content search. Only fall back to `grep`/`rg` via shell for cases `search` cannot handle (e.g. gitignored files like node_modules or build output). Pipelines that filter program output (e.g. `ps aux | grep node`) are fine.
|
|
10
19
|
|
|
20
|
+
Mapping from common shell habits:
|
|
21
|
+
- `grep -r` / `rg` / `git grep` over repo files → use `search`.
|
|
22
|
+
- `find` / `fd` / recursive `ls` for filenames → use `glob`.
|
|
23
|
+
- Build, test, package manager, diagnostics, process inspection → use `shell`.
|
|
24
|
+
|
|
11
25
|
## Usage tips
|
|
12
26
|
|
|
13
27
|
- Chain commands with `&&` to fail-fast.
|
|
@@ -142,7 +142,14 @@ async function discoverStaticProjectTools(
|
|
|
142
142
|
|
|
143
143
|
const discoveryPromise = (async () => {
|
|
144
144
|
const tools = new Map<string, Tool>();
|
|
145
|
-
|
|
145
|
+
const fsTools = buildFsTools(projectRoot);
|
|
146
|
+
for (const { name, tool } of fsTools.filter(({ name }) => name === 'read'))
|
|
147
|
+
tools.set(name, tool);
|
|
148
|
+
// Put apply_patch before exact replacement tools so models see it as the
|
|
149
|
+
// default editing path after reading files.
|
|
150
|
+
const ap = buildApplyPatchTool(projectRoot);
|
|
151
|
+
tools.set(ap.name, ap.tool);
|
|
152
|
+
for (const { name, tool } of fsTools.filter(({ name }) => name !== 'read'))
|
|
146
153
|
tools.set(name, tool);
|
|
147
154
|
for (const { name, tool } of buildGitTools(projectRoot))
|
|
148
155
|
tools.set(name, tool);
|
|
@@ -155,9 +162,6 @@ async function discoverStaticProjectTools(
|
|
|
155
162
|
tools.set(search.name, search.tool);
|
|
156
163
|
const glob = buildGlobTool(projectRoot);
|
|
157
164
|
tools.set(glob.name, glob.tool);
|
|
158
|
-
// Patch/apply
|
|
159
|
-
const ap = buildApplyPatchTool(projectRoot);
|
|
160
|
-
tools.set(ap.name, ap.tool);
|
|
161
165
|
// Todo tracking
|
|
162
166
|
tools.set('update_todos', updateTodosTool);
|
|
163
167
|
// Web search
|
package/src/index.ts
CHANGED
|
@@ -81,6 +81,7 @@ export type {
|
|
|
81
81
|
} from './providers/src/index.ts';
|
|
82
82
|
export {
|
|
83
83
|
isBuiltInProviderId,
|
|
84
|
+
resolveBuiltInProviderCatalogId,
|
|
84
85
|
getProviderSettings,
|
|
85
86
|
getProviderDefinition,
|
|
86
87
|
hasConfiguredProvider,
|
|
@@ -162,13 +163,9 @@ export { createOpencodeModel } from './providers/src/index.ts';
|
|
|
162
163
|
export type { OpencodeProviderConfig } from './providers/src/index.ts';
|
|
163
164
|
export {
|
|
164
165
|
createKimiModel,
|
|
165
|
-
createMoonshotModel,
|
|
166
166
|
readKimiApiKeyFromEnv,
|
|
167
167
|
} from './providers/src/index.ts';
|
|
168
|
-
export type {
|
|
169
|
-
KimiProviderConfig,
|
|
170
|
-
MoonshotProviderConfig,
|
|
171
|
-
} from './providers/src/index.ts';
|
|
168
|
+
export type { KimiProviderConfig } from './providers/src/index.ts';
|
|
172
169
|
export { createMinimaxModel } from './providers/src/index.ts';
|
|
173
170
|
export type { MinimaxProviderConfig } from './providers/src/index.ts';
|
|
174
171
|
export {
|
|
@@ -8,8 +8,8 @@ You help with coding and build tasks.
|
|
|
8
8
|
|
|
9
9
|
Pick the right tool for the job (each tool's description has its full contract):
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
- Use
|
|
11
|
+
- Prefer `apply_patch` for code and text changes when it is available. It gives the clearest diff preview and handles targeted, structural, and multi-file edits.
|
|
12
|
+
- Use exact-replacement tools (`edit`/`multiedit`) when a patch would be awkward, when you have a precise replacement block, or after patch attempts fail.
|
|
13
13
|
- Use `write` only for NEW files or >70% full-file rewrites. Never use it for targeted edits.
|
|
14
14
|
|
|
15
15
|
**Always read a file immediately before editing it.** Memory and earlier context are not reliable — the file may have changed.
|
|
@@ -14,7 +14,7 @@ import PROVIDER_ANTHROPIC from './providers/anthropic.txt' with {
|
|
|
14
14
|
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
15
15
|
import PROVIDER_GOOGLE from './providers/google.txt' with { type: 'text' };
|
|
16
16
|
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
17
|
-
import
|
|
17
|
+
import PROVIDER_KIMI from './providers/kimi.txt' with { type: 'text' };
|
|
18
18
|
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
19
19
|
import PROVIDER_DEFAULT from './providers/default.txt' with { type: 'text' };
|
|
20
20
|
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
|
@@ -24,7 +24,7 @@ const FAMILY_PROMPTS: Record<string, string> = {
|
|
|
24
24
|
openai: PROVIDER_OPENAI,
|
|
25
25
|
anthropic: PROVIDER_ANTHROPIC,
|
|
26
26
|
google: PROVIDER_GOOGLE,
|
|
27
|
-
|
|
27
|
+
kimi: PROVIDER_KIMI,
|
|
28
28
|
glm: PROVIDER_GLM,
|
|
29
29
|
minimax: PROVIDER_DEFAULT,
|
|
30
30
|
};
|
|
@@ -142,9 +142,9 @@ export async function providerBasePrompt(
|
|
|
142
142
|
const result = PROVIDER_GOOGLE.trim();
|
|
143
143
|
return { prompt: result, resolvedType: 'google' };
|
|
144
144
|
}
|
|
145
|
-
if (id === '
|
|
146
|
-
const result =
|
|
147
|
-
return { prompt: result, resolvedType: '
|
|
145
|
+
if (id === 'kimi') {
|
|
146
|
+
const result = PROVIDER_KIMI.trim();
|
|
147
|
+
return { prompt: result, resolvedType: 'kimi' };
|
|
148
148
|
}
|
|
149
149
|
if (id === 'zai' || id === 'zai-coding') {
|
|
150
150
|
const result = PROVIDER_GLM.trim();
|
|
@@ -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
|
{
|
|
@@ -46,7 +75,7 @@ const OWNER_NPM: Record<ModelOwner, string> = {
|
|
|
46
75
|
google: '@ai-sdk/google',
|
|
47
76
|
openrouter: '@openrouter/ai-sdk-provider',
|
|
48
77
|
xai: '@ai-sdk/xai',
|
|
49
|
-
|
|
78
|
+
kimi: '@ai-sdk/openai-compatible',
|
|
50
79
|
qwen: '@ai-sdk/openai-compatible',
|
|
51
80
|
zai: '@ai-sdk/openai-compatible',
|
|
52
81
|
minimax: '@ai-sdk/anthropic',
|
|
@@ -162,26 +191,85 @@ const DEPRECATED_KIMI_MODEL_IDS = new Set([
|
|
|
162
191
|
'kimi-k2-turbo-preview',
|
|
163
192
|
]);
|
|
164
193
|
|
|
194
|
+
const KIMI_MANUAL_MODELS: ModelInfo[] = [
|
|
195
|
+
{
|
|
196
|
+
id: 'kimi-k2.7-code-highspeed',
|
|
197
|
+
ownedBy: 'kimi',
|
|
198
|
+
label: 'Kimi K2.7 Code Highspeed',
|
|
199
|
+
modalities: { input: ['text', 'image', 'video'], output: ['text'] },
|
|
200
|
+
toolCall: true,
|
|
201
|
+
reasoningText: true,
|
|
202
|
+
attachment: true,
|
|
203
|
+
temperature: false,
|
|
204
|
+
knowledge: '2025-01',
|
|
205
|
+
openWeights: true,
|
|
206
|
+
cost: { input: 1.9, output: 8, cacheRead: 0.38 },
|
|
207
|
+
limit: { context: 262_144, output: 262_144 },
|
|
208
|
+
},
|
|
209
|
+
];
|
|
210
|
+
|
|
165
211
|
export function filterAvailableKimiModels(models: ModelInfo[]): ModelInfo[] {
|
|
166
212
|
return models.filter((model) => !DEPRECATED_KIMI_MODEL_IDS.has(model.id));
|
|
167
213
|
}
|
|
168
214
|
|
|
215
|
+
function appendKimiManualModels(models: ModelInfo[]): ModelInfo[] {
|
|
216
|
+
const manualById = new Map(
|
|
217
|
+
KIMI_MANUAL_MODELS.map((model) => [model.id, model]),
|
|
218
|
+
);
|
|
219
|
+
const mergedModels = models.map((model) => {
|
|
220
|
+
const override = manualById.get(model.id);
|
|
221
|
+
return override ? { ...model, ...override } : model;
|
|
222
|
+
});
|
|
223
|
+
const existingIds = new Set(mergedModels.map((model) => model.id));
|
|
224
|
+
const missingModels = KIMI_MANUAL_MODELS.filter(
|
|
225
|
+
(model) => !existingIds.has(model.id),
|
|
226
|
+
);
|
|
227
|
+
return missingModels.length
|
|
228
|
+
? [...mergedModels, ...missingModels]
|
|
229
|
+
: mergedModels;
|
|
230
|
+
}
|
|
231
|
+
|
|
169
232
|
export function applyOfficialKimiCatalogMetadata<
|
|
170
233
|
T extends ProviderCatalogEntry,
|
|
171
234
|
>(entry: T | undefined): T | undefined {
|
|
172
235
|
if (!entry) return undefined;
|
|
173
|
-
const env = Array.from(
|
|
174
|
-
new Set(['KIMI_API_KEY', 'MOONSHOT_API_KEY', ...(entry.env ?? [])]),
|
|
175
|
-
);
|
|
236
|
+
const env = Array.from(new Set(['KIMI_API_KEY', ...(entry.env ?? [])]));
|
|
176
237
|
return {
|
|
177
238
|
...entry,
|
|
178
|
-
models: filterAvailableKimiModels(entry.models),
|
|
179
|
-
label:
|
|
239
|
+
models: appendKimiManualModels(filterAvailableKimiModels(entry.models)),
|
|
240
|
+
label: 'Kimi',
|
|
180
241
|
env,
|
|
181
242
|
doc: 'https://platform.kimi.ai/docs/api/overview.md',
|
|
182
243
|
};
|
|
183
244
|
}
|
|
184
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
|
+
|
|
185
273
|
export function mergeManualCatalog(
|
|
186
274
|
base: CatalogMap,
|
|
187
275
|
): Record<BuiltInProviderId, ProviderCatalogEntry> {
|
|
@@ -195,9 +283,13 @@ export function mergeManualCatalog(
|
|
|
195
283
|
if (xaiEntry) {
|
|
196
284
|
merged.xai = xaiEntry;
|
|
197
285
|
}
|
|
198
|
-
const
|
|
199
|
-
if (
|
|
200
|
-
merged.
|
|
286
|
+
const kimiEntry = applyOfficialKimiCatalogMetadata(merged.kimi);
|
|
287
|
+
if (kimiEntry) {
|
|
288
|
+
merged.kimi = kimiEntry;
|
|
289
|
+
}
|
|
290
|
+
const zaiCodingEntry = applyZaiCodingCatalogMetadata(merged[ZAI_CODING_ID]);
|
|
291
|
+
if (zaiCodingEntry) {
|
|
292
|
+
merged[ZAI_CODING_ID] = zaiCodingEntry;
|
|
201
293
|
}
|
|
202
294
|
if (manualEntry) {
|
|
203
295
|
merged[OTTOROUTER_ID] = manualEntry;
|