@love-moon/ai-sdk 0.13.3 → 0.15.0
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/CHANGELOG.md +82 -0
- package/dist/client.d.ts +5 -0
- package/dist/client.js +22 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/providers/chat-web-session.d.ts +18 -0
- package/dist/providers/chat-web-session.js +44 -1
- package/dist/providers/claude-agent-sdk-session.d.ts +34 -0
- package/dist/providers/claude-agent-sdk-session.js +33 -0
- package/dist/providers/codex-app-server-session.d.ts +76 -6
- package/dist/providers/codex-app-server-session.js +114 -14
- package/dist/providers/codex-exec-session.d.ts +14 -0
- package/dist/providers/codex-exec-session.js +20 -1
- package/dist/providers/copilot-sdk-session.d.ts +21 -0
- package/dist/providers/copilot-sdk-session.js +55 -12
- package/dist/providers/dsh-sdk-session.d.ts +53 -3
- package/dist/providers/dsh-sdk-session.js +24 -1
- package/dist/providers/kimi-cli-session.d.ts +22 -0
- package/dist/providers/kimi-cli-session.js +23 -1
- package/dist/providers/kimi-print-session.d.ts +13 -0
- package/dist/providers/kimi-print-session.js +29 -0
- package/dist/providers/kimi-slash-commands.d.ts +1 -0
- package/dist/providers/kimi-slash-commands.js +23 -0
- package/dist/providers/opencode-sdk-session.d.ts +52 -3
- package/dist/providers/opencode-sdk-session.js +33 -1
- package/dist/resume/claude.d.ts +8 -0
- package/dist/resume/claude.js +28 -1
- package/dist/resume/codex.d.ts +8 -0
- package/dist/resume/codex.js +37 -1
- package/dist/resume/index.d.ts +8 -0
- package/dist/resume/shared.d.ts +18 -0
- package/dist/resume/shared.js +65 -1
- package/dist/shared.d.ts +46 -0
- package/dist/shared.js +18 -1
- package/dist/transports/codex-app-server-transport.js +22 -1
- package/dist/transports/kimi-wire-transport.d.ts +12 -0
- package/dist/transports/kimi-wire-transport.js +40 -1
- package/dist/turn-usage.d.ts +31 -0
- package/dist/turn-usage.js +44 -0
- package/dist/worker.js +1 -1
- package/package.json +3 -3
|
@@ -6,6 +6,7 @@ import { EventEmitter } from "node:events";
|
|
|
6
6
|
import readline from "node:readline";
|
|
7
7
|
import { KIMI_CLI_PRINT_VARIANT as KIMI_PRINT_PROVIDER_VARIANT } from "../built-in-backends.js";
|
|
8
8
|
import { appendContextFilesToPrompt } from "../context-files.js";
|
|
9
|
+
import { assertKimiClearReply } from "./kimi-slash-commands.js";
|
|
9
10
|
import { PROVIDER_MEDIA_CAPABILITIES, UNSUPPORTED_MEDIA_CAPABILITIES, buildKimiContent, } from "../media-adapters.js";
|
|
10
11
|
import { assertMediaCapabilities, defaultPromptForMedia, resolveTurnMedia, } from "../media-input.js";
|
|
11
12
|
import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, noteToolFinished, noteToolStarted, parseCommandParts, proxyToEnv, sanitizeForLog, withActiveTool, } from "../shared.js";
|
|
@@ -272,6 +273,7 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
272
273
|
capabilities: {
|
|
273
274
|
// Kimi Code's `--prompt` CLI is unverified for slash commands.
|
|
274
275
|
compact: this.cliMode !== "prompt",
|
|
276
|
+
clear: this.cliMode !== "prompt",
|
|
275
277
|
media: this.cliMode === "prompt"
|
|
276
278
|
? UNSUPPORTED_MEDIA_CAPABILITIES
|
|
277
279
|
: PROVIDER_MEDIA_CAPABILITIES[KIMI_PRINT_PROVIDER_VARIANT],
|
|
@@ -505,6 +507,33 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
505
507
|
metadata: turnResult.metadata,
|
|
506
508
|
};
|
|
507
509
|
}
|
|
510
|
+
/**
|
|
511
|
+
* Native `/clear`: kimi-cli's built-in slash command, answered locally without
|
|
512
|
+
* a model call. Print mode spawns a child per turn, so nothing is torn down;
|
|
513
|
+
* the `--session` id stays the same and only its context file rotates.
|
|
514
|
+
*
|
|
515
|
+
* @param {Record<string, unknown>} [request]
|
|
516
|
+
* @param {{ onProgress?: Function }} [options]
|
|
517
|
+
* @returns {Promise<import("../shared.js").ClearResult>}
|
|
518
|
+
*/
|
|
519
|
+
async runClear(request = {}, { onProgress = null } = {}) {
|
|
520
|
+
if (this.cliMode === "prompt") {
|
|
521
|
+
throw createTurnError("Kimi prompt mode does not support /clear", { reason: "unsupported_clear" });
|
|
522
|
+
}
|
|
523
|
+
if (this.pendingHistorySeed) {
|
|
524
|
+
return { clear: { status: "noop" }, usage: null, metadata: {} };
|
|
525
|
+
}
|
|
526
|
+
const turnResult = await this.runTurn("/clear", { onProgress, suppressReply: true });
|
|
527
|
+
// Print mode exits 0 even when the model call fails, so only kimi-cli's
|
|
528
|
+
// fixed reply is trusted.
|
|
529
|
+
assertKimiClearReply(turnResult.text);
|
|
530
|
+
this.history = [];
|
|
531
|
+
return {
|
|
532
|
+
clear: { status: "cleared", sessionId: this.sessionId || undefined },
|
|
533
|
+
usage: turnResult.usage,
|
|
534
|
+
metadata: turnResult.metadata,
|
|
535
|
+
};
|
|
536
|
+
}
|
|
508
537
|
async runTurn(promptText, { useInitialImages = false, media: mediaInput, contextFiles, onProgress = null, jsonSchema = null, suppressReply = false } = {}) {
|
|
509
538
|
if (this.closeRequested || this.closed) {
|
|
510
539
|
throw this.createSessionClosedError();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function assertKimiClearReply(reply: any): void;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kimi-cli answers `/clear` itself, without calling the model, with a fixed
|
|
3
|
+
* line (see kimi_cli/soul/slash.py). Both kimi modes verify that line before
|
|
4
|
+
* reporting success: if a kimi version ever stops treating `/clear` as a
|
|
5
|
+
* built-in, the text reaches the model as an ordinary turn — which grows the
|
|
6
|
+
* context instead of clearing it — and we must fail loudly rather than tell the
|
|
7
|
+
* user their context is gone.
|
|
8
|
+
*
|
|
9
|
+
* Kept in one place so the two modes cannot drift apart, in either direction.
|
|
10
|
+
*/
|
|
11
|
+
const KIMI_CLEAR_ACK = /context has been cleared/i;
|
|
12
|
+
function createClearFailedError(message) {
|
|
13
|
+
const error = new Error(message);
|
|
14
|
+
error.reason = "clear_failed";
|
|
15
|
+
return error;
|
|
16
|
+
}
|
|
17
|
+
export function assertKimiClearReply(reply) {
|
|
18
|
+
const text = String(reply || "");
|
|
19
|
+
if (KIMI_CLEAR_ACK.test(text)) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
throw createClearFailedError(`Kimi clear failed${text.trim() ? `: ${text.trim()}` : ""}`);
|
|
23
|
+
}
|
|
@@ -80,6 +80,7 @@ export class OpencodeSdkSession extends EventEmitter<[never]> {
|
|
|
80
80
|
currentTurnStatus: any;
|
|
81
81
|
capabilities: {
|
|
82
82
|
compact: boolean;
|
|
83
|
+
clear: boolean;
|
|
83
84
|
media: Readonly<{
|
|
84
85
|
image: "native";
|
|
85
86
|
}>;
|
|
@@ -208,9 +209,57 @@ export class OpencodeSdkSession extends EventEmitter<[never]> {
|
|
|
208
209
|
* @param {{ onProgress?: Function }} [options]
|
|
209
210
|
* @returns {Promise<import("../shared.js").CompactResult>}
|
|
210
211
|
*/
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
212
|
+
/**
|
|
213
|
+
* Native clear: create a new session on the SAME opencode server (the server
|
|
214
|
+
* process and its event stream stay up); the old session is left untouched on
|
|
215
|
+
* disk, exactly like the boot path's `session.create`.
|
|
216
|
+
*/
|
|
217
|
+
runClear(): Promise<{
|
|
218
|
+
clear: {
|
|
219
|
+
status: string;
|
|
220
|
+
sessionId?: undefined;
|
|
221
|
+
};
|
|
222
|
+
usage: null;
|
|
223
|
+
metadata: {};
|
|
224
|
+
} | {
|
|
225
|
+
clear: {
|
|
226
|
+
status: string;
|
|
227
|
+
sessionId: any;
|
|
228
|
+
};
|
|
229
|
+
usage: null;
|
|
230
|
+
metadata: {};
|
|
231
|
+
}>;
|
|
232
|
+
runCompact(request?: {}, { onProgress }?: {
|
|
233
|
+
onProgress?: null | undefined;
|
|
234
|
+
}): Promise<{
|
|
235
|
+
compact: {
|
|
236
|
+
status: string;
|
|
237
|
+
instructionsApplied: boolean;
|
|
238
|
+
};
|
|
239
|
+
usage: null;
|
|
240
|
+
metadata: {};
|
|
241
|
+
} | {
|
|
242
|
+
compact: {
|
|
243
|
+
status: string;
|
|
244
|
+
instructionsApplied: boolean;
|
|
245
|
+
};
|
|
246
|
+
usage: {
|
|
247
|
+
total: number | undefined;
|
|
248
|
+
input: number | undefined;
|
|
249
|
+
output: number | undefined;
|
|
250
|
+
reasoning: number | undefined;
|
|
251
|
+
cache: {
|
|
252
|
+
read: number | undefined;
|
|
253
|
+
write: number | undefined;
|
|
254
|
+
};
|
|
255
|
+
} | null;
|
|
256
|
+
metadata: {
|
|
257
|
+
providerID: any;
|
|
258
|
+
modelID: any;
|
|
259
|
+
source: string;
|
|
260
|
+
sessionId: any;
|
|
261
|
+
};
|
|
262
|
+
}>;
|
|
214
263
|
runTurn(promptText: any, { useInitialImages, media: mediaInput, contextFiles, onProgress, jsonSchema }?: {
|
|
215
264
|
useInitialImages?: boolean | undefined;
|
|
216
265
|
onProgress?: null | undefined;
|
|
@@ -231,7 +231,7 @@ export class OpencodeSdkSession extends EventEmitter {
|
|
|
231
231
|
resumeReady: Boolean(this.sessionId),
|
|
232
232
|
manualResume: null,
|
|
233
233
|
currentTurnStatus: this.getCurrentTurnStatus(),
|
|
234
|
-
capabilities: { compact: true, media: PROVIDER_MEDIA_CAPABILITIES[OPENCODE_PROVIDER_VARIANT] },
|
|
234
|
+
capabilities: { compact: true, clear: true, media: PROVIDER_MEDIA_CAPABILITIES[OPENCODE_PROVIDER_VARIANT] },
|
|
235
235
|
pid: this.transport.pid || undefined,
|
|
236
236
|
};
|
|
237
237
|
}
|
|
@@ -1227,6 +1227,38 @@ export class OpencodeSdkSession extends EventEmitter {
|
|
|
1227
1227
|
* @param {{ onProgress?: Function }} [options]
|
|
1228
1228
|
* @returns {Promise<import("../shared.js").CompactResult>}
|
|
1229
1229
|
*/
|
|
1230
|
+
/**
|
|
1231
|
+
* Native clear: create a new session on the SAME opencode server (the server
|
|
1232
|
+
* process and its event stream stay up); the old session is left untouched on
|
|
1233
|
+
* disk, exactly like the boot path's `session.create`.
|
|
1234
|
+
*/
|
|
1235
|
+
async runClear() {
|
|
1236
|
+
if (this.closeRequested) {
|
|
1237
|
+
throw this.createSessionClosedError();
|
|
1238
|
+
}
|
|
1239
|
+
if (this.currentTurn) {
|
|
1240
|
+
throw createTurnError("Opencode turn already running", {
|
|
1241
|
+
reason: "turn_already_running",
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
if (this.pendingHistorySeed || (!this.resumeSessionId && !this.sessionId && this.history.length === 0)) {
|
|
1245
|
+
return { clear: { status: "noop" }, usage: null, metadata: {} };
|
|
1246
|
+
}
|
|
1247
|
+
await this.boot();
|
|
1248
|
+
if (typeof this.client?.session?.create !== "function") {
|
|
1249
|
+
throw new Error("Opencode session create API is unavailable");
|
|
1250
|
+
}
|
|
1251
|
+
const session = await this.requestOrThrow(this.client.session.create({}, { throwOnError: true, responseStyle: "data" }));
|
|
1252
|
+
this.applySessionInfo(session);
|
|
1253
|
+
// A later re-boot must not reattach to the session we walked away from.
|
|
1254
|
+
this.resumeSessionId = "";
|
|
1255
|
+
this.history = [];
|
|
1256
|
+
return {
|
|
1257
|
+
clear: { status: "cleared", sessionId: this.sessionId || undefined },
|
|
1258
|
+
usage: null,
|
|
1259
|
+
metadata: {},
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1230
1262
|
async runCompact(request = {}, { onProgress = null } = {}) {
|
|
1231
1263
|
if (this.closeRequested) {
|
|
1232
1264
|
throw this.createSessionClosedError();
|
package/dist/resume/claude.d.ts
CHANGED
|
@@ -24,5 +24,13 @@ export function listSessions(options?: {}): Promise<{
|
|
|
24
24
|
cwd: any;
|
|
25
25
|
title: string | null;
|
|
26
26
|
updatedAt: any;
|
|
27
|
+
preview: {
|
|
28
|
+
firstUserMessage: string | null;
|
|
29
|
+
firstReply: string | null;
|
|
30
|
+
lastMessage: {
|
|
31
|
+
role: any;
|
|
32
|
+
text: string | null;
|
|
33
|
+
} | null;
|
|
34
|
+
};
|
|
27
35
|
}[]>;
|
|
28
36
|
export const BACKEND: "claude";
|
package/dist/resume/claude.js
CHANGED
|
@@ -3,7 +3,7 @@ import { promises as fsp } from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import readline from "node:readline";
|
|
5
5
|
import { resolveClaudeConfigDirs } from "../manager/paths.js";
|
|
6
|
-
import { buildResumeContext, isExistingDirectory, normalizeSessionId, normalizeSessionTitle, pathExists, readJsonlHeadEntries, resolveSessionRunDirectory, } from "./shared.js";
|
|
6
|
+
import { buildResumeContext, buildSessionPreview, isExistingDirectory, normalizeSessionId, normalizeSessionTitle, pathExists, readJsonlHeadEntries, readJsonlTailEntries, resolveSessionRunDirectory, } from "./shared.js";
|
|
7
7
|
export const BACKEND = "claude";
|
|
8
8
|
export function buildCliArgs(sessionId) {
|
|
9
9
|
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
@@ -156,12 +156,14 @@ export async function listSessions(options = {}) {
|
|
|
156
156
|
const sessions = [];
|
|
157
157
|
for (const candidate of selected) {
|
|
158
158
|
const entries = await readJsonlHeadEntries(candidate.sessionFilePath);
|
|
159
|
+
const tailEntries = await readJsonlTailEntries(candidate.sessionFilePath);
|
|
159
160
|
sessions.push({
|
|
160
161
|
sessionId: candidate.sessionId,
|
|
161
162
|
sessionFilePath: candidate.sessionFilePath,
|
|
162
163
|
cwd: extractHeadCwd(entries),
|
|
163
164
|
title: extractClaudeTitle(entries),
|
|
164
165
|
updatedAt: candidate.updatedAt,
|
|
166
|
+
preview: buildSessionPreview(extractClaudeMessages(entries), extractClaudeMessages(tailEntries)),
|
|
165
167
|
});
|
|
166
168
|
}
|
|
167
169
|
return sessions;
|
|
@@ -174,6 +176,31 @@ function extractHeadCwd(entries) {
|
|
|
174
176
|
}
|
|
175
177
|
return null;
|
|
176
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* User/assistant text messages. Tool calls, tool results, meta entries and
|
|
181
|
+
* wrapped slash-command output (<command-name>, <local-command-stdout>) are skipped.
|
|
182
|
+
*/
|
|
183
|
+
function extractClaudeMessages(entries) {
|
|
184
|
+
const messages = [];
|
|
185
|
+
for (const entry of entries) {
|
|
186
|
+
if ((entry?.type !== "user" && entry?.type !== "assistant") || entry?.isMeta === true) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const content = entry?.message?.content;
|
|
190
|
+
const text = typeof content === "string"
|
|
191
|
+
? content
|
|
192
|
+
: Array.isArray(content)
|
|
193
|
+
? content
|
|
194
|
+
.filter((item) => item?.type === "text" && typeof item.text === "string")
|
|
195
|
+
.map((item) => item.text)
|
|
196
|
+
.join("\n")
|
|
197
|
+
: "";
|
|
198
|
+
if (text.trim() && !text.trim().startsWith("<")) {
|
|
199
|
+
messages.push({ role: entry.type, text });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return messages;
|
|
203
|
+
}
|
|
177
204
|
function extractClaudeTitle(entries) {
|
|
178
205
|
for (const entry of entries) {
|
|
179
206
|
if (entry?.type !== "user" || entry?.isMeta === true) {
|
package/dist/resume/codex.d.ts
CHANGED
|
@@ -24,5 +24,13 @@ export function listSessions(options?: {}): Promise<{
|
|
|
24
24
|
cwd: string | null;
|
|
25
25
|
title: string | null;
|
|
26
26
|
updatedAt: number;
|
|
27
|
+
preview: {
|
|
28
|
+
firstUserMessage: string | null;
|
|
29
|
+
firstReply: string | null;
|
|
30
|
+
lastMessage: {
|
|
31
|
+
role: any;
|
|
32
|
+
text: string | null;
|
|
33
|
+
} | null;
|
|
34
|
+
};
|
|
27
35
|
}[]>;
|
|
28
36
|
export const BACKEND: "codex";
|
package/dist/resume/codex.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { promises as fsp } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { buildResumeContext, isExistingDirectory, iterateJsonlEntries, normalizeSessionId, normalizeSessionTitle, readJsonlHeadEntries, resolveHomeDir, resolveSessionRunDirectory, } from "./shared.js";
|
|
3
|
+
import { buildResumeContext, buildSessionPreview, isExistingDirectory, iterateJsonlEntries, normalizeSessionId, normalizeSessionTitle, readJsonlHeadEntries, readJsonlTailEntries, resolveHomeDir, resolveSessionRunDirectory, } from "./shared.js";
|
|
4
4
|
export const BACKEND = "codex";
|
|
5
5
|
export function buildCliArgs(sessionId) {
|
|
6
6
|
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
@@ -111,12 +111,14 @@ export async function listSessions(options = {}) {
|
|
|
111
111
|
continue;
|
|
112
112
|
}
|
|
113
113
|
const metaCwd = meta?.payload?.cwd;
|
|
114
|
+
const tailEntries = await readJsonlTailEntries(candidate.filePath);
|
|
114
115
|
sessions.push({
|
|
115
116
|
sessionId,
|
|
116
117
|
sessionFilePath: candidate.filePath,
|
|
117
118
|
cwd: typeof metaCwd === "string" && metaCwd.trim() ? metaCwd.trim() : null,
|
|
118
119
|
title: extractCodexTitle(headEntries),
|
|
119
120
|
updatedAt: candidate.updatedAt,
|
|
121
|
+
preview: buildSessionPreview(extractCodexMessages(headEntries), extractCodexMessages(tailEntries)),
|
|
120
122
|
});
|
|
121
123
|
}
|
|
122
124
|
return sessions;
|
|
@@ -126,6 +128,40 @@ function extractSessionIdFromFileName(filePath) {
|
|
|
126
128
|
const match = path.basename(filePath).match(SESSION_ID_IN_FILE_NAME);
|
|
127
129
|
return match ? match[0] : "";
|
|
128
130
|
}
|
|
131
|
+
// The event stream's user/agent messages: what the user typed and the
|
|
132
|
+
// assistant's replies, without injected context or tool output.
|
|
133
|
+
const CODEX_MESSAGE_ROLES = { user_message: "user", agent_message: "assistant" };
|
|
134
|
+
const CODEX_ITEM_TEXT_TYPES = { user: "input_text", assistant: "output_text" };
|
|
135
|
+
function extractCodexMessages(entries) {
|
|
136
|
+
const messages = [];
|
|
137
|
+
for (const entry of entries) {
|
|
138
|
+
const role = entry?.type === "event_msg" ? CODEX_MESSAGE_ROLES[entry?.payload?.type] : null;
|
|
139
|
+
const text = entry?.payload?.message;
|
|
140
|
+
if (role && typeof text === "string" && text.trim()) {
|
|
141
|
+
messages.push({ role, text });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (messages.length > 0) {
|
|
145
|
+
return messages;
|
|
146
|
+
}
|
|
147
|
+
// Sessions without those events only carry response items; skip injected
|
|
148
|
+
// wrappers such as <environment_context>, as the title does.
|
|
149
|
+
for (const entry of entries) {
|
|
150
|
+
const payload = entry?.type === "response_item" ? entry.payload : null;
|
|
151
|
+
const textType = payload?.type === "message" ? CODEX_ITEM_TEXT_TYPES[payload.role] : null;
|
|
152
|
+
if (!textType || !Array.isArray(payload.content)) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const text = payload.content
|
|
156
|
+
.filter((item) => item?.type === textType && typeof item.text === "string")
|
|
157
|
+
.map((item) => item.text)
|
|
158
|
+
.join("\n");
|
|
159
|
+
if (text.trim() && !text.trim().startsWith("<")) {
|
|
160
|
+
messages.push({ role: payload.role, text });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return messages;
|
|
164
|
+
}
|
|
129
165
|
function extractCodexTitle(entries) {
|
|
130
166
|
for (const entry of entries) {
|
|
131
167
|
if (entry?.type === "event_msg" && entry?.payload?.type === "user_message") {
|
package/dist/resume/index.d.ts
CHANGED
|
@@ -11,6 +11,14 @@ export function listSessionsForBackend(backendType: any, opts?: {}): Promise<{
|
|
|
11
11
|
cwd: string | null;
|
|
12
12
|
title: string | null;
|
|
13
13
|
updatedAt: number;
|
|
14
|
+
preview: {
|
|
15
|
+
firstUserMessage: string | null;
|
|
16
|
+
firstReply: string | null;
|
|
17
|
+
lastMessage: {
|
|
18
|
+
role: any;
|
|
19
|
+
text: string | null;
|
|
20
|
+
} | null;
|
|
21
|
+
};
|
|
14
22
|
}[]>;
|
|
15
23
|
export function resolveResumeContext(backend: any, sessionId: any, options?: {}): Promise<{
|
|
16
24
|
provider: any;
|
package/dist/resume/shared.d.ts
CHANGED
|
@@ -11,6 +11,24 @@ export function normalizeSessionTitle(text: any, maxLength?: number): string | n
|
|
|
11
11
|
* are skipped.
|
|
12
12
|
*/
|
|
13
13
|
export function readJsonlHeadEntries(filePath: any, maxLines?: number, maxBytes?: number): Promise<any[]>;
|
|
14
|
+
/**
|
|
15
|
+
* Parses the tail of a jsonl file (last `maxBytes`); the first, possibly
|
|
16
|
+
* partial, line is dropped unless the read started at the file's beginning.
|
|
17
|
+
*/
|
|
18
|
+
export function readJsonlTailEntries(filePath: any, maxBytes?: number): Promise<any[]>;
|
|
19
|
+
/**
|
|
20
|
+
* Session preview for the resume picker, from `{ role, text }` messages read
|
|
21
|
+
* off the head and tail of a session file: the first user message, the
|
|
22
|
+
* assistant text that answered it, and the session's last message.
|
|
23
|
+
*/
|
|
24
|
+
export function buildSessionPreview(headMessages: any, tailMessages: any): {
|
|
25
|
+
firstUserMessage: string | null;
|
|
26
|
+
firstReply: string | null;
|
|
27
|
+
lastMessage: {
|
|
28
|
+
role: any;
|
|
29
|
+
text: string | null;
|
|
30
|
+
} | null;
|
|
31
|
+
};
|
|
14
32
|
export function isExistingDirectory(targetPath: any): Promise<boolean>;
|
|
15
33
|
export function resolveSessionRunDirectory(sessionPath: any): Promise<string>;
|
|
16
34
|
export function listCandidateWorkingDirectories(options?: {}): any[];
|
package/dist/resume/shared.js
CHANGED
|
@@ -46,7 +46,7 @@ export function normalizeSessionTitle(text, maxLength = 80) {
|
|
|
46
46
|
* session files stay cheap to inspect). Unreadable files and unparsable lines
|
|
47
47
|
* are skipped.
|
|
48
48
|
*/
|
|
49
|
-
export async function readJsonlHeadEntries(filePath, maxLines =
|
|
49
|
+
export async function readJsonlHeadEntries(filePath, maxLines = 200, maxBytes = 128 * 1024) {
|
|
50
50
|
const entries = [];
|
|
51
51
|
const input = fs.createReadStream(filePath, { end: maxBytes - 1 });
|
|
52
52
|
const rl = readline.createInterface({ input, crlfDelay: Infinity });
|
|
@@ -78,6 +78,70 @@ export async function readJsonlHeadEntries(filePath, maxLines = 50, maxBytes = 1
|
|
|
78
78
|
}
|
|
79
79
|
return entries;
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Parses the tail of a jsonl file (last `maxBytes`); the first, possibly
|
|
83
|
+
* partial, line is dropped unless the read started at the file's beginning.
|
|
84
|
+
*/
|
|
85
|
+
export async function readJsonlTailEntries(filePath, maxBytes = 64 * 1024) {
|
|
86
|
+
let handle;
|
|
87
|
+
try {
|
|
88
|
+
handle = await fsp.open(filePath, "r");
|
|
89
|
+
const { size } = await handle.stat();
|
|
90
|
+
const start = Math.max(0, size - maxBytes);
|
|
91
|
+
const buffer = Buffer.alloc(size - start);
|
|
92
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, start);
|
|
93
|
+
const lines = buffer.subarray(0, bytesRead).toString("utf8").split("\n");
|
|
94
|
+
if (start > 0) {
|
|
95
|
+
lines.shift();
|
|
96
|
+
}
|
|
97
|
+
const entries = [];
|
|
98
|
+
for (const line of lines) {
|
|
99
|
+
try {
|
|
100
|
+
if (line.trim())
|
|
101
|
+
entries.push(JSON.parse(line));
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return entries;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
await handle?.close();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function clipPreviewText(text, maxLength = 2000) {
|
|
117
|
+
const trimmed = String(text || "").trim();
|
|
118
|
+
if (!trimmed) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
return trimmed.length > maxLength ? `${trimmed.slice(0, maxLength)}…` : trimmed;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Session preview for the resume picker, from `{ role, text }` messages read
|
|
125
|
+
* off the head and tail of a session file: the first user message, the
|
|
126
|
+
* assistant text that answered it, and the session's last message.
|
|
127
|
+
*/
|
|
128
|
+
export function buildSessionPreview(headMessages, tailMessages) {
|
|
129
|
+
const firstUserIndex = headMessages.findIndex((message) => message.role === "user");
|
|
130
|
+
const replies = [];
|
|
131
|
+
if (firstUserIndex >= 0) {
|
|
132
|
+
for (const message of headMessages.slice(firstUserIndex + 1)) {
|
|
133
|
+
if (message.role === "user")
|
|
134
|
+
break;
|
|
135
|
+
replies.push(message.text);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const last = tailMessages[tailMessages.length - 1] || headMessages[headMessages.length - 1] || null;
|
|
139
|
+
return {
|
|
140
|
+
firstUserMessage: firstUserIndex >= 0 ? clipPreviewText(headMessages[firstUserIndex].text) : null,
|
|
141
|
+
firstReply: clipPreviewText(replies.join("\n\n")),
|
|
142
|
+
lastMessage: last ? { role: last.role, text: clipPreviewText(last.text) } : null,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
81
145
|
export async function isExistingDirectory(targetPath) {
|
|
82
146
|
const normalizedPath = typeof targetPath === "string" ? targetPath.trim() : "";
|
|
83
147
|
if (!normalizedPath) {
|
package/dist/shared.d.ts
CHANGED
|
@@ -100,6 +100,7 @@ export const TERMINAL_GOAL_STATUSES: ReadonlyArray<GoalStatus>;
|
|
|
100
100
|
* @typedef {Object} SessionCapabilities
|
|
101
101
|
* @property {boolean} [goal]
|
|
102
102
|
* @property {boolean} [compact]
|
|
103
|
+
* @property {boolean} [clear]
|
|
103
104
|
*/
|
|
104
105
|
/**
|
|
105
106
|
* Manual context compaction (`/compact`) contract for providers that
|
|
@@ -124,6 +125,22 @@ export const TERMINAL_GOAL_STATUSES: ReadonlyArray<GoalStatus>;
|
|
|
124
125
|
* @property {unknown} [usage]
|
|
125
126
|
* @property {Record<string, unknown>} [metadata]
|
|
126
127
|
*/
|
|
128
|
+
/**
|
|
129
|
+
* Native context clear (`/clear`) contract for providers that implement the
|
|
130
|
+
* optional `runClear` method and advertise `capabilities.clear === true`.
|
|
131
|
+
* Providers must NOT emit assistant messages while clearing; the caller
|
|
132
|
+
* renders the user-facing confirmation.
|
|
133
|
+
*
|
|
134
|
+
* @typedef {Object} ClearState
|
|
135
|
+
* @property {"cleared"|"noop"} status `noop` = there was nothing to clear.
|
|
136
|
+
* @property {string} [sessionId] New session id when the backend starts a new
|
|
137
|
+
* conversation; callers rebind the task to it.
|
|
138
|
+
*
|
|
139
|
+
* @typedef {Object} ClearResult
|
|
140
|
+
* @property {ClearState} clear
|
|
141
|
+
* @property {unknown} [usage]
|
|
142
|
+
* @property {Record<string, unknown>} [metadata]
|
|
143
|
+
*/
|
|
127
144
|
/**
|
|
128
145
|
* Default capabilities assigned to providers that do not opt into any optional
|
|
129
146
|
* feature. Treat the returned object as read-only; clone before mutating.
|
|
@@ -212,6 +229,7 @@ export type GoalResult = {
|
|
|
212
229
|
export type SessionCapabilities = {
|
|
213
230
|
goal?: boolean | undefined;
|
|
214
231
|
compact?: boolean | undefined;
|
|
232
|
+
clear?: boolean | undefined;
|
|
215
233
|
};
|
|
216
234
|
/**
|
|
217
235
|
* Manual context compaction (`/compact`) contract for providers that
|
|
@@ -254,3 +272,31 @@ export type CompactResult = {
|
|
|
254
272
|
usage?: unknown;
|
|
255
273
|
metadata?: Record<string, unknown> | undefined;
|
|
256
274
|
};
|
|
275
|
+
/**
|
|
276
|
+
* Native context clear (`/clear`) contract for providers that implement the
|
|
277
|
+
* optional `runClear` method and advertise `capabilities.clear === true`.
|
|
278
|
+
* Providers must NOT emit assistant messages while clearing; the caller
|
|
279
|
+
* renders the user-facing confirmation.
|
|
280
|
+
*/
|
|
281
|
+
export type ClearState = {
|
|
282
|
+
/**
|
|
283
|
+
* `noop` = there was nothing to clear.
|
|
284
|
+
*/
|
|
285
|
+
status: "cleared" | "noop";
|
|
286
|
+
/**
|
|
287
|
+
* New session id when the backend starts a new
|
|
288
|
+
* conversation; callers rebind the task to it.
|
|
289
|
+
*/
|
|
290
|
+
sessionId?: string | undefined;
|
|
291
|
+
};
|
|
292
|
+
/**
|
|
293
|
+
* Native context clear (`/clear`) contract for providers that implement the
|
|
294
|
+
* optional `runClear` method and advertise `capabilities.clear === true`.
|
|
295
|
+
* Providers must NOT emit assistant messages while clearing; the caller
|
|
296
|
+
* renders the user-facing confirmation.
|
|
297
|
+
*/
|
|
298
|
+
export type ClearResult = {
|
|
299
|
+
clear: ClearState;
|
|
300
|
+
usage?: unknown;
|
|
301
|
+
metadata?: Record<string, unknown> | undefined;
|
|
302
|
+
};
|
package/dist/shared.js
CHANGED
|
@@ -81,6 +81,7 @@ export function isTerminalGoalStatus(value) {
|
|
|
81
81
|
* @typedef {Object} SessionCapabilities
|
|
82
82
|
* @property {boolean} [goal]
|
|
83
83
|
* @property {boolean} [compact]
|
|
84
|
+
* @property {boolean} [clear]
|
|
84
85
|
*/
|
|
85
86
|
/**
|
|
86
87
|
* Manual context compaction (`/compact`) contract for providers that
|
|
@@ -105,13 +106,29 @@ export function isTerminalGoalStatus(value) {
|
|
|
105
106
|
* @property {unknown} [usage]
|
|
106
107
|
* @property {Record<string, unknown>} [metadata]
|
|
107
108
|
*/
|
|
109
|
+
/**
|
|
110
|
+
* Native context clear (`/clear`) contract for providers that implement the
|
|
111
|
+
* optional `runClear` method and advertise `capabilities.clear === true`.
|
|
112
|
+
* Providers must NOT emit assistant messages while clearing; the caller
|
|
113
|
+
* renders the user-facing confirmation.
|
|
114
|
+
*
|
|
115
|
+
* @typedef {Object} ClearState
|
|
116
|
+
* @property {"cleared"|"noop"} status `noop` = there was nothing to clear.
|
|
117
|
+
* @property {string} [sessionId] New session id when the backend starts a new
|
|
118
|
+
* conversation; callers rebind the task to it.
|
|
119
|
+
*
|
|
120
|
+
* @typedef {Object} ClearResult
|
|
121
|
+
* @property {ClearState} clear
|
|
122
|
+
* @property {unknown} [usage]
|
|
123
|
+
* @property {Record<string, unknown>} [metadata]
|
|
124
|
+
*/
|
|
108
125
|
/**
|
|
109
126
|
* Default capabilities assigned to providers that do not opt into any optional
|
|
110
127
|
* feature. Treat the returned object as read-only; clone before mutating.
|
|
111
128
|
*
|
|
112
129
|
* @type {Readonly<SessionCapabilities>}
|
|
113
130
|
*/
|
|
114
|
-
export const DEFAULT_SESSION_CAPABILITIES = Object.freeze({ goal: false, compact: false });
|
|
131
|
+
export const DEFAULT_SESSION_CAPABILITIES = Object.freeze({ goal: false, compact: false, clear: false });
|
|
115
132
|
/**
|
|
116
133
|
* Resolve a session's capability snapshot, falling back to
|
|
117
134
|
* {@link DEFAULT_SESSION_CAPABILITIES}.
|
|
@@ -46,6 +46,27 @@ function injectEnableGoalsArgs(args, enableGoals) {
|
|
|
46
46
|
normalized.splice(insertAt, 0, "--enable", "goals");
|
|
47
47
|
return normalized;
|
|
48
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Add one `-c key=value` per config override (e.g. the per-task MCP server a
|
|
51
|
+
* remote-worktree task needs), before `--listen` like `--enable goals`.
|
|
52
|
+
*
|
|
53
|
+
* @param {string[]} args
|
|
54
|
+
* @param {unknown} overrides
|
|
55
|
+
* @returns {string[]}
|
|
56
|
+
*/
|
|
57
|
+
function injectConfigOverrideArgs(args, overrides) {
|
|
58
|
+
const pairs = Array.isArray(overrides)
|
|
59
|
+
? overrides.filter((entry) => typeof entry === "string" && entry.includes("="))
|
|
60
|
+
: [];
|
|
61
|
+
if (pairs.length === 0) {
|
|
62
|
+
return args;
|
|
63
|
+
}
|
|
64
|
+
const normalized = [...args];
|
|
65
|
+
const listenIndex = normalized.indexOf("--listen");
|
|
66
|
+
const insertAt = listenIndex >= 0 ? listenIndex : normalized.length;
|
|
67
|
+
normalized.splice(insertAt, 0, ...pairs.flatMap((pair) => ["-c", pair]));
|
|
68
|
+
return normalized;
|
|
69
|
+
}
|
|
49
70
|
function createRpcError(payload) {
|
|
50
71
|
const message = String(payload?.message || payload?.error?.message || "Codex app-server request failed");
|
|
51
72
|
const error = new Error(message);
|
|
@@ -78,7 +99,7 @@ export class CodexAppServerTransport extends EventEmitter {
|
|
|
78
99
|
}
|
|
79
100
|
this.command = command;
|
|
80
101
|
this.enableGoals = options.enableGoals === true;
|
|
81
|
-
this.args = injectEnableGoalsArgs(args, this.enableGoals);
|
|
102
|
+
this.args = injectConfigOverrideArgs(injectEnableGoalsArgs(args, this.enableGoals), options.configOverrides);
|
|
82
103
|
this.env = options.env && typeof options.env === "object" ? { ...options.env } : {};
|
|
83
104
|
this.ignoreCodexApiKey = options.ignoreCodexApiKey === true;
|
|
84
105
|
this.child = null;
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drop resume flags from the configured command line. The session id is owned
|
|
3
|
+
* by the caller (we always append our own `--session=`), so a configured
|
|
4
|
+
* `--continue`/`--session`/`--resume` would silently reattach a fresh session —
|
|
5
|
+
* e.g. a `/clear` — to the previous conversation.
|
|
6
|
+
*
|
|
7
|
+
* Only those flags are touched. Notably `-c` is kimi's alias for
|
|
8
|
+
* `--prompt`/`--command` (it takes a value), not a continue switch, so it is
|
|
9
|
+
* passed through untouched — dropping the flag alone would leave its value
|
|
10
|
+
* behind as a stray positional argument.
|
|
11
|
+
*/
|
|
12
|
+
export function filterKimiWireBaseArgs(args: any): string[];
|
|
1
13
|
export class KimiWireTransport extends EventEmitter<[never]> {
|
|
2
14
|
constructor(options?: {});
|
|
3
15
|
options: {};
|