kimi-plugin-cc 2.4.5 → 2.5.1
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +14 -0
- package/README.md +42 -26
- package/dist/dsh/companion.js +174 -0
- package/dist/dsh/index.js +156 -0
- package/dist/hooks/install-paths.js +2 -2
- package/dist/version.js +1 -1
- package/docs/models.md +5 -1
- package/docs/registry-releases.md +20 -17
- package/docs/safety.md +22 -9
- package/docs/upstream-2.4.1-certification.md +6 -0
- package/package.json +40 -6
- package/plugins/kimi-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/kimi-codex/dist/dsh/companion.js +174 -0
- package/plugins/kimi-codex/dist/dsh/index.js +156 -0
- package/plugins/kimi-codex/dist/hooks/install-paths.js +2 -2
- package/plugins/kimi-codex/dist/version.js +1 -1
- package/plugins/kimi-dsh/LICENSE +201 -0
- package/plugins/kimi-dsh/README.md +80 -0
- package/plugins/kimi-dsh/cordis.patch.yml +45 -0
- package/plugins/kimi-dsh/dist/background-spawn.js +216 -0
- package/plugins/kimi-dsh/dist/cli-cancellation.js +43 -0
- package/plugins/kimi-dsh/dist/cli-client.js +876 -0
- package/plugins/kimi-dsh/dist/commands/ask.js +336 -0
- package/plugins/kimi-dsh/dist/commands/cancel.js +216 -0
- package/plugins/kimi-dsh/dist/commands/cli-helpers.js +201 -0
- package/plugins/kimi-dsh/dist/commands/models.js +140 -0
- package/plugins/kimi-dsh/dist/commands/not-implemented.js +4 -0
- package/plugins/kimi-dsh/dist/commands/pursue.js +307 -0
- package/plugins/kimi-dsh/dist/commands/recover-swarm.js +135 -0
- package/plugins/kimi-dsh/dist/commands/registry.js +74 -0
- package/plugins/kimi-dsh/dist/commands/repair-sessions.js +160 -0
- package/plugins/kimi-dsh/dist/commands/replay.js +124 -0
- package/plugins/kimi-dsh/dist/commands/rescue.js +347 -0
- package/plugins/kimi-dsh/dist/commands/result.js +66 -0
- package/plugins/kimi-dsh/dist/commands/review-gate.js +390 -0
- package/plugins/kimi-dsh/dist/commands/review.js +220 -0
- package/plugins/kimi-dsh/dist/commands/setup.js +1047 -0
- package/plugins/kimi-dsh/dist/commands/status.js +27 -0
- package/plugins/kimi-dsh/dist/commands/swarm.js +754 -0
- package/plugins/kimi-dsh/dist/commands/worker.js +25 -0
- package/plugins/kimi-dsh/dist/companion.js +97 -0
- package/plugins/kimi-dsh/dist/config.js +22 -0
- package/plugins/kimi-dsh/dist/dsh/companion.js +174 -0
- package/plugins/kimi-dsh/dist/dsh/index.js +156 -0
- package/plugins/kimi-dsh/dist/errors.js +40 -0
- package/plugins/kimi-dsh/dist/git.js +224 -0
- package/plugins/kimi-dsh/dist/help.js +43 -0
- package/plugins/kimi-dsh/dist/hook-escape.js +71 -0
- package/plugins/kimi-dsh/dist/hooks/approval-hook.js +101 -0
- package/plugins/kimi-dsh/dist/hooks/approval-policy.js +211 -0
- package/plugins/kimi-dsh/dist/hooks/config-safety.js +653 -0
- package/plugins/kimi-dsh/dist/hooks/install-paths.js +428 -0
- package/plugins/kimi-dsh/dist/hooks/install.js +210 -0
- package/plugins/kimi-dsh/dist/hooks/managed-block.js +619 -0
- package/plugins/kimi-dsh/dist/hooks/review-gate-stop.js +19 -0
- package/plugins/kimi-dsh/dist/job-id.js +12 -0
- package/plugins/kimi-dsh/dist/job-store.js +659 -0
- package/plugins/kimi-dsh/dist/jobs.js +217 -0
- package/plugins/kimi-dsh/dist/kimi-command.js +70 -0
- package/plugins/kimi-dsh/dist/kimi-engine.js +632 -0
- package/plugins/kimi-dsh/dist/kimi-errors.js +260 -0
- package/plugins/kimi-dsh/dist/kimi-home.js +9 -0
- package/plugins/kimi-dsh/dist/kimi-timeouts.js +52 -0
- package/plugins/kimi-dsh/dist/kimi-version-probe.js +216 -0
- package/plugins/kimi-dsh/dist/logging.js +22 -0
- package/plugins/kimi-dsh/dist/mcp/binding.js +58 -0
- package/plugins/kimi-dsh/dist/mcp/job-control.js +345 -0
- package/plugins/kimi-dsh/dist/mcp/launcher.js +257 -0
- package/plugins/kimi-dsh/dist/mcp/operations.js +301 -0
- package/plugins/kimi-dsh/dist/mcp/protocol.js +275 -0
- package/plugins/kimi-dsh/dist/mcp/server.js +8 -0
- package/plugins/kimi-dsh/dist/mcp/stdio.js +78 -0
- package/plugins/kimi-dsh/dist/mcp/supervised-child.js +15 -0
- package/plugins/kimi-dsh/dist/mcp/tool-result.js +7 -0
- package/plugins/kimi-dsh/dist/mcp/workspace.js +34 -0
- package/plugins/kimi-dsh/dist/model-command.js +32 -0
- package/plugins/kimi-dsh/dist/native-v2-preflight.js +417 -0
- package/plugins/kimi-dsh/dist/package.json +1 -0
- package/plugins/kimi-dsh/dist/parsing.js +620 -0
- package/plugins/kimi-dsh/dist/paths.js +27 -0
- package/plugins/kimi-dsh/dist/plugin-data.js +98 -0
- package/plugins/kimi-dsh/dist/process-tree.js +479 -0
- package/plugins/kimi-dsh/dist/prompt-size.js +18 -0
- package/plugins/kimi-dsh/dist/render.js +177 -0
- package/plugins/kimi-dsh/dist/rescue-approval.js +865 -0
- package/plugins/kimi-dsh/dist/schemas/review-gate-output.js +43 -0
- package/plugins/kimi-dsh/dist/session-recovery.js +200 -0
- package/plugins/kimi-dsh/dist/session-title.js +293 -0
- package/plugins/kimi-dsh/dist/stream-json.js +346 -0
- package/plugins/kimi-dsh/dist/swarm-activation.js +834 -0
- package/plugins/kimi-dsh/dist/swarm-recovery.js +134 -0
- package/plugins/kimi-dsh/dist/types.js +1 -0
- package/plugins/kimi-dsh/dist/vendor/shell-quote/LICENSE +24 -0
- package/plugins/kimi-dsh/dist/vendor/shell-quote/index.js +1 -0
- package/plugins/kimi-dsh/dist/vendor/shell-quote/parse.js +229 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/LICENSE +24 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/date.js +156 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/error.js +67 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/extract.js +293 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/parse.js +164 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/primitive.js +151 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/struct.js +156 -0
- package/plugins/kimi-dsh/dist/vendor/smol-toml/util.js +57 -0
- package/plugins/kimi-dsh/dist/version.js +7 -0
- package/plugins/kimi-dsh/package.json +48 -0
- package/plugins/kimi-dsh/scripts/companion.sh +37 -0
- package/plugins/kimi-dsh/scripts/review-gate-hook.sh +32 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
// Stream-JSON parser for `kimi -p --output-format stream-json`.
|
|
2
|
+
//
|
|
3
|
+
// Canonical record shapes (verified against kimi-code source at
|
|
4
|
+
// apps/kimi-code/src/cli/prompt-render.ts — `PromptJsonWriter` emits one JSON
|
|
5
|
+
// object per line via `JSON.stringify(message) + '\n'`):
|
|
6
|
+
//
|
|
7
|
+
// {"role":"assistant","content":"..."}
|
|
8
|
+
// {"role":"assistant","content":"...","tool_calls":[{type,id,function:{name,arguments}}]}
|
|
9
|
+
// {"role":"assistant","tool_calls":[...]}
|
|
10
|
+
// {"role":"tool","tool_call_id":"...","content":"..."}
|
|
11
|
+
// {"role":"meta","type":"session.resume_hint","session_id":"session_<uuid>","command":"kimi -r session_<uuid>","content":"To resume this session: kimi -r session_<uuid>"}
|
|
12
|
+
// {"role":"meta","type":"turn.step.retrying","failed_attempt":1,"next_attempt":2,"max_attempts":3,"delay_ms":300,"error_name":"APIProviderRateLimitError","error_message":"...","status_code":429}
|
|
13
|
+
// {"role":"meta","type":"system.version","version":"0.24.2"} // native-v2 marker; required first on native-v2 plans and filtered after capture
|
|
14
|
+
//
|
|
15
|
+
// {"type":"error","code":"session.occupied","message":"...","details":{...}} // 2.3.0; stdout-only refusal, exit 4
|
|
16
|
+
//
|
|
17
|
+
// Notes on what does and does not appear here:
|
|
18
|
+
// - session.resume_hint is emitted once at session end. Legacy 0.1.x sent
|
|
19
|
+
// the resume hint to stderr; stream-json uses this stdout meta record.
|
|
20
|
+
// - hook.result events arrive as role:"assistant" with the block rendered as
|
|
21
|
+
// plain text (see formatHookResultPlain in run-prompt.ts). Our
|
|
22
|
+
// PreToolUse hook's deny reason therefore surfaces in the assistant
|
|
23
|
+
// stream, not on a separate channel.
|
|
24
|
+
// - thinking.delta events are silently discarded by the CLI's
|
|
25
|
+
// PromptJsonWriter (`writeThinkingDelta(): void {}`).
|
|
26
|
+
// - tool.progress events are written to stderr only, never entering
|
|
27
|
+
// stream-json.
|
|
28
|
+
// - assistant messages are only emitted when content OR tool_calls are
|
|
29
|
+
// non-empty; we still defend against the empty case for resilience.
|
|
30
|
+
export const MAX_STREAM_JSON_LINE_BYTES = 1_048_576;
|
|
31
|
+
/**
|
|
32
|
+
* Stateful line-buffer parser. Hold partial-line bytes across chunk boundaries.
|
|
33
|
+
*
|
|
34
|
+
* Lifecycle:
|
|
35
|
+
* - `push(chunk)` — consume bytes; returns outcomes for any complete lines
|
|
36
|
+
* that finished within this chunk.
|
|
37
|
+
* - `flush()` — at end of stream, parse a valid trailing JSON record even
|
|
38
|
+
* without a final newline; malformed trailing input becomes diagnostic.
|
|
39
|
+
*
|
|
40
|
+
* Outcomes are non-throwing — malformed lines surface via `malformedLine` /
|
|
41
|
+
* `malformedReason` so callers can route them to a diagnostics log without
|
|
42
|
+
* crashing the parser. Blank lines (between records, or trailing whitespace)
|
|
43
|
+
* are skipped silently.
|
|
44
|
+
*/
|
|
45
|
+
export class StreamJsonParser {
|
|
46
|
+
buffer = "";
|
|
47
|
+
push(chunk) {
|
|
48
|
+
this.buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
49
|
+
const outcomes = this.drainCompleteLines();
|
|
50
|
+
if (this.buffer.indexOf("\n") === -1 &&
|
|
51
|
+
Buffer.byteLength(this.buffer, "utf8") > MAX_STREAM_JSON_LINE_BYTES) {
|
|
52
|
+
outcomes.push({
|
|
53
|
+
malformedLine: makeOversizePreview(this.buffer),
|
|
54
|
+
malformedReason: `stream-json line exceeded ${MAX_STREAM_JSON_LINE_BYTES} bytes without newline`,
|
|
55
|
+
});
|
|
56
|
+
this.buffer = "";
|
|
57
|
+
}
|
|
58
|
+
return outcomes;
|
|
59
|
+
}
|
|
60
|
+
flush() {
|
|
61
|
+
const remainder = this.buffer;
|
|
62
|
+
this.buffer = "";
|
|
63
|
+
if (remainder.length === 0)
|
|
64
|
+
return [];
|
|
65
|
+
if (remainder.replace(/\r/g, "").length === 0)
|
|
66
|
+
return [];
|
|
67
|
+
return [parseLine(remainder)];
|
|
68
|
+
}
|
|
69
|
+
drainCompleteLines() {
|
|
70
|
+
const out = [];
|
|
71
|
+
let newlineIndex = this.buffer.indexOf("\n");
|
|
72
|
+
while (newlineIndex !== -1) {
|
|
73
|
+
const line = this.buffer.slice(0, newlineIndex);
|
|
74
|
+
this.buffer = this.buffer.slice(newlineIndex + 1);
|
|
75
|
+
if (line.replace(/\r/g, "").length > 0) {
|
|
76
|
+
out.push(parseLine(line));
|
|
77
|
+
}
|
|
78
|
+
newlineIndex = this.buffer.indexOf("\n");
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function parseLine(line) {
|
|
84
|
+
const cleaned = line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
85
|
+
let parsed;
|
|
86
|
+
try {
|
|
87
|
+
parsed = JSON.parse(cleaned);
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
return {
|
|
91
|
+
malformedLine: line,
|
|
92
|
+
malformedReason: `json parse: ${err.message}`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (!isRecord(parsed)) {
|
|
96
|
+
return { malformedLine: line, malformedReason: "not a JSON object" };
|
|
97
|
+
}
|
|
98
|
+
const role = parsed["role"];
|
|
99
|
+
if (role === "assistant") {
|
|
100
|
+
return validateAssistant(parsed, line);
|
|
101
|
+
}
|
|
102
|
+
if (role === "tool") {
|
|
103
|
+
return validateToolResult(parsed, line);
|
|
104
|
+
}
|
|
105
|
+
if (role === "meta") {
|
|
106
|
+
return validateMeta(parsed, line);
|
|
107
|
+
}
|
|
108
|
+
if (role === undefined && parsed["type"] === "goal.summary") {
|
|
109
|
+
return validateGoalSummary(parsed, line);
|
|
110
|
+
}
|
|
111
|
+
if (role === undefined && parsed["type"] === "error") {
|
|
112
|
+
if (typeof parsed["code"] !== "string" || parsed["code"].length === 0 ||
|
|
113
|
+
typeof parsed["message"] !== "string" || parsed["message"].length === 0 ||
|
|
114
|
+
(parsed["details"] !== undefined && !isRecord(parsed["details"]))) {
|
|
115
|
+
return { malformedLine: line, malformedReason: "error record field has unexpected type" };
|
|
116
|
+
}
|
|
117
|
+
// Keep bounded diagnostic fields only; holder metadata is not needed and
|
|
118
|
+
// must not become a session-id source or be copied into persisted errors.
|
|
119
|
+
return { cliError: {
|
|
120
|
+
type: "error",
|
|
121
|
+
code: parsed["code"].slice(0, 200),
|
|
122
|
+
message: parsed["message"].slice(0, 4000),
|
|
123
|
+
} };
|
|
124
|
+
}
|
|
125
|
+
if (typeof role === "string" && role.length > 0) {
|
|
126
|
+
return { unknownRecord: { role, raw: parsed } };
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
malformedLine: line,
|
|
130
|
+
malformedReason: `unknown role: ${JSON.stringify(role)}`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function validateGoalSummary(parsed, line) {
|
|
134
|
+
// Each field is `T | null` upstream (null when no goal snapshot exists). A
|
|
135
|
+
// missing field is tolerated as null; a present-but-wrong-typed field is
|
|
136
|
+
// malformed (so a shape change surfaces in diagnostics rather than silently
|
|
137
|
+
// coercing). Mirrors the defensive posture of validateMeta.
|
|
138
|
+
const asStringOrNull = (v) => v === undefined || v === null ? null : typeof v === "string" ? v : undefined;
|
|
139
|
+
const asNumberOrNull = (v) => v === undefined || v === null
|
|
140
|
+
? null
|
|
141
|
+
: typeof v === "number" && Number.isFinite(v)
|
|
142
|
+
? v
|
|
143
|
+
: undefined;
|
|
144
|
+
const goalId = asStringOrNull(parsed["goalId"]);
|
|
145
|
+
const status = asStringOrNull(parsed["status"]);
|
|
146
|
+
const reason = asStringOrNull(parsed["reason"]);
|
|
147
|
+
const turnsUsed = asNumberOrNull(parsed["turnsUsed"]);
|
|
148
|
+
const tokensUsed = asNumberOrNull(parsed["tokensUsed"]);
|
|
149
|
+
const wallClockMs = asNumberOrNull(parsed["wallClockMs"]);
|
|
150
|
+
if (goalId === undefined ||
|
|
151
|
+
status === undefined ||
|
|
152
|
+
reason === undefined ||
|
|
153
|
+
turnsUsed === undefined ||
|
|
154
|
+
tokensUsed === undefined ||
|
|
155
|
+
wallClockMs === undefined) {
|
|
156
|
+
return { malformedLine: line, malformedReason: "goal.summary field has unexpected type" };
|
|
157
|
+
}
|
|
158
|
+
const goalSummary = {
|
|
159
|
+
type: "goal.summary",
|
|
160
|
+
goalId,
|
|
161
|
+
status,
|
|
162
|
+
reason,
|
|
163
|
+
turnsUsed,
|
|
164
|
+
tokensUsed,
|
|
165
|
+
wallClockMs,
|
|
166
|
+
};
|
|
167
|
+
return { goalSummary };
|
|
168
|
+
}
|
|
169
|
+
function validateMeta(parsed, line) {
|
|
170
|
+
const type = parsed["type"];
|
|
171
|
+
if (type === "session.resume_hint") {
|
|
172
|
+
const sessionId = parsed["session_id"];
|
|
173
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
174
|
+
return {
|
|
175
|
+
malformedLine: line,
|
|
176
|
+
malformedReason: "meta.session.resume_hint.session_id not non-empty string",
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
const record = {
|
|
180
|
+
role: "meta",
|
|
181
|
+
type: "session.resume_hint",
|
|
182
|
+
sessionId,
|
|
183
|
+
};
|
|
184
|
+
return { record };
|
|
185
|
+
}
|
|
186
|
+
if (type === "turn.step.retrying") {
|
|
187
|
+
const failedAttempt = parsed["failed_attempt"];
|
|
188
|
+
const nextAttempt = parsed["next_attempt"];
|
|
189
|
+
const maxAttempts = parsed["max_attempts"];
|
|
190
|
+
const delayMs = parsed["delay_ms"];
|
|
191
|
+
const errorName = parsed["error_name"];
|
|
192
|
+
const errorMessage = parsed["error_message"];
|
|
193
|
+
const statusCode = parsed["status_code"];
|
|
194
|
+
if (typeof failedAttempt !== "number" ||
|
|
195
|
+
!Number.isFinite(failedAttempt) ||
|
|
196
|
+
typeof nextAttempt !== "number" ||
|
|
197
|
+
!Number.isFinite(nextAttempt) ||
|
|
198
|
+
typeof maxAttempts !== "number" ||
|
|
199
|
+
!Number.isFinite(maxAttempts) ||
|
|
200
|
+
typeof delayMs !== "number" ||
|
|
201
|
+
!Number.isFinite(delayMs) ||
|
|
202
|
+
typeof errorName !== "string" ||
|
|
203
|
+
typeof errorMessage !== "string" ||
|
|
204
|
+
(statusCode !== undefined &&
|
|
205
|
+
(typeof statusCode !== "number" || !Number.isFinite(statusCode)))) {
|
|
206
|
+
return {
|
|
207
|
+
malformedLine: line,
|
|
208
|
+
malformedReason: "meta.turn.step.retrying field has unexpected type",
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const record = {
|
|
212
|
+
role: "meta",
|
|
213
|
+
type: "turn.step.retrying",
|
|
214
|
+
failedAttempt,
|
|
215
|
+
nextAttempt,
|
|
216
|
+
maxAttempts,
|
|
217
|
+
delayMs,
|
|
218
|
+
errorName,
|
|
219
|
+
errorMessage,
|
|
220
|
+
...(typeof statusCode === "number" && { statusCode }),
|
|
221
|
+
};
|
|
222
|
+
return { record };
|
|
223
|
+
}
|
|
224
|
+
if (type === "system.version") {
|
|
225
|
+
const version = parsed["version"];
|
|
226
|
+
if (typeof version !== "string" || version.length === 0) {
|
|
227
|
+
return {
|
|
228
|
+
malformedLine: line,
|
|
229
|
+
malformedReason: "meta.system.version.version not non-empty string",
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const record = {
|
|
233
|
+
role: "meta",
|
|
234
|
+
type: "system.version",
|
|
235
|
+
version,
|
|
236
|
+
};
|
|
237
|
+
return { record };
|
|
238
|
+
}
|
|
239
|
+
// Unknown meta types are diagnostic-only and nonfatal.
|
|
240
|
+
return {
|
|
241
|
+
malformedLine: line,
|
|
242
|
+
malformedReason: `unknown meta.type: ${JSON.stringify(type)}`,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function validateAssistant(parsed, line) {
|
|
246
|
+
const content = parsed["content"];
|
|
247
|
+
if (content !== undefined && typeof content !== "string") {
|
|
248
|
+
return { malformedLine: line, malformedReason: "assistant.content not string" };
|
|
249
|
+
}
|
|
250
|
+
const toolCallsRaw = parsed["tool_calls"];
|
|
251
|
+
let toolCalls;
|
|
252
|
+
if (toolCallsRaw !== undefined) {
|
|
253
|
+
if (!Array.isArray(toolCallsRaw)) {
|
|
254
|
+
return { malformedLine: line, malformedReason: "assistant.tool_calls not array" };
|
|
255
|
+
}
|
|
256
|
+
toolCalls = [];
|
|
257
|
+
for (const candidate of toolCallsRaw) {
|
|
258
|
+
const validated = validateToolCall(candidate);
|
|
259
|
+
if (validated === undefined) {
|
|
260
|
+
return {
|
|
261
|
+
malformedLine: line,
|
|
262
|
+
malformedReason: "assistant.tool_calls entry invalid",
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
toolCalls.push(validated);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (content === undefined && (toolCalls === undefined || toolCalls.length === 0)) {
|
|
269
|
+
return {
|
|
270
|
+
malformedLine: line,
|
|
271
|
+
malformedReason: "assistant has neither content nor tool_calls",
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
const record = {
|
|
275
|
+
role: "assistant",
|
|
276
|
+
...(content !== undefined && { content }),
|
|
277
|
+
...(toolCalls !== undefined && { tool_calls: toolCalls }),
|
|
278
|
+
};
|
|
279
|
+
return { record };
|
|
280
|
+
}
|
|
281
|
+
function validateToolCall(candidate) {
|
|
282
|
+
if (!isRecord(candidate))
|
|
283
|
+
return undefined;
|
|
284
|
+
if (candidate["type"] !== "function")
|
|
285
|
+
return undefined;
|
|
286
|
+
const id = candidate["id"];
|
|
287
|
+
if (typeof id !== "string" || id.length === 0)
|
|
288
|
+
return undefined;
|
|
289
|
+
const fn = candidate["function"];
|
|
290
|
+
if (!isRecord(fn))
|
|
291
|
+
return undefined;
|
|
292
|
+
const name = fn["name"];
|
|
293
|
+
const args = fn["arguments"];
|
|
294
|
+
if (typeof name !== "string" || typeof args !== "string")
|
|
295
|
+
return undefined;
|
|
296
|
+
return { type: "function", id, function: { name, arguments: args } };
|
|
297
|
+
}
|
|
298
|
+
function validateToolResult(parsed, line) {
|
|
299
|
+
const toolCallId = parsed["tool_call_id"];
|
|
300
|
+
if (typeof toolCallId !== "string" || toolCallId.length === 0) {
|
|
301
|
+
return {
|
|
302
|
+
malformedLine: line,
|
|
303
|
+
malformedReason: "tool.tool_call_id not non-empty string",
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
const content = parsed["content"];
|
|
307
|
+
if (typeof content !== "string") {
|
|
308
|
+
return { malformedLine: line, malformedReason: "tool.content not string" };
|
|
309
|
+
}
|
|
310
|
+
const record = {
|
|
311
|
+
role: "tool",
|
|
312
|
+
tool_call_id: toolCallId,
|
|
313
|
+
content,
|
|
314
|
+
};
|
|
315
|
+
return { record };
|
|
316
|
+
}
|
|
317
|
+
function isRecord(value) {
|
|
318
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Match kimi-code's stderr session announce line. In stream-json mode this
|
|
322
|
+
* is a 0.1.x compatibility fallback only — kimi 0.2.0+ emits the resume
|
|
323
|
+
* hint as a `role: meta, type: session.resume_hint` record on stdout (see
|
|
324
|
+
* SessionResumeHintRecord above and apps/kimi-code/src/cli/run-prompt.ts).
|
|
325
|
+
*
|
|
326
|
+
* The pattern accepts both 0.1.x bare-UUID format and 0.2.0+ `session_<uuid>`
|
|
327
|
+
* format so the fallback survives mixed-version environments. The captured
|
|
328
|
+
* token is whatever appears after `kimi -r ` — we treat it as an opaque
|
|
329
|
+
* session identifier and round-trip it verbatim via `kimi -r <token>`.
|
|
330
|
+
*
|
|
331
|
+
* Returns the captured session id, or undefined if no match.
|
|
332
|
+
*/
|
|
333
|
+
export function extractSessionIdFromStderr(stderr) {
|
|
334
|
+
// The accepted shapes are tightly anchored — both alternations require
|
|
335
|
+
// a full UUID payload. The `session_` prefix is the 0.2.0+ form; the
|
|
336
|
+
// bare UUID is the 0.1.x form. Without the anchoring, a malformed or
|
|
337
|
+
// hostile stderr line could pin `session_--------` (8 dashes) or any
|
|
338
|
+
// 8+ char hex-dash token as the captured session id, weakening the
|
|
339
|
+
// "anchored full-UUID regex" safety invariant documented in AGENTS.md.
|
|
340
|
+
const uuid = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
|
|
341
|
+
const pattern = new RegExp(`^To resume this session:\\s+kimi\\s+-r\\s+(session_${uuid}|${uuid})\\s*$`, "im");
|
|
342
|
+
return stderr.match(pattern)?.[1];
|
|
343
|
+
}
|
|
344
|
+
function makeOversizePreview(line) {
|
|
345
|
+
return `${line.slice(0, 200)}[truncated]`;
|
|
346
|
+
}
|