@love-moon/ai-sdk 0.13.0 → 0.13.2
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 +35 -0
- package/dist/client.d.ts +10 -0
- package/dist/client.js +38 -1
- package/dist/providers/claude-agent-sdk-session.d.ts +25 -1
- package/dist/providers/claude-agent-sdk-session.js +114 -11
- package/dist/providers/codex-app-server-session.d.ts +44 -0
- package/dist/providers/codex-app-server-session.js +182 -12
- package/dist/providers/codex-exec-session.js +9 -2
- package/dist/providers/copilot-sdk-session.d.ts +29 -1
- package/dist/providers/copilot-sdk-session.js +115 -18
- package/dist/providers/dsh-sdk-session.d.ts +20 -1
- package/dist/providers/dsh-sdk-session.js +81 -10
- package/dist/providers/kimi-cli-session.d.ts +18 -1
- package/dist/providers/kimi-cli-session.js +40 -7
- package/dist/providers/kimi-print-session.d.ts +16 -1
- package/dist/providers/kimi-print-session.js +51 -6
- package/dist/providers/opencode-sdk-session.d.ts +44 -2
- package/dist/providers/opencode-sdk-session.js +177 -26
- package/dist/shared.d.ts +78 -0
- package/dist/shared.js +95 -1
- package/dist/worker.js +1 -1
- package/package.json +3 -3
|
@@ -6,11 +6,17 @@ import os from "node:os";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { DSH_SDK_VARIANT } from "../built-in-backends.js";
|
|
9
|
-
import { emitLog, extractLongFlagFromCommandLine, getBoundedEnvInt, loadEnvConfig, normalizeLogger, proxyToEnv, sanitizeForLog, } from "../shared.js";
|
|
9
|
+
import { emitLog, extractLongFlagFromCommandLine, getBoundedEnvInt, loadEnvConfig, normalizeLogger, noteToolFinished, noteToolStarted, proxyToEnv, sanitizeForLog, withActiveTool, } from "../shared.js";
|
|
10
10
|
const require = createRequire(import.meta.url);
|
|
11
11
|
const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
|
|
12
12
|
const MIN_TURN_DEADLINE_MS = 30 * 1000;
|
|
13
13
|
const MAX_TURN_DEADLINE_MS = 30 * 60 * 1000;
|
|
14
|
+
export const COMPACT_SUMMARY_PROMPT = [
|
|
15
|
+
"Summarize our conversation so far so it can replace the full history.",
|
|
16
|
+
"Keep the goals, decisions, current state of the work, relevant files and commands, and open next steps.",
|
|
17
|
+
"Do not use tools. Reply with the summary only.",
|
|
18
|
+
].join(" ");
|
|
19
|
+
const COMPACT_SUMMARY_PREFIX = "Summary of the conversation so far:\n\n";
|
|
14
20
|
// Pinned defaults for the dsh runtime route. `deepseek-official` is the
|
|
15
21
|
// provider route the stock `@deepseek-ai/dsh-llm-deepseek` adapter registers;
|
|
16
22
|
// the model can be overridden via options.model or an allow_cli_list command
|
|
@@ -126,7 +132,7 @@ function accumulateUsage(total, usage) {
|
|
|
126
132
|
* the next runTurn spawns a fresh runtime resuming the same session id.
|
|
127
133
|
*/
|
|
128
134
|
export class DshSdkSession extends EventEmitter {
|
|
129
|
-
static capabilities = Object.freeze({ goal: false });
|
|
135
|
+
static capabilities = Object.freeze({ goal: false, compact: true });
|
|
130
136
|
getCapabilities() {
|
|
131
137
|
return { ...DshSdkSession.capabilities };
|
|
132
138
|
}
|
|
@@ -220,7 +226,7 @@ export class DshSdkSession extends EventEmitter {
|
|
|
220
226
|
return this.sessionInfo ? { ...this.sessionInfo } : null;
|
|
221
227
|
}
|
|
222
228
|
getCurrentTurnStatus() {
|
|
223
|
-
return this.currentTurnStatus
|
|
229
|
+
return withActiveTool(this.currentTurnStatus, this.currentTurn);
|
|
224
230
|
}
|
|
225
231
|
async ensureSessionInfo() {
|
|
226
232
|
return this.getSessionInfo();
|
|
@@ -376,6 +382,7 @@ export class DshSdkSession extends EventEmitter {
|
|
|
376
382
|
return [];
|
|
377
383
|
}
|
|
378
384
|
const restored = [];
|
|
385
|
+
let compactSummaryPending = false;
|
|
379
386
|
let content;
|
|
380
387
|
try {
|
|
381
388
|
content = fs.readFileSync(logPath, "utf8");
|
|
@@ -397,14 +404,26 @@ export class DshSdkSession extends EventEmitter {
|
|
|
397
404
|
}
|
|
398
405
|
if (event?.type === "user/message" && event.data?.source?.kind === "user") {
|
|
399
406
|
const text = extractTextBlocks(event.data?.content);
|
|
400
|
-
|
|
407
|
+
// A `/compact` summary turn supersedes everything before it, but only
|
|
408
|
+
// once its reply lands; a failed or interrupted one changes nothing.
|
|
409
|
+
compactSummaryPending = text.includes(COMPACT_SUMMARY_PROMPT);
|
|
410
|
+
if (text && !compactSummaryPending) {
|
|
401
411
|
restored.push({ role: "user", content: text });
|
|
402
412
|
}
|
|
403
413
|
continue;
|
|
404
414
|
}
|
|
415
|
+
if (event?.type === "turn/end" && event.data?.reason?.kind !== "completed") {
|
|
416
|
+
compactSummaryPending = false;
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
405
419
|
if (event?.type === "assistant/message") {
|
|
406
420
|
const text = extractTextBlocks(event.data?.message?.content);
|
|
407
|
-
if (text) {
|
|
421
|
+
if (text && compactSummaryPending) {
|
|
422
|
+
restored.length = 0;
|
|
423
|
+
restored.push({ role: "assistant", content: `${COMPACT_SUMMARY_PREFIX}${text}` });
|
|
424
|
+
compactSummaryPending = false;
|
|
425
|
+
}
|
|
426
|
+
else if (text) {
|
|
408
427
|
restored.push({ role: "assistant", content: text });
|
|
409
428
|
}
|
|
410
429
|
}
|
|
@@ -701,8 +720,11 @@ export class DshSdkSession extends EventEmitter {
|
|
|
701
720
|
if (!text) {
|
|
702
721
|
return;
|
|
703
722
|
}
|
|
704
|
-
currentTurn.emittedAssistantMessage = true;
|
|
705
723
|
currentTurn.fullText = text;
|
|
724
|
+
if (currentTurn.suppressReply) {
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
currentTurn.emittedAssistantMessage = true;
|
|
706
728
|
await this.emitWorkingStatus({
|
|
707
729
|
phase: "message_aggregation",
|
|
708
730
|
reply_in_progress: true,
|
|
@@ -714,6 +736,7 @@ export class DshSdkSession extends EventEmitter {
|
|
|
714
736
|
}
|
|
715
737
|
case "tool/call": {
|
|
716
738
|
const toolName = String(event.data?.name || "");
|
|
739
|
+
noteToolStarted(currentTurn, event.data?.callId, toolName, event.data?.arguments);
|
|
717
740
|
const phase = toolPhaseForName(toolName);
|
|
718
741
|
const argsPreview = sanitizeForLog(event.data?.arguments, 120);
|
|
719
742
|
await this.emitWorkingStatus({
|
|
@@ -723,6 +746,9 @@ export class DshSdkSession extends EventEmitter {
|
|
|
723
746
|
}, onProgress);
|
|
724
747
|
return;
|
|
725
748
|
}
|
|
749
|
+
case "tool/result":
|
|
750
|
+
noteToolFinished(currentTurn, event.data?.message?.content?.[0]?.toolCallId);
|
|
751
|
+
return;
|
|
726
752
|
case "todo/write": {
|
|
727
753
|
const todos = Array.isArray(event.data?.todos) ? event.data.todos : [];
|
|
728
754
|
const done = todos.filter((item) => item?.status === "completed").length;
|
|
@@ -741,6 +767,15 @@ export class DshSdkSession extends EventEmitter {
|
|
|
741
767
|
}, onProgress);
|
|
742
768
|
return;
|
|
743
769
|
}
|
|
770
|
+
case "compaction/end": {
|
|
771
|
+
// Automatic compaction finished mid-turn; drop the "compacting" line.
|
|
772
|
+
await this.emitWorkingStatus({
|
|
773
|
+
phase: "turn_started",
|
|
774
|
+
reply_in_progress: true,
|
|
775
|
+
status_line: "dsh is working",
|
|
776
|
+
}, onProgress);
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
744
779
|
case "turn/end": {
|
|
745
780
|
currentTurn.turnEndReason = event.data?.reason || null;
|
|
746
781
|
return;
|
|
@@ -762,7 +797,40 @@ export class DshSdkSession extends EventEmitter {
|
|
|
762
797
|
await this.disposeHarness();
|
|
763
798
|
return true;
|
|
764
799
|
}
|
|
765
|
-
|
|
800
|
+
/**
|
|
801
|
+
* Manually compact the conversation. The dsh SDK wire exposes no compaction
|
|
802
|
+
* request (its engine's `compactNow` is internal), so the model summarizes
|
|
803
|
+
* the conversation in one silent turn and the chat continues on a fresh
|
|
804
|
+
* wire session seeded with only that summary.
|
|
805
|
+
*
|
|
806
|
+
* @param {import("../shared.js").CompactRequest} [request]
|
|
807
|
+
* @param {{ onProgress?: Function }} [options]
|
|
808
|
+
* @returns {Promise<import("../shared.js").CompactResult>}
|
|
809
|
+
*/
|
|
810
|
+
async runCompact(request = {}, { onProgress = null } = {}) {
|
|
811
|
+
const instructions = typeof request?.instructions === "string" ? request.instructions.trim() : "";
|
|
812
|
+
const resumableLog = this.pendingResumeFromSessionId && this.findPersistedSessionLog(this.pendingResumeFromSessionId);
|
|
813
|
+
if (this.history.length === 0 && !resumableLog) {
|
|
814
|
+
return { compact: { status: "noop", instructionsApplied: false }, usage: null, metadata: {} };
|
|
815
|
+
}
|
|
816
|
+
const prompt = instructions ? `${COMPACT_SUMMARY_PROMPT}\n\nAdditional focus: ${instructions}` : COMPACT_SUMMARY_PROMPT;
|
|
817
|
+
const turnResult = await this.runTurn(prompt, { onProgress, suppressReply: true });
|
|
818
|
+
const summary = String(turnResult.text || "").trim();
|
|
819
|
+
if (!summary) {
|
|
820
|
+
throw createTurnError("dsh compaction produced an empty summary", { reason: "compact_failed" });
|
|
821
|
+
}
|
|
822
|
+
this.history = [{ role: "assistant", content: `${COMPACT_SUMMARY_PREFIX}${summary}` }];
|
|
823
|
+
// Dropping the runtime makes the next turn rotate onto a fresh id seeded
|
|
824
|
+
// with the summary. Rotating here instead would expose the new id for
|
|
825
|
+
// binding before it has a persisted log, so a restart would resume empty.
|
|
826
|
+
await this.disposeHarness();
|
|
827
|
+
return {
|
|
828
|
+
compact: { status: "compacted", instructionsApplied: Boolean(instructions) },
|
|
829
|
+
usage: turnResult.usage,
|
|
830
|
+
metadata: { source: DSH_SDK_VARIANT, sessionId: this.sessionId },
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
async runTurn(promptText, { useInitialImages = false, onProgress = null, suppressReply = false } = {}) {
|
|
766
834
|
if (this.closeRequested) {
|
|
767
835
|
throw this.createSessionClosedError();
|
|
768
836
|
}
|
|
@@ -794,8 +862,11 @@ export class DshSdkSession extends EventEmitter {
|
|
|
794
862
|
this.rotateSessionId();
|
|
795
863
|
}
|
|
796
864
|
const effectivePrompt = this.buildPrompt(promptText, { useInitialImages });
|
|
797
|
-
|
|
865
|
+
if (!suppressReply) {
|
|
866
|
+
this.history.push({ role: "user", content: promptText });
|
|
867
|
+
}
|
|
798
868
|
const currentTurn = {
|
|
869
|
+
suppressReply,
|
|
799
870
|
emittedAssistantMessage: false,
|
|
800
871
|
fullText: "",
|
|
801
872
|
interrupted: false,
|
|
@@ -857,10 +928,10 @@ export class DshSdkSession extends EventEmitter {
|
|
|
857
928
|
const responseText = typeof runResult?.finalResponse === "string" && runResult.finalResponse
|
|
858
929
|
? runResult.finalResponse
|
|
859
930
|
: currentTurn.fullText;
|
|
860
|
-
if (!currentTurn.emittedAssistantMessage && responseText) {
|
|
931
|
+
if (!suppressReply && !currentTurn.emittedAssistantMessage && responseText) {
|
|
861
932
|
await this.emitAssistantMessage(responseText);
|
|
862
933
|
}
|
|
863
|
-
if (responseText) {
|
|
934
|
+
if (!suppressReply && responseText) {
|
|
864
935
|
this.history.push({ role: "assistant", content: responseText });
|
|
865
936
|
}
|
|
866
937
|
const turnEndKind = currentTurn.turnEndReason?.kind;
|
|
@@ -20,6 +20,8 @@ export class KimiCliSession extends EventEmitter<[never]> {
|
|
|
20
20
|
activeReplyTarget: string;
|
|
21
21
|
lastReplyTarget: string;
|
|
22
22
|
currentTurn: {
|
|
23
|
+
suppressReply: boolean;
|
|
24
|
+
compacted: boolean;
|
|
23
25
|
fullText: string;
|
|
24
26
|
bufferedAssistantText: string;
|
|
25
27
|
thinkText: string;
|
|
@@ -84,6 +86,7 @@ export class KimiCliSession extends EventEmitter<[never]> {
|
|
|
84
86
|
} | null;
|
|
85
87
|
currentTurnStatus: any;
|
|
86
88
|
capabilities: {
|
|
89
|
+
compact: boolean;
|
|
87
90
|
media: Readonly<{
|
|
88
91
|
image: "native";
|
|
89
92
|
}>;
|
|
@@ -154,10 +157,23 @@ export class KimiCliSession extends EventEmitter<[never]> {
|
|
|
154
157
|
handleTransportFailure(error: any): void;
|
|
155
158
|
handleTransportExit(payload: any): void;
|
|
156
159
|
interruptCurrentTurn(): Promise<boolean>;
|
|
157
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Manually compact via Kimi CLI's built-in `/compact` slash command, which
|
|
162
|
+
* the wire `prompt` method dispatches like the interactive shell does. The
|
|
163
|
+
* command takes no focus instructions.
|
|
164
|
+
*
|
|
165
|
+
* @param {import("../shared.js").CompactRequest} [request]
|
|
166
|
+
* @param {{ onProgress?: Function }} [options]
|
|
167
|
+
* @returns {Promise<import("../shared.js").CompactResult>}
|
|
168
|
+
*/
|
|
169
|
+
runCompact(request?: import("../shared.js").CompactRequest, { onProgress }?: {
|
|
170
|
+
onProgress?: Function;
|
|
171
|
+
}): Promise<import("../shared.js").CompactResult>;
|
|
172
|
+
runTurn(promptText: any, { useInitialImages, media: mediaInput, contextFiles, onProgress, jsonSchema, suppressReply }?: {
|
|
158
173
|
useInitialImages?: boolean | undefined;
|
|
159
174
|
onProgress?: null | undefined;
|
|
160
175
|
jsonSchema?: null | undefined;
|
|
176
|
+
suppressReply?: boolean | undefined;
|
|
161
177
|
}): Promise<{
|
|
162
178
|
text: string;
|
|
163
179
|
usage: null;
|
|
@@ -175,6 +191,7 @@ export class KimiCliSession extends EventEmitter<[never]> {
|
|
|
175
191
|
promptStatus: string | undefined;
|
|
176
192
|
contextUsagePercent: number | undefined;
|
|
177
193
|
};
|
|
194
|
+
compacted: boolean;
|
|
178
195
|
}>;
|
|
179
196
|
close(): Promise<void>;
|
|
180
197
|
}
|
|
@@ -5,7 +5,7 @@ import { appendContextFilesToPrompt } from "../context-files.js";
|
|
|
5
5
|
import { PROVIDER_MEDIA_CAPABILITIES, buildKimiContent } from "../media-adapters.js";
|
|
6
6
|
import { assertMediaCapabilities, defaultPromptForMedia, resolveTurnMedia, } from "../media-input.js";
|
|
7
7
|
import { KimiWireTransport } from "../transports/kimi-wire-transport.js";
|
|
8
|
-
import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, proxyToEnv, sanitizeForLog, } from "../shared.js";
|
|
8
|
+
import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, noteToolFinished, noteToolStarted, proxyToEnv, sanitizeForLog, withActiveTool, } from "../shared.js";
|
|
9
9
|
const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
|
|
10
10
|
const MIN_TURN_DEADLINE_MS = 30 * 1000;
|
|
11
11
|
const MAX_TURN_DEADLINE_MS = 30 * 60 * 1000;
|
|
@@ -233,7 +233,7 @@ export class KimiCliSession extends EventEmitter {
|
|
|
233
233
|
}
|
|
234
234
|
: null,
|
|
235
235
|
currentTurnStatus: this.getCurrentTurnStatus(),
|
|
236
|
-
capabilities: { media: PROVIDER_MEDIA_CAPABILITIES[KIMI_PROVIDER_VARIANT] },
|
|
236
|
+
capabilities: { compact: true, media: PROVIDER_MEDIA_CAPABILITIES[KIMI_PROVIDER_VARIANT] },
|
|
237
237
|
pid: this.transport.pid || undefined,
|
|
238
238
|
};
|
|
239
239
|
}
|
|
@@ -241,7 +241,7 @@ export class KimiCliSession extends EventEmitter {
|
|
|
241
241
|
return this.sessionInfo ? { ...this.sessionInfo } : null;
|
|
242
242
|
}
|
|
243
243
|
getCurrentTurnStatus() {
|
|
244
|
-
return this.currentTurnStatus
|
|
244
|
+
return withActiveTool(this.currentTurnStatus, this.currentTurn);
|
|
245
245
|
}
|
|
246
246
|
async ensureSessionInfo() {
|
|
247
247
|
await this.boot();
|
|
@@ -642,6 +642,9 @@ export class KimiCliSession extends EventEmitter {
|
|
|
642
642
|
}
|
|
643
643
|
currentTurn.fullText += text;
|
|
644
644
|
currentTurn.bufferedAssistantText = "";
|
|
645
|
+
if (currentTurn.suppressReply) {
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
645
648
|
await this.emitAssistantMessage(text);
|
|
646
649
|
return true;
|
|
647
650
|
}
|
|
@@ -760,6 +763,7 @@ export class KimiCliSession extends EventEmitter {
|
|
|
760
763
|
}, currentTurn.onProgress);
|
|
761
764
|
return;
|
|
762
765
|
case "CompactionBegin":
|
|
766
|
+
currentTurn.compacted = true;
|
|
763
767
|
await this.emitWorkingStatus({
|
|
764
768
|
phase: "context_compaction",
|
|
765
769
|
reply_in_progress: true,
|
|
@@ -792,7 +796,9 @@ export class KimiCliSession extends EventEmitter {
|
|
|
792
796
|
phase: "message_aggregation",
|
|
793
797
|
reply_in_progress: true,
|
|
794
798
|
status_line: statusLineForPhase("message_aggregation"),
|
|
795
|
-
reply_preview:
|
|
799
|
+
reply_preview: currentTurn.suppressReply
|
|
800
|
+
? undefined
|
|
801
|
+
: sanitizeSummary(currentTurn.bufferedAssistantText, 120),
|
|
796
802
|
}, currentTurn.onProgress);
|
|
797
803
|
}
|
|
798
804
|
return;
|
|
@@ -802,6 +808,7 @@ export class KimiCliSession extends EventEmitter {
|
|
|
802
808
|
if (toolId) {
|
|
803
809
|
currentTurn.toolCalls.set(toolId, toolName);
|
|
804
810
|
}
|
|
811
|
+
noteToolStarted(currentTurn, toolId, toolName, normalizedPayload?.function?.arguments);
|
|
805
812
|
const phase = toolPhaseForName(toolName);
|
|
806
813
|
await this.emitWorkingStatus({
|
|
807
814
|
phase,
|
|
@@ -813,6 +820,7 @@ export class KimiCliSession extends EventEmitter {
|
|
|
813
820
|
case "ToolResult": {
|
|
814
821
|
const toolId = normalizeText(normalizedPayload.tool_call_id);
|
|
815
822
|
const toolName = currentTurn.toolCalls.get(toolId) || "";
|
|
823
|
+
noteToolFinished(currentTurn, toolId);
|
|
816
824
|
const phase = toolPhaseForName(toolName);
|
|
817
825
|
const statusDoneLine = sanitizeSummary(normalizedPayload?.return_value?.message || normalizedPayload?.return_value?.output, 160) || undefined;
|
|
818
826
|
await this.emitWorkingStatus({
|
|
@@ -877,7 +885,27 @@ export class KimiCliSession extends EventEmitter {
|
|
|
877
885
|
}
|
|
878
886
|
return false;
|
|
879
887
|
}
|
|
880
|
-
|
|
888
|
+
/**
|
|
889
|
+
* Manually compact via Kimi CLI's built-in `/compact` slash command, which
|
|
890
|
+
* the wire `prompt` method dispatches like the interactive shell does. The
|
|
891
|
+
* command takes no focus instructions.
|
|
892
|
+
*
|
|
893
|
+
* @param {import("../shared.js").CompactRequest} [request]
|
|
894
|
+
* @param {{ onProgress?: Function }} [options]
|
|
895
|
+
* @returns {Promise<import("../shared.js").CompactResult>}
|
|
896
|
+
*/
|
|
897
|
+
async runCompact(request = {}, { onProgress = null } = {}) {
|
|
898
|
+
if (this.pendingHistorySeed) {
|
|
899
|
+
return { compact: { status: "noop", instructionsApplied: false }, usage: null, metadata: {} };
|
|
900
|
+
}
|
|
901
|
+
const turnResult = await this.runTurn("/compact", { onProgress, suppressReply: true });
|
|
902
|
+
return {
|
|
903
|
+
compact: { status: turnResult.compacted ? "compacted" : "noop", instructionsApplied: false },
|
|
904
|
+
usage: turnResult.usage,
|
|
905
|
+
metadata: turnResult.metadata,
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
async runTurn(promptText, { useInitialImages = false, media: mediaInput, contextFiles, onProgress = null, jsonSchema = null, suppressReply = false } = {}) {
|
|
881
909
|
if (this.closeRequested || this.closed) {
|
|
882
910
|
throw this.createSessionClosedError();
|
|
883
911
|
}
|
|
@@ -905,8 +933,12 @@ export class KimiCliSession extends EventEmitter {
|
|
|
905
933
|
await this.failPendingTurnStart(error, onProgress);
|
|
906
934
|
throw error;
|
|
907
935
|
}
|
|
908
|
-
|
|
936
|
+
if (!suppressReply) {
|
|
937
|
+
this.history.push({ role: "user", content: promptText });
|
|
938
|
+
}
|
|
909
939
|
const currentTurn = {
|
|
940
|
+
suppressReply,
|
|
941
|
+
compacted: false,
|
|
910
942
|
fullText: "",
|
|
911
943
|
bufferedAssistantText: "",
|
|
912
944
|
thinkText: "",
|
|
@@ -948,7 +980,7 @@ export class KimiCliSession extends EventEmitter {
|
|
|
948
980
|
]);
|
|
949
981
|
currentTurn.settled = true;
|
|
950
982
|
await this.finalizeAssistantMessage(currentTurn);
|
|
951
|
-
if (currentTurn.fullText) {
|
|
983
|
+
if (currentTurn.fullText && !suppressReply) {
|
|
952
984
|
this.history.push({ role: "assistant", content: currentTurn.fullText });
|
|
953
985
|
}
|
|
954
986
|
const normalizedStatus = normalizeText(promptResult?.status);
|
|
@@ -977,6 +1009,7 @@ export class KimiCliSession extends EventEmitter {
|
|
|
977
1009
|
promptStatus: normalizedStatus || undefined,
|
|
978
1010
|
contextUsagePercent: this.lastContextUsagePercent,
|
|
979
1011
|
},
|
|
1012
|
+
compacted: currentTurn.compacted,
|
|
980
1013
|
};
|
|
981
1014
|
}
|
|
982
1015
|
catch (error) {
|
|
@@ -61,6 +61,7 @@ export class KimiPrintSession extends EventEmitter<[never]> {
|
|
|
61
61
|
} | null;
|
|
62
62
|
currentTurnStatus: any;
|
|
63
63
|
capabilities: {
|
|
64
|
+
compact: boolean;
|
|
64
65
|
media: Readonly<{
|
|
65
66
|
image: "unsupported";
|
|
66
67
|
}> | Readonly<{
|
|
@@ -115,10 +116,24 @@ export class KimiPrintSession extends EventEmitter<[never]> {
|
|
|
115
116
|
};
|
|
116
117
|
maybeEmitAuthRequired(stderrTail: any): void;
|
|
117
118
|
interruptCurrentTurn(): Promise<boolean>;
|
|
118
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Manually compact via kimi-cli's built-in `/compact` slash command; print
|
|
121
|
+
* mode runs it through the same soul as the interactive shell. Print output
|
|
122
|
+
* carries no compaction events, so the outcome is read from the CLI's fixed
|
|
123
|
+
* replies. The command takes no focus instructions.
|
|
124
|
+
*
|
|
125
|
+
* @param {import("../shared.js").CompactRequest} [request]
|
|
126
|
+
* @param {{ onProgress?: Function }} [options]
|
|
127
|
+
* @returns {Promise<import("../shared.js").CompactResult>}
|
|
128
|
+
*/
|
|
129
|
+
runCompact(request?: import("../shared.js").CompactRequest, { onProgress }?: {
|
|
130
|
+
onProgress?: Function;
|
|
131
|
+
}): Promise<import("../shared.js").CompactResult>;
|
|
132
|
+
runTurn(promptText: any, { useInitialImages, media: mediaInput, contextFiles, onProgress, jsonSchema, suppressReply }?: {
|
|
119
133
|
useInitialImages?: boolean | undefined;
|
|
120
134
|
onProgress?: null | undefined;
|
|
121
135
|
jsonSchema?: null | undefined;
|
|
136
|
+
suppressReply?: boolean | undefined;
|
|
122
137
|
}): Promise<{
|
|
123
138
|
text: string;
|
|
124
139
|
usage: null;
|
|
@@ -8,7 +8,7 @@ import { KIMI_CLI_PRINT_VARIANT as KIMI_PRINT_PROVIDER_VARIANT } from "../built-
|
|
|
8
8
|
import { appendContextFilesToPrompt } from "../context-files.js";
|
|
9
9
|
import { PROVIDER_MEDIA_CAPABILITIES, UNSUPPORTED_MEDIA_CAPABILITIES, buildKimiContent, } from "../media-adapters.js";
|
|
10
10
|
import { assertMediaCapabilities, defaultPromptForMedia, resolveTurnMedia, } from "../media-input.js";
|
|
11
|
-
import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, parseCommandParts, proxyToEnv, sanitizeForLog, } from "../shared.js";
|
|
11
|
+
import { emitLog, getBoundedEnvInt, loadEnvConfig, normalizeLogger, noteToolFinished, noteToolStarted, parseCommandParts, proxyToEnv, sanitizeForLog, withActiveTool, } from "../shared.js";
|
|
12
12
|
const DEFAULT_TURN_DEADLINE_MS = 12 * 60 * 1000;
|
|
13
13
|
const MIN_TURN_DEADLINE_MS = 30 * 1000;
|
|
14
14
|
const MAX_TURN_DEADLINE_MS = 30 * 60 * 1000;
|
|
@@ -270,6 +270,8 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
270
270
|
: null,
|
|
271
271
|
currentTurnStatus: this.getCurrentTurnStatus(),
|
|
272
272
|
capabilities: {
|
|
273
|
+
// Kimi Code's `--prompt` CLI is unverified for slash commands.
|
|
274
|
+
compact: this.cliMode !== "prompt",
|
|
273
275
|
media: this.cliMode === "prompt"
|
|
274
276
|
? UNSUPPORTED_MEDIA_CAPABILITIES
|
|
275
277
|
: PROVIDER_MEDIA_CAPABILITIES[KIMI_PRINT_PROVIDER_VARIANT],
|
|
@@ -281,7 +283,7 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
281
283
|
return this.sessionInfo ? { ...this.sessionInfo } : null;
|
|
282
284
|
}
|
|
283
285
|
getCurrentTurnStatus() {
|
|
284
|
-
return this.currentTurnStatus
|
|
286
|
+
return withActiveTool(this.currentTurnStatus, this.currentTurn);
|
|
285
287
|
}
|
|
286
288
|
async ensureSessionInfo() {
|
|
287
289
|
if (!this.sessionInfo?.sessionIdDeferred) {
|
|
@@ -468,7 +470,42 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
468
470
|
return false;
|
|
469
471
|
}
|
|
470
472
|
}
|
|
471
|
-
|
|
473
|
+
/**
|
|
474
|
+
* Manually compact via kimi-cli's built-in `/compact` slash command; print
|
|
475
|
+
* mode runs it through the same soul as the interactive shell. Print output
|
|
476
|
+
* carries no compaction events, so the outcome is read from the CLI's fixed
|
|
477
|
+
* replies. The command takes no focus instructions.
|
|
478
|
+
*
|
|
479
|
+
* @param {import("../shared.js").CompactRequest} [request]
|
|
480
|
+
* @param {{ onProgress?: Function }} [options]
|
|
481
|
+
* @returns {Promise<import("../shared.js").CompactResult>}
|
|
482
|
+
*/
|
|
483
|
+
async runCompact(request = {}, { onProgress = null } = {}) {
|
|
484
|
+
if (this.cliMode === "prompt") {
|
|
485
|
+
throw createTurnError("Kimi prompt mode does not support /compact", { reason: "unsupported_compact" });
|
|
486
|
+
}
|
|
487
|
+
if (this.pendingHistorySeed) {
|
|
488
|
+
return { compact: { status: "noop", instructionsApplied: false }, usage: null, metadata: {} };
|
|
489
|
+
}
|
|
490
|
+
const turnResult = await this.runTurn("/compact", { onProgress, suppressReply: true });
|
|
491
|
+
const reply = String(turnResult.text || "");
|
|
492
|
+
// Print mode exits 0 even when the model call fails, so only kimi-cli's
|
|
493
|
+
// fixed replies are trusted.
|
|
494
|
+
if (!/context has been compacted|context is empty/i.test(reply)) {
|
|
495
|
+
throw createTurnError(`Kimi compaction failed${reply.trim() ? `: ${reply.trim()}` : ""}`, {
|
|
496
|
+
reason: "compact_failed",
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
return {
|
|
500
|
+
compact: {
|
|
501
|
+
status: /context is empty/i.test(reply) ? "noop" : "compacted",
|
|
502
|
+
instructionsApplied: false,
|
|
503
|
+
},
|
|
504
|
+
usage: turnResult.usage,
|
|
505
|
+
metadata: turnResult.metadata,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
async runTurn(promptText, { useInitialImages = false, media: mediaInput, contextFiles, onProgress = null, jsonSchema = null, suppressReply = false } = {}) {
|
|
472
509
|
if (this.closeRequested || this.closed) {
|
|
473
510
|
throw this.createSessionClosedError();
|
|
474
511
|
}
|
|
@@ -497,7 +534,9 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
497
534
|
return buildEmptyTurnResult();
|
|
498
535
|
}
|
|
499
536
|
this.announceSession();
|
|
500
|
-
|
|
537
|
+
if (!suppressReply) {
|
|
538
|
+
this.history.push({ role: "user", content: String(promptText || "") });
|
|
539
|
+
}
|
|
501
540
|
const currentTurn = {
|
|
502
541
|
child: null,
|
|
503
542
|
fullText: "",
|
|
@@ -606,6 +645,9 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
606
645
|
if (role === "assistant") {
|
|
607
646
|
const assistantText = normalizeTextContent(payload.content);
|
|
608
647
|
if (Array.isArray(payload?.tool_calls) && payload.tool_calls.length > 0) {
|
|
648
|
+
for (const call of payload.tool_calls) {
|
|
649
|
+
noteToolStarted(currentTurn, call?.id, call?.function?.name, call?.function?.arguments);
|
|
650
|
+
}
|
|
609
651
|
void this.emitWorkingStatus({
|
|
610
652
|
phase: "command_execution",
|
|
611
653
|
reply_in_progress: true,
|
|
@@ -621,11 +663,14 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
621
663
|
}
|
|
622
664
|
if (assistantText) {
|
|
623
665
|
currentTurn.fullText += assistantText;
|
|
624
|
-
|
|
666
|
+
if (!suppressReply) {
|
|
667
|
+
void this.emitAssistantMessage(assistantText);
|
|
668
|
+
}
|
|
625
669
|
}
|
|
626
670
|
return;
|
|
627
671
|
}
|
|
628
672
|
if (role === "tool") {
|
|
673
|
+
noteToolFinished(currentTurn, payload.tool_call_id);
|
|
629
674
|
void this.emitWorkingStatus({
|
|
630
675
|
phase: "command_execution",
|
|
631
676
|
reply_in_progress: true,
|
|
@@ -684,7 +729,7 @@ export class KimiPrintSession extends EventEmitter {
|
|
|
684
729
|
}
|
|
685
730
|
child.stdin.end();
|
|
686
731
|
});
|
|
687
|
-
if (result?.text) {
|
|
732
|
+
if (result?.text && !suppressReply) {
|
|
688
733
|
this.history.push({ role: "assistant", content: result.text });
|
|
689
734
|
}
|
|
690
735
|
this.activeReplyTarget = "";
|
|
@@ -20,7 +20,7 @@ export class OpencodeSdkSession extends EventEmitter<[never]> {
|
|
|
20
20
|
activeReplyTarget: string;
|
|
21
21
|
lastReplyTarget: string;
|
|
22
22
|
currentTurn: {
|
|
23
|
-
abortController:
|
|
23
|
+
abortController: any;
|
|
24
24
|
assistantMessages: Map<any, any>;
|
|
25
25
|
assistantMessageOrder: never[];
|
|
26
26
|
activeAssistantMessageId: string;
|
|
@@ -29,7 +29,7 @@ export class OpencodeSdkSession extends EventEmitter<[never]> {
|
|
|
29
29
|
fullText: string;
|
|
30
30
|
items: never[];
|
|
31
31
|
lastAssistantInfo: null;
|
|
32
|
-
onProgress:
|
|
32
|
+
onProgress: any;
|
|
33
33
|
resolve: null;
|
|
34
34
|
reject: null;
|
|
35
35
|
settled: boolean;
|
|
@@ -79,6 +79,7 @@ export class OpencodeSdkSession extends EventEmitter<[never]> {
|
|
|
79
79
|
manualResume: null;
|
|
80
80
|
currentTurnStatus: any;
|
|
81
81
|
capabilities: {
|
|
82
|
+
compact: boolean;
|
|
82
83
|
media: Readonly<{
|
|
83
84
|
image: "native";
|
|
84
85
|
}>;
|
|
@@ -169,6 +170,47 @@ export class OpencodeSdkSession extends EventEmitter<[never]> {
|
|
|
169
170
|
handleTransportFailure(error: any): void;
|
|
170
171
|
handleTransportExit(payload: any): void;
|
|
171
172
|
interruptCurrentTurn(): Promise<boolean>;
|
|
173
|
+
createTurnState(abortController: any, onProgress: any): {
|
|
174
|
+
currentTurn: {
|
|
175
|
+
abortController: any;
|
|
176
|
+
assistantMessages: Map<any, any>;
|
|
177
|
+
assistantMessageOrder: never[];
|
|
178
|
+
activeAssistantMessageId: string;
|
|
179
|
+
messageRoles: Map<any, any>;
|
|
180
|
+
pendingMessageEvents: Map<any, any>;
|
|
181
|
+
fullText: string;
|
|
182
|
+
items: never[];
|
|
183
|
+
lastAssistantInfo: null;
|
|
184
|
+
onProgress: any;
|
|
185
|
+
resolve: null;
|
|
186
|
+
reject: null;
|
|
187
|
+
settled: boolean;
|
|
188
|
+
terminalWorkingStatusEmitted: boolean;
|
|
189
|
+
};
|
|
190
|
+
completionPromise: Promise<any>;
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* Model to summarize with: `session.summarize` requires one explicitly.
|
|
194
|
+
* Prefer what the session actually ran on (last turn, then its recent
|
|
195
|
+
* messages); the configured model is only a fallback.
|
|
196
|
+
*/
|
|
197
|
+
resolveCompactionModel(): Promise<{
|
|
198
|
+
providerID: any;
|
|
199
|
+
modelID: any;
|
|
200
|
+
}>;
|
|
201
|
+
/**
|
|
202
|
+
* Manually compact via the server's `session.summarize` (what the TUI's
|
|
203
|
+
* `/compact` calls). It takes no focus instructions and answers `true`
|
|
204
|
+
* even when summarizing fails, so success is judged from the summary
|
|
205
|
+
* message itself.
|
|
206
|
+
*
|
|
207
|
+
* @param {import("../shared.js").CompactRequest} [request]
|
|
208
|
+
* @param {{ onProgress?: Function }} [options]
|
|
209
|
+
* @returns {Promise<import("../shared.js").CompactResult>}
|
|
210
|
+
*/
|
|
211
|
+
runCompact(request?: import("../shared.js").CompactRequest, { onProgress }?: {
|
|
212
|
+
onProgress?: Function;
|
|
213
|
+
}): Promise<import("../shared.js").CompactResult>;
|
|
172
214
|
runTurn(promptText: any, { useInitialImages, media: mediaInput, contextFiles, onProgress, jsonSchema }?: {
|
|
173
215
|
useInitialImages?: boolean | undefined;
|
|
174
216
|
onProgress?: null | undefined;
|