@love-moon/ai-sdk 0.7.6 → 0.8.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
CHANGED
|
@@ -139,6 +139,17 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
139
139
|
thread_id: any;
|
|
140
140
|
};
|
|
141
141
|
emitWorkingStatus(payload: any): Promise<void>;
|
|
142
|
+
/**
|
|
143
|
+
* Idempotent terminal working-status clear (see BUG-R6-01 lesson). The
|
|
144
|
+
* "reply_in_progress:false" signal that unsticks the composer must be emitted
|
|
145
|
+
* from a reliable, always-arriving path, and multiple paths emitting it must
|
|
146
|
+
* be a safe no-op. A one-time guard on the turn object ensures we clear
|
|
147
|
+
* exactly once regardless of how many code paths reach here.
|
|
148
|
+
*/
|
|
149
|
+
emitTurnCompletedStatus(currentTurn: any, { phase, status_done_line }?: {
|
|
150
|
+
phase?: string | undefined;
|
|
151
|
+
status_done_line?: string | undefined;
|
|
152
|
+
}): Promise<void>;
|
|
142
153
|
emitAssistantMessage(text: any): Promise<void>;
|
|
143
154
|
finalizeActiveAssistantMessage(currentTurn?: {
|
|
144
155
|
turnId: string;
|
|
@@ -489,6 +489,26 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
489
489
|
}
|
|
490
490
|
this.emit("working_status", snapshot);
|
|
491
491
|
}
|
|
492
|
+
/**
|
|
493
|
+
* Idempotent terminal working-status clear (see BUG-R6-01 lesson). The
|
|
494
|
+
* "reply_in_progress:false" signal that unsticks the composer must be emitted
|
|
495
|
+
* from a reliable, always-arriving path, and multiple paths emitting it must
|
|
496
|
+
* be a safe no-op. A one-time guard on the turn object ensures we clear
|
|
497
|
+
* exactly once regardless of how many code paths reach here.
|
|
498
|
+
*/
|
|
499
|
+
async emitTurnCompletedStatus(currentTurn, { phase = "turn_completed", status_done_line = "codex finished" } = {}) {
|
|
500
|
+
if (currentTurn && currentTurn.terminalStatusEmitted) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (currentTurn) {
|
|
504
|
+
currentTurn.terminalStatusEmitted = true;
|
|
505
|
+
}
|
|
506
|
+
await this.emitWorkingStatus({
|
|
507
|
+
phase,
|
|
508
|
+
reply_in_progress: false,
|
|
509
|
+
status_done_line,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
492
512
|
async emitAssistantMessage(text) {
|
|
493
513
|
this.touchTurnActivity();
|
|
494
514
|
const payload = {
|
|
@@ -869,7 +889,15 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
869
889
|
return;
|
|
870
890
|
}
|
|
871
891
|
case "turn/completed": {
|
|
872
|
-
|
|
892
|
+
let currentTurn = this.ensureCurrentTurn(params);
|
|
893
|
+
if (!currentTurn && this.currentTurn && !this.currentTurn.goalMode) {
|
|
894
|
+
// Defense-in-depth (BUG-R6-01 class): a non-goal session has exactly
|
|
895
|
+
// one active turn. If turn matching fails (missing `turn/started`, a
|
|
896
|
+
// turnId mismatch, or an id carried on a different field), we must NOT
|
|
897
|
+
// strand the terminal clear here — otherwise the composer stays on
|
|
898
|
+
// "codex composing reply" until the 12-min idle deadline or a reload.
|
|
899
|
+
currentTurn = this.currentTurn;
|
|
900
|
+
}
|
|
873
901
|
if (!currentTurn) {
|
|
874
902
|
return;
|
|
875
903
|
}
|
|
@@ -887,9 +915,8 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
887
915
|
turnStatus: status,
|
|
888
916
|
turnError,
|
|
889
917
|
});
|
|
890
|
-
await this.
|
|
918
|
+
await this.emitTurnCompletedStatus(currentTurn, {
|
|
891
919
|
phase: "turn_failed",
|
|
892
|
-
reply_in_progress: false,
|
|
893
920
|
status_done_line: `codex failed (${status})`,
|
|
894
921
|
});
|
|
895
922
|
if (currentTurn.goalMode) {
|
|
@@ -927,11 +954,7 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
927
954
|
currentTurn.activeAssistantMessageText = "";
|
|
928
955
|
return;
|
|
929
956
|
}
|
|
930
|
-
await this.
|
|
931
|
-
phase: "turn_completed",
|
|
932
|
-
reply_in_progress: false,
|
|
933
|
-
status_done_line: "codex finished",
|
|
934
|
-
});
|
|
957
|
+
await this.emitTurnCompletedStatus(currentTurn);
|
|
935
958
|
currentTurn.resolve({
|
|
936
959
|
turn,
|
|
937
960
|
usage: this.tokenUsage ? { ...this.tokenUsage } : null,
|
|
@@ -1075,6 +1098,10 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
1075
1098
|
await this.finalizeActiveAssistantMessage(currentTurn, {
|
|
1076
1099
|
messageId: currentTurn.activeAssistantMessageId,
|
|
1077
1100
|
});
|
|
1101
|
+
// Belt-and-suspenders terminal clear. Idempotent (guarded on the turn),
|
|
1102
|
+
// so when the wire `turn/completed` already cleared this is a no-op; it
|
|
1103
|
+
// only fires if the turn resolved without the wire clear reaching here.
|
|
1104
|
+
await this.emitTurnCompletedStatus(currentTurn);
|
|
1078
1105
|
if (currentTurn.fullText) {
|
|
1079
1106
|
this.history.push({ role: "assistant", content: currentTurn.fullText });
|
|
1080
1107
|
}
|
|
@@ -22,6 +22,9 @@ export class KimiCliSession extends EventEmitter<[never]> {
|
|
|
22
22
|
currentTurn: {
|
|
23
23
|
fullText: string;
|
|
24
24
|
bufferedAssistantText: string;
|
|
25
|
+
thinkText: string;
|
|
26
|
+
thinkStatusLine: string;
|
|
27
|
+
thinkStatusLineAt: number;
|
|
25
28
|
items: never[];
|
|
26
29
|
toolCalls: Map<any, any>;
|
|
27
30
|
onProgress: null;
|
|
@@ -136,6 +139,8 @@ export class KimiCliSession extends EventEmitter<[never]> {
|
|
|
136
139
|
bootInternal(): Promise<void>;
|
|
137
140
|
updateUsageFromStatus(payload: any): void;
|
|
138
141
|
appendAssistantText(currentTurn: any, text: any): void;
|
|
142
|
+
appendThinkText(currentTurn: any, text: any): void;
|
|
143
|
+
thinkStatusLine(currentTurn: any): any;
|
|
139
144
|
finalizeAssistantMessage(currentTurn: any): Promise<boolean>;
|
|
140
145
|
maybeEmitAuthRequired(error: any): void;
|
|
141
146
|
failUnexpectedInteractiveRequest(id: any, requestType: any, payload: any): Promise<void>;
|
|
@@ -11,6 +11,8 @@ const DEFAULT_STATUS_THROTTLE_MS = 450;
|
|
|
11
11
|
const DEFAULT_REASONING_STATUS_THROTTLE_MS = 2500;
|
|
12
12
|
const DEFAULT_STATUS_PREVIEW_DELTA_CHARS = 24;
|
|
13
13
|
const MAX_STATUS_TIMING_MS = 10 * 1000;
|
|
14
|
+
const THINK_STATUS_LINE_MAX_CHARS = 120;
|
|
15
|
+
const THINK_TAIL_BUFFER_CHARS = 400;
|
|
14
16
|
function waitForever() {
|
|
15
17
|
return new Promise(() => { });
|
|
16
18
|
}
|
|
@@ -603,6 +605,29 @@ export class KimiCliSession extends EventEmitter {
|
|
|
603
605
|
}
|
|
604
606
|
currentTurn.bufferedAssistantText += normalized;
|
|
605
607
|
}
|
|
608
|
+
appendThinkText(currentTurn, text) {
|
|
609
|
+
const normalized = normalizeText(text);
|
|
610
|
+
if (!normalized) {
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
currentTurn.thinkText = `${currentTurn.thinkText}${normalized}`.slice(-THINK_TAIL_BUFFER_CHARS);
|
|
614
|
+
}
|
|
615
|
+
thinkStatusLine(currentTurn) {
|
|
616
|
+
const now = this.now();
|
|
617
|
+
if (currentTurn.thinkStatusLine &&
|
|
618
|
+
now - currentTurn.thinkStatusLineAt < this.reasoningStatusThrottleMs) {
|
|
619
|
+
return currentTurn.thinkStatusLine;
|
|
620
|
+
}
|
|
621
|
+
const sanitized = sanitizeSummary(currentTurn.thinkText, THINK_TAIL_BUFFER_CHARS);
|
|
622
|
+
const snippet = sanitized.length > THINK_STATUS_LINE_MAX_CHARS
|
|
623
|
+
? `…${sanitized.slice(-THINK_STATUS_LINE_MAX_CHARS)}`
|
|
624
|
+
: sanitized;
|
|
625
|
+
if (snippet && snippet !== currentTurn.thinkStatusLine) {
|
|
626
|
+
currentTurn.thinkStatusLine = snippet;
|
|
627
|
+
currentTurn.thinkStatusLineAt = now;
|
|
628
|
+
}
|
|
629
|
+
return currentTurn.thinkStatusLine || statusLineForPhase("reasoning");
|
|
630
|
+
}
|
|
606
631
|
async finalizeAssistantMessage(currentTurn) {
|
|
607
632
|
if (!currentTurn) {
|
|
608
633
|
return false;
|
|
@@ -707,8 +732,23 @@ export class KimiCliSession extends EventEmitter {
|
|
|
707
732
|
return;
|
|
708
733
|
case "TurnEnd":
|
|
709
734
|
currentTurn.seenTurnEnd = true;
|
|
735
|
+
if (currentTurn.bufferedAssistantText) {
|
|
736
|
+
await this.finalizeAssistantMessage(currentTurn);
|
|
737
|
+
}
|
|
738
|
+
// Clear reply_in_progress as soon as the wire reports the turn ended,
|
|
739
|
+
// decoupled from the `prompt` JSON-RPC promise. Without this, a delayed
|
|
740
|
+
// or dropped prompt RPC response leaves the web UI pinned on
|
|
741
|
+
// "Kimi is writing the reply" until reload. emitTerminalWorkingStatus is
|
|
742
|
+
// idempotent, so the later runTurn call becomes a harmless no-op.
|
|
743
|
+
await this.emitTerminalWorkingStatus(currentTurn, {
|
|
744
|
+
phase: "turn_completed",
|
|
745
|
+
status_done_line: "Kimi finished",
|
|
746
|
+
}, currentTurn.onProgress);
|
|
710
747
|
return;
|
|
711
748
|
case "StepBegin":
|
|
749
|
+
if (currentTurn.bufferedAssistantText) {
|
|
750
|
+
await this.finalizeAssistantMessage(currentTurn);
|
|
751
|
+
}
|
|
712
752
|
await this.emitWorkingStatus({
|
|
713
753
|
phase: "reasoning",
|
|
714
754
|
reply_in_progress: true,
|
|
@@ -734,10 +774,11 @@ export class KimiCliSession extends EventEmitter {
|
|
|
734
774
|
return;
|
|
735
775
|
case "ContentPart":
|
|
736
776
|
if (normalizedPayload.type === "think") {
|
|
777
|
+
this.appendThinkText(currentTurn, normalizedPayload.think);
|
|
737
778
|
await this.emitWorkingStatus({
|
|
738
779
|
phase: "reasoning",
|
|
739
780
|
reply_in_progress: true,
|
|
740
|
-
status_line:
|
|
781
|
+
status_line: this.thinkStatusLine(currentTurn),
|
|
741
782
|
}, currentTurn.onProgress);
|
|
742
783
|
return;
|
|
743
784
|
}
|
|
@@ -860,6 +901,9 @@ export class KimiCliSession extends EventEmitter {
|
|
|
860
901
|
const currentTurn = {
|
|
861
902
|
fullText: "",
|
|
862
903
|
bufferedAssistantText: "",
|
|
904
|
+
thinkText: "",
|
|
905
|
+
thinkStatusLine: "",
|
|
906
|
+
thinkStatusLineAt: 0,
|
|
863
907
|
items: [],
|
|
864
908
|
toolCalls: new Map(),
|
|
865
909
|
onProgress,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@love-moon/ai-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/lovemoon-ai/conductor.git"
|
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
"zod": "^4.1.5"
|
|
33
33
|
},
|
|
34
34
|
"optionalDependencies": {
|
|
35
|
-
"@love-moon/chat-web": "^0.
|
|
35
|
+
"@love-moon/chat-web": "^0.8.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^22.10.2",
|
|
39
39
|
"tsx": "^4.20.6",
|
|
40
40
|
"typescript": "^5.6.3"
|
|
41
41
|
},
|
|
42
|
-
"gitCommitId": "
|
|
42
|
+
"gitCommitId": "04d4f62"
|
|
43
43
|
}
|