@openagents-org/agent-launcher 0.2.172 → 0.2.175
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/icons/commandcode.svg +3 -0
- package/package.json +1 -1
- package/registry.json +259 -35
- package/src/adapters/base.js +32 -0
- package/src/adapters/claude.js +10 -2
- package/src/adapters/codex.js +41 -1
- package/src/adapters/commandcode-stream.js +405 -0
- package/src/adapters/commandcode.js +853 -0
- package/src/adapters/hermes.js +56 -0
- package/src/adapters/index.js +4 -1
- package/src/adapters/kimi-stream.js +360 -0
- package/src/adapters/kimi.js +724 -13
- package/src/adapters/openclaw.js +28 -2
- package/src/adapters/opencode.js +86 -0
- package/src/adapters/pi-stream.js +28 -0
- package/src/adapters/pi.js +21 -2
- package/src/adapters/workspace-prompt.js +42 -8
- package/src/cli.js +182 -6
- package/src/config.js +13 -2
- package/src/daemon.js +192 -59
- package/src/env.js +22 -2
- package/src/index.js +13 -0
- package/src/installer.js +69 -1
- package/src/paths.js +315 -11
- package/src/probe.js +29 -8
- package/src/registry.js +4 -1
- package/src/workspace-client.js +1 -0
package/src/adapters/hermes.js
CHANGED
|
@@ -428,6 +428,62 @@ class HermesAdapter extends BaseAdapter {
|
|
|
428
428
|
await this.sendError(msgChannel, `Error processing message: ${e.message}`);
|
|
429
429
|
}
|
|
430
430
|
}
|
|
431
|
+
|
|
432
|
+
// ------------------------------------------------------------------
|
|
433
|
+
// Static: configure Hermes's native model config from LLM env vars
|
|
434
|
+
// ------------------------------------------------------------------
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Point Hermes at a custom OpenAI-compatible endpoint from user-provided
|
|
438
|
+
* LLM_API_KEY / LLM_BASE_URL / LLM_MODEL values. Called by saveAgentEnv when
|
|
439
|
+
* type === 'hermes', same pattern as OpenClaw's configureNativeAuth.
|
|
440
|
+
*
|
|
441
|
+
* Hermes has no env-var path for a custom endpoint (OPENAI_BASE_URL is
|
|
442
|
+
* honored only for its built-in openai provider) — the supported form is the
|
|
443
|
+
* `model:` section of its config.yaml:
|
|
444
|
+
* model: { default: <id>, provider: custom, base_url: <url>, api_key: <key> }
|
|
445
|
+
* Rather than hand-writing that YAML (and clobbering user config), drive
|
|
446
|
+
* Hermes's own `hermes config set` CLI: it owns the schema, merges into the
|
|
447
|
+
* existing file, and resolves its platform config path itself (~/.hermes on
|
|
448
|
+
* Unix, %LOCALAPPDATA%\hermes on native Windows, in-distro under WSL).
|
|
449
|
+
*
|
|
450
|
+
* Without this, a remotely-created hermes agent died on every message with
|
|
451
|
+
* "No inference provider configured" — the workspace's API-key field was a
|
|
452
|
+
* silent no-op for this type.
|
|
453
|
+
*
|
|
454
|
+
* Only acts when LLM_BASE_URL is set: a user signed in via `hermes setup`
|
|
455
|
+
* (the Nous login) must keep their own provider selection untouched.
|
|
456
|
+
*/
|
|
457
|
+
static configureNativeAuth(env) {
|
|
458
|
+
const vals = env || {};
|
|
459
|
+
const baseUrl = String(vals.LLM_BASE_URL || '').trim().replace(/\/+$/, '');
|
|
460
|
+
if (!baseUrl) return { skipped: true, reason: 'no LLM_BASE_URL' };
|
|
461
|
+
const model = String(vals.LLM_MODEL || '').trim();
|
|
462
|
+
const apiKey = String(vals.LLM_API_KEY || '').trim();
|
|
463
|
+
|
|
464
|
+
// Borrow the adapter's binary resolution without running the constructor.
|
|
465
|
+
const probe = Object.create(HermesAdapter.prototype);
|
|
466
|
+
probe._log = () => {};
|
|
467
|
+
const bin = probe._findHermesBinary();
|
|
468
|
+
if (!bin) return { skipped: true, reason: 'hermes binary not found' };
|
|
469
|
+
|
|
470
|
+
const { execFileSync } = require('child_process');
|
|
471
|
+
const run = (args) => {
|
|
472
|
+
const spawnBin = probe._hermesViaWsl ? 'wsl.exe' : bin;
|
|
473
|
+
const spawnArgs = probe._hermesViaWsl ? ['-e', bin, ...args] : args;
|
|
474
|
+
execFileSync(spawnBin, spawnArgs, {
|
|
475
|
+
stdio: 'ignore',
|
|
476
|
+
timeout: 20000,
|
|
477
|
+
windowsHide: true,
|
|
478
|
+
});
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
run(['config', 'set', 'model.provider', 'custom']);
|
|
482
|
+
run(['config', 'set', 'model.base_url', baseUrl]);
|
|
483
|
+
if (model) run(['config', 'set', 'model.default', model]);
|
|
484
|
+
if (apiKey) run(['config', 'set', 'model.api_key', apiKey]);
|
|
485
|
+
return { configured: true };
|
|
486
|
+
}
|
|
431
487
|
}
|
|
432
488
|
|
|
433
489
|
module.exports = HermesAdapter;
|
package/src/adapters/index.js
CHANGED
|
@@ -23,6 +23,7 @@ const MiniSweAgentAdapter = require('./mini');
|
|
|
23
23
|
const PiAdapter = require('./pi');
|
|
24
24
|
const DeepSeekAdapter = require('./deepseek');
|
|
25
25
|
const AntigravityAdapter = require('./antigravity');
|
|
26
|
+
const CommandCodeAdapter = require('./commandcode');
|
|
26
27
|
|
|
27
28
|
const ADAPTER_MAP = {
|
|
28
29
|
openclaw: OpenClawAdapter,
|
|
@@ -43,11 +44,12 @@ const ADAPTER_MAP = {
|
|
|
43
44
|
pi: PiAdapter,
|
|
44
45
|
deepseek: DeepSeekAdapter,
|
|
45
46
|
antigravity: AntigravityAdapter,
|
|
47
|
+
commandcode: CommandCodeAdapter,
|
|
46
48
|
};
|
|
47
49
|
|
|
48
50
|
/**
|
|
49
51
|
* Create an adapter instance for the given agent type.
|
|
50
|
-
* @param {string} type - Agent type (openclaw, claude, codex, opencode, nanoclaw, cursor, hermes, gemini, kimi, aider, goose, copilot, cline, amp, mini-swe-agent, pi, deepseek, antigravity)
|
|
52
|
+
* @param {string} type - Agent type (openclaw, claude, codex, opencode, nanoclaw, cursor, hermes, gemini, kimi, aider, goose, copilot, cline, amp, mini-swe-agent, pi, deepseek, antigravity, commandcode)
|
|
51
53
|
* @param {object} opts - Adapter constructor options
|
|
52
54
|
* @returns {BaseAdapter}
|
|
53
55
|
*/
|
|
@@ -79,6 +81,7 @@ module.exports = {
|
|
|
79
81
|
PiAdapter,
|
|
80
82
|
DeepSeekAdapter,
|
|
81
83
|
AntigravityAdapter,
|
|
84
|
+
CommandCodeAdapter,
|
|
82
85
|
createAdapter,
|
|
83
86
|
ADAPTER_MAP,
|
|
84
87
|
};
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, side-effect-free helpers for the Kimi Code CLI adapter.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is I/O-free and deterministic so it can be unit-tested
|
|
5
|
+
* without spawning the `kimi` binary or touching the network: the JSONL
|
|
6
|
+
* stream parser, the message interpreter, secret redaction, error/version
|
|
7
|
+
* classification, the argument builder, and the env mapping that turns the
|
|
8
|
+
* launcher's KIMI_* fields into the CLI's KIMI_MODEL_* provider variables.
|
|
9
|
+
*
|
|
10
|
+
* Verified against Kimi Code CLI v0.39.1 (`kimi -p --output-format
|
|
11
|
+
* stream-json`). The stream is JSONL on stdout where each line is a message:
|
|
12
|
+
* {role:"assistant", content, tool_calls?:[{type:"function",id,function:{name,arguments}}]}
|
|
13
|
+
* {role:"tool", tool_call_id, content}
|
|
14
|
+
* {role:"meta", type:"system.version", version}
|
|
15
|
+
* {role:"meta", type:"session.resume_hint", session_id, command, content}
|
|
16
|
+
* {role:"meta", type:"turn.step.retrying", failed_attempt, next_attempt,
|
|
17
|
+
* max_attempts, delay_ms, error_name, error_message}
|
|
18
|
+
* Fatal errors arrive on stderr as `error: ...` lines. Exit codes: 0 success,
|
|
19
|
+
* 1 permanent failure (config/auth/quota), 75 transient (retryable).
|
|
20
|
+
*
|
|
21
|
+
* IMPORTANT product distinction: two Moonshot products install a `kimi`
|
|
22
|
+
* binary. Kimi Code CLI (npm `@moonshot-ai/kimi-code`) versions are 0.x;
|
|
23
|
+
* the legacy Python `kimi-cli` (PyPI) is 1.x and has a DIFFERENT headless
|
|
24
|
+
* interface. A 1.x `kimi --version` therefore means the WRONG product.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
'use strict';
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Version / product classification
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
/** Pull a dotted version (e.g. "0.39.1") out of `kimi --version` output. */
|
|
34
|
+
function parseKimiVersion(raw) {
|
|
35
|
+
if (!raw || typeof raw !== 'string') return null;
|
|
36
|
+
const m = raw.match(/(\d+)\.(\d+)\.(\d+)(?:[-.][0-9A-Za-z.]+)?/);
|
|
37
|
+
return m ? m[0] : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Classify `kimi --version` output into a product identity.
|
|
42
|
+
* @returns {{version: string|null, product: 'kimi-code'|'legacy'|null}}
|
|
43
|
+
* 'kimi-code' → the real Kimi Code CLI (0.x) this adapter drives
|
|
44
|
+
* 'legacy' → the wound-down Python kimi-cli (1.x) — incompatible
|
|
45
|
+
* null → undetermined (unparseable output); proceed leniently
|
|
46
|
+
*/
|
|
47
|
+
function classifyKimiVersion(rawVersion) {
|
|
48
|
+
const version = parseKimiVersion(rawVersion);
|
|
49
|
+
if (!version) return { version: null, product: null };
|
|
50
|
+
const major = parseInt(version.split('.')[0], 10);
|
|
51
|
+
return { version, product: major === 0 ? 'kimi-code' : 'legacy' };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Secret redaction (same patterns as the other CLI adapters)
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
const SECRET_PATTERNS = [
|
|
59
|
+
/\bsk-[A-Za-z0-9._-]{8,}\b/g,
|
|
60
|
+
/\b(?:or|rk|gsk|ghp|gho|ghu|ghs|github_pat)[-_][A-Za-z0-9._-]{12,}\b/g,
|
|
61
|
+
/\b(?:Bearer|Basic)\s+[A-Za-z0-9._~+/=-]{8,}/gi,
|
|
62
|
+
/\bAKIA[0-9A-Z]{16}\b/g,
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
/** Redact obvious secret material from a free-text string. */
|
|
66
|
+
function redactSecrets(text) {
|
|
67
|
+
if (text == null) return text;
|
|
68
|
+
let out = String(text);
|
|
69
|
+
for (const re of SECRET_PATTERNS) out = out.replace(re, '«redacted»');
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Redact an argv array for logging. The prompt (arg after -p) is elided. */
|
|
74
|
+
function redactArgs(args) {
|
|
75
|
+
const out = [];
|
|
76
|
+
for (let i = 0; i < args.length; i++) {
|
|
77
|
+
const a = args[i];
|
|
78
|
+
if (a === '-p' || a === '--prompt') {
|
|
79
|
+
out.push(a, '«prompt»');
|
|
80
|
+
i++;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
out.push(redactSecrets(a));
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// JSONL stream parser
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
/** Line-buffered JSONL parser. push() returns complete parsed messages. */
|
|
93
|
+
class KimiStreamParser {
|
|
94
|
+
constructor() {
|
|
95
|
+
this._buf = '';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** @param {Buffer|string} chunk @returns {object[]} parsed messages */
|
|
99
|
+
push(chunk) {
|
|
100
|
+
this._buf += chunk.toString('utf-8');
|
|
101
|
+
const lines = this._buf.split('\n');
|
|
102
|
+
this._buf = lines.pop();
|
|
103
|
+
return parseLines(lines);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Flush any trailing partial line. */
|
|
107
|
+
flush() {
|
|
108
|
+
const rest = this._buf;
|
|
109
|
+
this._buf = '';
|
|
110
|
+
return parseLines([rest]);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function parseLines(lines) {
|
|
115
|
+
const out = [];
|
|
116
|
+
for (const line of lines) {
|
|
117
|
+
const t = line.trim();
|
|
118
|
+
if (!t || t[0] !== '{') continue;
|
|
119
|
+
try {
|
|
120
|
+
const obj = JSON.parse(t);
|
|
121
|
+
if (obj && typeof obj === 'object') out.push(obj);
|
|
122
|
+
} catch {
|
|
123
|
+
// Partial or non-JSON diagnostic line — skip.
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// Message interpretation
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
/** Flatten a message `content` (string or array of text blocks) to a string. */
|
|
134
|
+
function contentText(content) {
|
|
135
|
+
if (typeof content === 'string') return content;
|
|
136
|
+
if (Array.isArray(content)) {
|
|
137
|
+
return content
|
|
138
|
+
.map((b) => (b && typeof b === 'object' && typeof b.text === 'string' ? b.text : ''))
|
|
139
|
+
.join('');
|
|
140
|
+
}
|
|
141
|
+
return '';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Argument keys worth surfacing in a tool-status preview, best first.
|
|
145
|
+
const PREVIEW_KEYS = ['command', 'path', 'file_path', 'pattern', 'url', 'query', 'prompt', 'description'];
|
|
146
|
+
|
|
147
|
+
/** A short, redacted human preview for a tool call's JSON arguments. */
|
|
148
|
+
function toolPreview(argsJson) {
|
|
149
|
+
let args;
|
|
150
|
+
try {
|
|
151
|
+
args = JSON.parse(argsJson);
|
|
152
|
+
} catch {
|
|
153
|
+
return '';
|
|
154
|
+
}
|
|
155
|
+
if (!args || typeof args !== 'object') return '';
|
|
156
|
+
for (const key of PREVIEW_KEYS) {
|
|
157
|
+
const v = args[key];
|
|
158
|
+
if (typeof v === 'string' && v.trim()) {
|
|
159
|
+
const one = redactSecrets(v.replace(/\s+/g, ' ').trim());
|
|
160
|
+
return one.length > 80 ? one.slice(0, 80) + '…' : one;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return '';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Map one parsed stream message to zero or more adapter events:
|
|
168
|
+
* {kind:'text', text} assistant prose
|
|
169
|
+
* {kind:'tool_start', name, preview} a tool call was issued
|
|
170
|
+
* {kind:'tool_result', id, text} a tool finished
|
|
171
|
+
* {kind:'session', sessionId} resume hint (inline session id)
|
|
172
|
+
* {kind:'retrying', attempt, maxAttempts, message} provider retry
|
|
173
|
+
* {kind:'version', version} stream preamble
|
|
174
|
+
*/
|
|
175
|
+
function interpretKimiMessage(msg) {
|
|
176
|
+
if (!msg || typeof msg !== 'object') return [];
|
|
177
|
+
const events = [];
|
|
178
|
+
|
|
179
|
+
if (msg.role === 'assistant') {
|
|
180
|
+
const text = contentText(msg.content).trim();
|
|
181
|
+
if (text) events.push({ kind: 'text', text });
|
|
182
|
+
if (Array.isArray(msg.tool_calls)) {
|
|
183
|
+
for (const tc of msg.tool_calls) {
|
|
184
|
+
const fn = tc && typeof tc === 'object' ? tc.function : null;
|
|
185
|
+
const name = fn && typeof fn.name === 'string' ? fn.name : 'tool';
|
|
186
|
+
events.push({ kind: 'tool_start', name, preview: toolPreview(fn && fn.arguments) });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return events;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (msg.role === 'tool') {
|
|
193
|
+
events.push({
|
|
194
|
+
kind: 'tool_result',
|
|
195
|
+
id: typeof msg.tool_call_id === 'string' ? msg.tool_call_id : '',
|
|
196
|
+
text: contentText(msg.content),
|
|
197
|
+
});
|
|
198
|
+
return events;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (msg.role === 'meta') {
|
|
202
|
+
if (msg.type === 'session.resume_hint' && typeof msg.session_id === 'string' && msg.session_id) {
|
|
203
|
+
events.push({ kind: 'session', sessionId: msg.session_id });
|
|
204
|
+
} else if (msg.type === 'turn.step.retrying') {
|
|
205
|
+
events.push({
|
|
206
|
+
kind: 'retrying',
|
|
207
|
+
attempt: typeof msg.failed_attempt === 'number' ? msg.failed_attempt : 0,
|
|
208
|
+
maxAttempts: typeof msg.max_attempts === 'number' ? msg.max_attempts : 0,
|
|
209
|
+
message: typeof msg.error_message === 'string' ? redactSecrets(msg.error_message) : '',
|
|
210
|
+
});
|
|
211
|
+
} else if (msg.type === 'system.version') {
|
|
212
|
+
events.push({ kind: 'version', version: typeof msg.version === 'string' ? msg.version : '' });
|
|
213
|
+
}
|
|
214
|
+
return events;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return events;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
// Argument + environment builders
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Build the argv for one non-interactive run. Print mode (`-p`) already
|
|
226
|
+
* auto-approves tools — the CLI REJECTS combining it with --yolo/--auto/--plan
|
|
227
|
+
* (verified v0.39.1), so permission/plan flags must never be added here.
|
|
228
|
+
*/
|
|
229
|
+
function buildKimiArgs({ prompt, sessionId }) {
|
|
230
|
+
const args = [];
|
|
231
|
+
if (sessionId) args.push('-S', sessionId);
|
|
232
|
+
args.push('-p', prompt, '--output-format', 'stream-json');
|
|
233
|
+
return args;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const DEFAULT_KIMI_MODEL = 'kimi-k2.6';
|
|
237
|
+
// The CLI defaults max_completion_tokens to the model's full context size
|
|
238
|
+
// (262144), which OpenAI-compatible gateways reject (input+output > context).
|
|
239
|
+
// A fixed, generous output budget works against both Moonshot and gateways.
|
|
240
|
+
const DEFAULT_MAX_COMPLETION_TOKENS = '32768';
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Map the launcher's saved KIMI_* fields onto the env-var provider contract of
|
|
244
|
+
* Kimi Code CLI: setting KIMI_MODEL_NAME synthesizes an in-memory provider
|
|
245
|
+
* from KIMI_MODEL_API_KEY (required) + KIMI_MODEL_BASE_URL +
|
|
246
|
+
* KIMI_MODEL_PROVIDER_TYPE, bypassing `kimi login`.
|
|
247
|
+
*
|
|
248
|
+
* With no API key configured, the env is passed through untouched so the
|
|
249
|
+
* CLI's own `kimi login` credentials / config.toml apply.
|
|
250
|
+
*
|
|
251
|
+
* @returns {{env: object, viaEnvProvider: boolean}}
|
|
252
|
+
*/
|
|
253
|
+
function buildKimiEnv(agentEnv) {
|
|
254
|
+
const env = { ...(agentEnv || {}) };
|
|
255
|
+
const apiKey =
|
|
256
|
+
env.KIMI_MODEL_API_KEY ||
|
|
257
|
+
env.KIMI_API_KEY ||
|
|
258
|
+
env.MOONSHOT_API_KEY ||
|
|
259
|
+
'';
|
|
260
|
+
if (!apiKey) return { env, viaEnvProvider: false };
|
|
261
|
+
|
|
262
|
+
env.KIMI_MODEL_API_KEY = apiKey;
|
|
263
|
+
if (!env.KIMI_MODEL_NAME) env.KIMI_MODEL_NAME = env.KIMI_MODEL || DEFAULT_KIMI_MODEL;
|
|
264
|
+
const baseUrl = env.KIMI_MODEL_BASE_URL || env.KIMI_BASE_URL;
|
|
265
|
+
if (baseUrl) env.KIMI_MODEL_BASE_URL = baseUrl.replace(/\/$/, '');
|
|
266
|
+
if (!env.KIMI_MODEL_PROVIDER_TYPE) env.KIMI_MODEL_PROVIDER_TYPE = 'kimi';
|
|
267
|
+
if (!env.KIMI_MODEL_MAX_COMPLETION_TOKENS) {
|
|
268
|
+
env.KIMI_MODEL_MAX_COMPLETION_TOKENS = DEFAULT_MAX_COMPLETION_TOKENS;
|
|
269
|
+
}
|
|
270
|
+
return { env, viaEnvProvider: true };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
// Error classification
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
|
|
277
|
+
// Kimi Code CLI's documented exit codes.
|
|
278
|
+
const EXIT_PERMANENT = 1;
|
|
279
|
+
const EXIT_TRANSIENT = 75;
|
|
280
|
+
|
|
281
|
+
/** Extract the message from `error: ...` lines on stderr (last one wins). */
|
|
282
|
+
function extractStderrError(stderrText) {
|
|
283
|
+
if (!stderrText) return '';
|
|
284
|
+
let last = '';
|
|
285
|
+
for (const line of String(stderrText).split('\n')) {
|
|
286
|
+
const m = line.match(/^\s*error:\s*(.+)$/i);
|
|
287
|
+
if (m) last = m[1].trim();
|
|
288
|
+
}
|
|
289
|
+
// Strip the CLI's wrapper prefix so the user sees the actual cause.
|
|
290
|
+
return redactSecrets(last.replace(/^failed to run prompt:\s*/i, ''));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Classify a failed run into a user-facing message.
|
|
295
|
+
* @param {{code: number|null, signal: string|null, stderrText: string, retryMessage: string}} info
|
|
296
|
+
* @returns {{kind: string, userMessage: string}}
|
|
297
|
+
*/
|
|
298
|
+
function classifyKimiError(info) {
|
|
299
|
+
const { code, signal, stderrText, retryMessage } = info || {};
|
|
300
|
+
const detail = extractStderrError(stderrText) || redactSecrets(retryMessage || '');
|
|
301
|
+
|
|
302
|
+
if (/auth_error|401|unauthorized|invalid.{0,20}(api.?key|token)|credential/i.test(detail)) {
|
|
303
|
+
return {
|
|
304
|
+
kind: 'auth',
|
|
305
|
+
userMessage:
|
|
306
|
+
'Kimi authentication failed. Check KIMI_API_KEY in the launcher, or run `kimi login` in a terminal.' +
|
|
307
|
+
(detail ? `\n\nDetails: ${detail}` : ''),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (/no model configured|llm not set|kimi_model_api_key is missing/i.test(detail)) {
|
|
311
|
+
return {
|
|
312
|
+
kind: 'config',
|
|
313
|
+
userMessage:
|
|
314
|
+
'Kimi Code CLI is not configured. Set KIMI_API_KEY (plus optional KIMI_BASE_URL / KIMI_MODEL) ' +
|
|
315
|
+
'in the launcher, or run `kimi login` in a terminal.',
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
if (/rate.?limit|429|quota|insufficient.{0,20}balance/i.test(detail)) {
|
|
319
|
+
return {
|
|
320
|
+
kind: 'rate_limit',
|
|
321
|
+
userMessage: `Kimi hit a rate/quota limit. Please retry shortly.${detail ? `\n\nDetails: ${detail}` : ''}`,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
if (/context length|maximum context|too many tokens/i.test(detail)) {
|
|
325
|
+
return {
|
|
326
|
+
kind: 'context',
|
|
327
|
+
userMessage: `The request exceeded the model's context limits.${detail ? `\n\nDetails: ${detail}` : ''}`,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
if (code === EXIT_TRANSIENT) {
|
|
331
|
+
return {
|
|
332
|
+
kind: 'transient',
|
|
333
|
+
userMessage: `Kimi hit a temporary provider error — please try again.${detail ? `\n\nDetails: ${detail}` : ''}`,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
if (detail) {
|
|
337
|
+
return { kind: 'error', userMessage: `Kimi failed: ${detail}` };
|
|
338
|
+
}
|
|
339
|
+
const why = signal ? `terminated by signal ${signal}` : `exited with code ${code}`;
|
|
340
|
+
return { kind: 'error', userMessage: `Kimi Code CLI ${why}.` };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
module.exports = {
|
|
344
|
+
parseKimiVersion,
|
|
345
|
+
classifyKimiVersion,
|
|
346
|
+
redactSecrets,
|
|
347
|
+
redactArgs,
|
|
348
|
+
KimiStreamParser,
|
|
349
|
+
interpretKimiMessage,
|
|
350
|
+
contentText,
|
|
351
|
+
toolPreview,
|
|
352
|
+
buildKimiArgs,
|
|
353
|
+
buildKimiEnv,
|
|
354
|
+
extractStderrError,
|
|
355
|
+
classifyKimiError,
|
|
356
|
+
DEFAULT_KIMI_MODEL,
|
|
357
|
+
DEFAULT_MAX_COMPLETION_TOKENS,
|
|
358
|
+
EXIT_PERMANENT,
|
|
359
|
+
EXIT_TRANSIENT,
|
|
360
|
+
};
|