@love-moon/ai-sdk 0.13.1 → 0.13.3
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 +48 -0
- package/dist/client.d.ts +5 -0
- package/dist/client.js +24 -0
- package/dist/providers/claude-agent-sdk-session.d.ts +25 -1
- package/dist/providers/claude-agent-sdk-session.js +96 -7
- package/dist/providers/codex-app-server-session.d.ts +44 -0
- package/dist/providers/codex-app-server-session.js +199 -17
- package/dist/providers/copilot-sdk-session.d.ts +29 -1
- package/dist/providers/copilot-sdk-session.js +111 -16
- package/dist/providers/dsh-sdk-session.d.ts +20 -1
- package/dist/providers/dsh-sdk-session.js +75 -8
- package/dist/providers/kimi-cli-session.d.ts +18 -1
- package/dist/providers/kimi-cli-session.js +36 -5
- package/dist/providers/kimi-print-session.d.ts +16 -1
- package/dist/providers/kimi-print-session.js +45 -4
- package/dist/providers/opencode-sdk-session.d.ts +44 -2
- package/dist/providers/opencode-sdk-session.js +169 -24
- package/dist/shared.d.ts +66 -0
- package/dist/shared.js +25 -1
- package/dist/worker.js +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# @love-moon/ai-sdk
|
|
2
2
|
|
|
3
|
+
## 0.13.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 0859251: Show Codex progress notes as soon as Codex finishes writing them. The session
|
|
8
|
+
did not recognize the app-server's `agentMessage` items, so a commentary message
|
|
9
|
+
was held back until the next message started or the turn ended, often minutes
|
|
10
|
+
later during long tool runs.
|
|
11
|
+
- c1b66d1: Fix Codex tasks that stopped mid-reply after the agent spawned sub-agents. In
|
|
12
|
+
multi-agent mode the Codex app-server also streams the sub-agent threads, and
|
|
13
|
+
the first sub-agent to finish ended the parent turn. The parent's later
|
|
14
|
+
messages and final answer were dropped, and a sub-agent's report could show up
|
|
15
|
+
as the AI reply. The session now ignores notifications from other threads.
|
|
16
|
+
- f6dbd6b: Show completed Codex replies while subsequent tools are still running, and keep replies intact when their final text and completion arrive together.
|
|
17
|
+
|
|
18
|
+
## 0.13.2
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- 8c2833d: Support `/compact [focus instructions]` in task chats. Fire detects the command
|
|
23
|
+
per message (like `/goal`), runs the backend's native context compaction
|
|
24
|
+
instead of sending the text to the model, and posts one confirmation with the
|
|
25
|
+
token savings. Sessions advertise `capabilities.compact` and implement the new
|
|
26
|
+
optional `runCompact()`: claude (native `/compact`), codex app-server
|
|
27
|
+
(`thread/compact/start`), copilot (`session.history.compact`), kimi wire and
|
|
28
|
+
legacy print (built-in `/compact`), opencode (`session.summarize`) and dsh
|
|
29
|
+
(summary turn, then a fresh session seeded with the summary). Backends without
|
|
30
|
+
the capability (chat-web, codex exec, Kimi Code prompt mode) reply with a
|
|
31
|
+
not-supported notice. Opencode compaction summaries no longer surface as chat
|
|
32
|
+
replies, and dsh clears its "compacting context" status when automatic
|
|
33
|
+
compaction ends.
|
|
34
|
+
- 8e3a4c3: Report how many model tokens each turn consumed. After every turn (or `/goal`)
|
|
35
|
+
`conductor fire` sends the turn's token count (fresh input + cache reads/writes +
|
|
36
|
+
output) over the new `task_turn_usage` websocket event, and the server adds it
|
|
37
|
+
to the task's running total shown in the task detail card. A persistent task's
|
|
38
|
+
total restarts with each round.
|
|
39
|
+
|
|
40
|
+
Failed or interrupted turns and `/compact` are counted too; when a turn's usage
|
|
41
|
+
is unknown the fire reports `null`, which clears the task's last-turn count.
|
|
42
|
+
|
|
43
|
+
- `@love-moon/conductor-sdk`: new `ConductorClient.sendTurnUsage(taskId, { tokens })`.
|
|
44
|
+
- `@love-moon/ai-sdk`: the Codex app-server provider's turn/goal `usage` now
|
|
45
|
+
carries `turnTotalTokens`, the turn's share of Codex's thread-cumulative total.
|
|
46
|
+
Claude and Codex turn errors carry the tokens spent before the failure as
|
|
47
|
+
`error.usage` (Claude sums streamed usage when an interrupted query ends
|
|
48
|
+
without a result).
|
|
49
|
+
- Claude and Codex backends are counted; other backends report nothing yet.
|
|
50
|
+
|
|
3
51
|
## 0.13.1
|
|
4
52
|
|
|
5
53
|
## 0.13.0
|
package/dist/client.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export class RemoteAiSession extends EventEmitter<[never]> {
|
|
|
18
18
|
workerReady: boolean;
|
|
19
19
|
capabilities: {
|
|
20
20
|
goal?: boolean | undefined;
|
|
21
|
+
compact?: boolean | undefined;
|
|
21
22
|
};
|
|
22
23
|
};
|
|
23
24
|
sessionMessageHandler: any;
|
|
@@ -51,6 +52,7 @@ export class RemoteAiSession extends EventEmitter<[never]> {
|
|
|
51
52
|
workerReady: boolean;
|
|
52
53
|
capabilities: {
|
|
53
54
|
goal?: boolean | undefined;
|
|
55
|
+
compact?: boolean | undefined;
|
|
54
56
|
};
|
|
55
57
|
};
|
|
56
58
|
usesSessionFileReplyStream(): boolean;
|
|
@@ -69,6 +71,7 @@ export class RemoteAiSession extends EventEmitter<[never]> {
|
|
|
69
71
|
interruptCurrentTurn(): Promise<any>;
|
|
70
72
|
runTurn(promptText: any, options?: {}): Promise<any>;
|
|
71
73
|
runGoal(goal: any, options?: {}): Promise<any>;
|
|
74
|
+
runCompact(request?: {}, options?: {}): Promise<any>;
|
|
72
75
|
getGoal(): Promise<any>;
|
|
73
76
|
clearGoal(): Promise<any>;
|
|
74
77
|
close(): Promise<void>;
|
|
@@ -108,6 +111,7 @@ declare class LocalAiSessionProxy extends EventEmitter<[never]> {
|
|
|
108
111
|
workerReady: boolean;
|
|
109
112
|
capabilities: {
|
|
110
113
|
goal?: boolean | undefined;
|
|
114
|
+
compact?: boolean | undefined;
|
|
111
115
|
};
|
|
112
116
|
};
|
|
113
117
|
readyPromise: Promise<any>;
|
|
@@ -126,6 +130,7 @@ declare class LocalAiSessionProxy extends EventEmitter<[never]> {
|
|
|
126
130
|
interruptCurrentTurn(): Promise<any>;
|
|
127
131
|
runTurn(promptText: any, options?: {}): Promise<any>;
|
|
128
132
|
runGoal(goal: any, options?: {}): Promise<any>;
|
|
133
|
+
runCompact(request?: {}, options?: {}): Promise<any>;
|
|
129
134
|
getGoal(): Promise<any>;
|
|
130
135
|
clearGoal(): Promise<any>;
|
|
131
136
|
close(): Promise<void>;
|
package/dist/client.js
CHANGED
|
@@ -9,6 +9,11 @@ function createUnsupportedGoalError(name) {
|
|
|
9
9
|
err.reason = "unsupported_goal";
|
|
10
10
|
return err;
|
|
11
11
|
}
|
|
12
|
+
function createUnsupportedCompactError(name) {
|
|
13
|
+
const err = new Error(`runCompact() is not supported by backend session (${name || "unknown"}).`);
|
|
14
|
+
err.reason = "unsupported_compact";
|
|
15
|
+
return err;
|
|
16
|
+
}
|
|
12
17
|
const WORKER_PATH = fileURLToPath(new URL("./worker.js", import.meta.url));
|
|
13
18
|
function sanitizeOptionsForWorker(options = {}) {
|
|
14
19
|
const sanitized = {};
|
|
@@ -217,6 +222,16 @@ export class RemoteAiSession extends EventEmitter {
|
|
|
217
222
|
progressHandler: typeof onProgress === "function" ? onProgress : null,
|
|
218
223
|
});
|
|
219
224
|
}
|
|
225
|
+
async runCompact(request = {}, options = {}) {
|
|
226
|
+
await this.readyPromise;
|
|
227
|
+
if (!this.snapshot?.capabilities || this.snapshot.capabilities.compact !== true) {
|
|
228
|
+
throw createUnsupportedCompactError(this.snapshot?.provider || this.snapshot?.backend);
|
|
229
|
+
}
|
|
230
|
+
const { onProgress, ...restOptions } = options || {};
|
|
231
|
+
return this.callWorker("runCompact", [request, restOptions], {
|
|
232
|
+
progressHandler: typeof onProgress === "function" ? onProgress : null,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
220
235
|
async getGoal() {
|
|
221
236
|
// Propagate worker-creation errors instead of masking them as "no goal".
|
|
222
237
|
// See N3 in the review.
|
|
@@ -675,6 +690,15 @@ class LocalAiSessionProxy extends EventEmitter {
|
|
|
675
690
|
}
|
|
676
691
|
return await session.runGoal(goal, options);
|
|
677
692
|
}
|
|
693
|
+
async runCompact(request = {}, options = {}) {
|
|
694
|
+
const session = await this.readyPromise;
|
|
695
|
+
// Capabilities resolved at init (snapshot first) are authoritative; some
|
|
696
|
+
// providers only advertise them via getSnapshot().
|
|
697
|
+
if (this.snapshot?.capabilities?.compact !== true || typeof session.runCompact !== "function") {
|
|
698
|
+
throw createUnsupportedCompactError(session?.constructor?.name);
|
|
699
|
+
}
|
|
700
|
+
return await session.runCompact(request, options);
|
|
701
|
+
}
|
|
678
702
|
async getGoal() {
|
|
679
703
|
const session = await this.readyPromise;
|
|
680
704
|
const capabilities = resolveSessionCapabilities(session);
|
|
@@ -9,6 +9,7 @@ export function resolveClaudeCommandForRoot(commandLine: any, env?: NodeJS.Proce
|
|
|
9
9
|
export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
10
10
|
static capabilities: Readonly<{
|
|
11
11
|
goal: true;
|
|
12
|
+
compact: true;
|
|
12
13
|
media: Readonly<{
|
|
13
14
|
image: "native";
|
|
14
15
|
}>;
|
|
@@ -16,6 +17,7 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
|
16
17
|
constructor(backend: any, options?: {});
|
|
17
18
|
getCapabilities(): {
|
|
18
19
|
goal: true;
|
|
20
|
+
compact: true;
|
|
19
21
|
media: Readonly<{
|
|
20
22
|
image: "native";
|
|
21
23
|
}>;
|
|
@@ -42,12 +44,16 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
|
42
44
|
manualResumeReady: boolean;
|
|
43
45
|
currentTurn: {
|
|
44
46
|
abortController: AbortController;
|
|
47
|
+
suppressReply: boolean;
|
|
48
|
+
compactMetadata: null;
|
|
49
|
+
compactError: string;
|
|
45
50
|
emittedAssistantMessage: boolean;
|
|
46
51
|
fullText: string;
|
|
47
52
|
items: never[];
|
|
48
53
|
query: null;
|
|
49
54
|
resultMessage: null;
|
|
50
55
|
terminalWorkingStatusEmitted: boolean;
|
|
56
|
+
usageByMessageId: Map<any, any>;
|
|
51
57
|
} | null;
|
|
52
58
|
lastResult: any;
|
|
53
59
|
rateLimitInfo: any;
|
|
@@ -88,6 +94,7 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
|
88
94
|
currentTurnStatus: any;
|
|
89
95
|
capabilities: {
|
|
90
96
|
goal: true;
|
|
97
|
+
compact: true;
|
|
91
98
|
media: Readonly<{
|
|
92
99
|
image: "native";
|
|
93
100
|
}>;
|
|
@@ -154,10 +161,11 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
|
154
161
|
onProgress: any;
|
|
155
162
|
}): Promise<void>;
|
|
156
163
|
interruptCurrentTurn(): Promise<boolean>;
|
|
157
|
-
runTurn(promptText: any, { useInitialImages, media: mediaInput, contextFiles, onProgress, jsonSchema }?: {
|
|
164
|
+
runTurn(promptText: any, { useInitialImages, media: mediaInput, contextFiles, onProgress, jsonSchema, suppressReply }?: {
|
|
158
165
|
useInitialImages?: boolean | undefined;
|
|
159
166
|
onProgress?: null | undefined;
|
|
160
167
|
jsonSchema?: null | undefined;
|
|
168
|
+
suppressReply?: boolean | undefined;
|
|
161
169
|
}): Promise<{
|
|
162
170
|
text: string;
|
|
163
171
|
usage: null;
|
|
@@ -165,6 +173,8 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
|
165
173
|
events: never[];
|
|
166
174
|
provider?: undefined;
|
|
167
175
|
metadata?: undefined;
|
|
176
|
+
compactMetadata?: undefined;
|
|
177
|
+
compactError?: undefined;
|
|
168
178
|
} | {
|
|
169
179
|
text: any;
|
|
170
180
|
usage: any;
|
|
@@ -178,6 +188,8 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
|
178
188
|
modelUsage: any;
|
|
179
189
|
structuredOutput: any;
|
|
180
190
|
};
|
|
191
|
+
compactMetadata: undefined;
|
|
192
|
+
compactError: string | undefined;
|
|
181
193
|
}>;
|
|
182
194
|
/**
|
|
183
195
|
* Trigger Claude's native `/goal` slash command. Implemented by sending the
|
|
@@ -204,6 +216,18 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
|
204
216
|
* @returns {Promise<import("../shared.js").GoalResult>}
|
|
205
217
|
*/
|
|
206
218
|
runGoal(goal: import("../shared.js").GoalRequest, options?: object): Promise<import("../shared.js").GoalResult>;
|
|
219
|
+
/**
|
|
220
|
+
* Manually compact the conversation via Claude's native `/compact` slash
|
|
221
|
+
* command. Custom focus instructions are forwarded as the command argument.
|
|
222
|
+
* No assistant message is emitted; the caller renders the confirmation.
|
|
223
|
+
*
|
|
224
|
+
* @param {import("../shared.js").CompactRequest} [request]
|
|
225
|
+
* @param {{ onProgress?: Function }} [options]
|
|
226
|
+
* @returns {Promise<import("../shared.js").CompactResult>}
|
|
227
|
+
*/
|
|
228
|
+
runCompact(request?: import("../shared.js").CompactRequest, { onProgress }?: {
|
|
229
|
+
onProgress?: Function;
|
|
230
|
+
}): Promise<import("../shared.js").CompactResult>;
|
|
207
231
|
close(): Promise<void>;
|
|
208
232
|
}
|
|
209
233
|
import { EventEmitter } from "node:events";
|
|
@@ -32,6 +32,22 @@ function createTurnError(message, extras = {}) {
|
|
|
32
32
|
}
|
|
33
33
|
return error;
|
|
34
34
|
}
|
|
35
|
+
/** Per-turn usage summed from streamed assistant messages (one entry per API response). */
|
|
36
|
+
function sumStreamedUsage(usageByMessageId) {
|
|
37
|
+
if (!usageByMessageId.size) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const total = {};
|
|
41
|
+
for (const usage of usageByMessageId.values()) {
|
|
42
|
+
for (const key of ["input_tokens", "cache_creation_input_tokens", "cache_read_input_tokens", "output_tokens"]) {
|
|
43
|
+
const value = Number(usage?.[key]);
|
|
44
|
+
if (Number.isFinite(value)) {
|
|
45
|
+
total[key] = (total[key] || 0) + value;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return total;
|
|
50
|
+
}
|
|
35
51
|
function normalizeClaudeBackend(backend) {
|
|
36
52
|
const normalized = String(backend || "").trim().toLowerCase();
|
|
37
53
|
if (normalized === "claude-code") {
|
|
@@ -214,9 +230,10 @@ function sanitizeSummary(value, maxLen = 180) {
|
|
|
214
230
|
export class ClaudeAgentSdkSession extends EventEmitter {
|
|
215
231
|
// Capability advertised via getSnapshot().capabilities so worker proxies
|
|
216
232
|
// can short-circuit runGoal without an IPC round trip. Claude exposes
|
|
217
|
-
// native `/goal` slash
|
|
233
|
+
// native `/goal` and `/compact` slash commands, so both are true.
|
|
218
234
|
static capabilities = Object.freeze({
|
|
219
235
|
goal: true,
|
|
236
|
+
compact: true,
|
|
220
237
|
media: PROVIDER_MEDIA_CAPABILITIES[CLAUDE_PROVIDER_VARIANT],
|
|
221
238
|
});
|
|
222
239
|
getCapabilities() {
|
|
@@ -720,6 +737,16 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
720
737
|
this.updateSessionInfo(message.session_id || message.sessionId);
|
|
721
738
|
return;
|
|
722
739
|
}
|
|
740
|
+
if (message.subtype === "compact_boundary") {
|
|
741
|
+
currentTurn.compactMetadata =
|
|
742
|
+
message.compact_metadata && typeof message.compact_metadata === "object"
|
|
743
|
+
? { ...message.compact_metadata }
|
|
744
|
+
: {};
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
if (message.subtype === "status" && message.compact_result === "failed") {
|
|
748
|
+
currentTurn.compactError = normalizeText(message.compact_error) || "compaction failed";
|
|
749
|
+
}
|
|
723
750
|
if (message.subtype === "status" && message.status === "compacting") {
|
|
724
751
|
await this.emitWorkingStatus({
|
|
725
752
|
phase: "context_compaction",
|
|
@@ -775,6 +802,9 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
775
802
|
}
|
|
776
803
|
case "assistant": {
|
|
777
804
|
this.updateSessionInfo(message.session_id || message.sessionId);
|
|
805
|
+
if (message.message?.id && message.message.usage) {
|
|
806
|
+
currentTurn.usageByMessageId.set(message.message.id, message.message.usage);
|
|
807
|
+
}
|
|
778
808
|
for (const block of contentBlocks(message.message)) {
|
|
779
809
|
if (block?.type === "tool_use") {
|
|
780
810
|
noteToolStarted(currentTurn, block.id, block.name, block.input);
|
|
@@ -784,8 +814,11 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
784
814
|
if (!text) {
|
|
785
815
|
return;
|
|
786
816
|
}
|
|
787
|
-
currentTurn.emittedAssistantMessage = true;
|
|
788
817
|
currentTurn.fullText = text;
|
|
818
|
+
if (currentTurn.suppressReply) {
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
currentTurn.emittedAssistantMessage = true;
|
|
789
822
|
await this.emitWorkingStatus({
|
|
790
823
|
phase: "message_aggregation",
|
|
791
824
|
reply_in_progress: true,
|
|
@@ -843,7 +876,7 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
843
876
|
}
|
|
844
877
|
return true;
|
|
845
878
|
}
|
|
846
|
-
async runTurn(promptText, { useInitialImages = false, media: mediaInput, contextFiles, onProgress = null, jsonSchema = null } = {}) {
|
|
879
|
+
async runTurn(promptText, { useInitialImages = false, media: mediaInput, contextFiles, onProgress = null, jsonSchema = null, suppressReply = false } = {}) {
|
|
847
880
|
if (this.closeRequested) {
|
|
848
881
|
throw this.createSessionClosedError();
|
|
849
882
|
}
|
|
@@ -869,16 +902,22 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
869
902
|
if (!sdkModule || typeof sdkModule.query !== "function") {
|
|
870
903
|
throw new Error("Claude Agent SDK is unavailable");
|
|
871
904
|
}
|
|
872
|
-
|
|
905
|
+
if (!suppressReply) {
|
|
906
|
+
this.history.push({ role: "user", content: promptText });
|
|
907
|
+
}
|
|
873
908
|
const abortController = new AbortController();
|
|
874
909
|
const currentTurn = {
|
|
875
910
|
abortController,
|
|
911
|
+
suppressReply,
|
|
912
|
+
compactMetadata: null,
|
|
913
|
+
compactError: "",
|
|
876
914
|
emittedAssistantMessage: false,
|
|
877
915
|
fullText: "",
|
|
878
916
|
items: [],
|
|
879
917
|
query: null,
|
|
880
918
|
resultMessage: null,
|
|
881
919
|
terminalWorkingStatusEmitted: false,
|
|
920
|
+
usageByMessageId: new Map(),
|
|
882
921
|
};
|
|
883
922
|
this.currentTurn = currentTurn;
|
|
884
923
|
this.markTurnStartedStatus();
|
|
@@ -934,6 +973,8 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
934
973
|
permissionDenials: Array.isArray(resultMessage.permission_denials)
|
|
935
974
|
? [...resultMessage.permission_denials]
|
|
936
975
|
: [],
|
|
976
|
+
// A failed turn still spent tokens.
|
|
977
|
+
usage: resultMessage.usage ? { ...resultMessage.usage } : null,
|
|
937
978
|
});
|
|
938
979
|
}
|
|
939
980
|
const structuredOutput = jsonSchema && resultMessage.structured_output && typeof resultMessage.structured_output === "object"
|
|
@@ -944,10 +985,10 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
944
985
|
: normalizeText(resultMessage.result) ||
|
|
945
986
|
currentTurn.fullText ||
|
|
946
987
|
extractAssistantText(currentTurn.items.find((item) => item?.type === "assistant")?.message);
|
|
947
|
-
if (!currentTurn.emittedAssistantMessage && responseText) {
|
|
988
|
+
if (!suppressReply && !currentTurn.emittedAssistantMessage && responseText) {
|
|
948
989
|
await this.emitAssistantMessage(responseText);
|
|
949
990
|
}
|
|
950
|
-
if (responseText) {
|
|
991
|
+
if (!suppressReply && responseText) {
|
|
951
992
|
this.history.push({ role: "assistant", content: responseText });
|
|
952
993
|
}
|
|
953
994
|
this.manualResumeReady = Boolean(this.sessionId);
|
|
@@ -971,9 +1012,14 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
971
1012
|
modelUsage: resultMessage.modelUsage ? { ...resultMessage.modelUsage } : undefined,
|
|
972
1013
|
structuredOutput: structuredOutput !== null ? structuredOutput : undefined,
|
|
973
1014
|
},
|
|
1015
|
+
compactMetadata: currentTurn.compactMetadata || undefined,
|
|
1016
|
+
compactError: currentTurn.compactError || undefined,
|
|
974
1017
|
};
|
|
975
1018
|
}
|
|
976
1019
|
catch (error) {
|
|
1020
|
+
// A failed turn still spent tokens; an interrupted query ends without a
|
|
1021
|
+
// result, so fall back to the usage streamed so far.
|
|
1022
|
+
const usage = error?.usage ?? sumStreamedUsage(currentTurn.usageByMessageId);
|
|
977
1023
|
if (error?.reason === "turn_timeout") {
|
|
978
1024
|
await this.interruptCurrentTurn();
|
|
979
1025
|
}
|
|
@@ -985,9 +1031,12 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
985
1031
|
}, onProgress);
|
|
986
1032
|
}
|
|
987
1033
|
if (this.closeRequested && error?.reason !== "session_closed") {
|
|
988
|
-
throw this.createSessionClosedError();
|
|
1034
|
+
throw Object.assign(this.createSessionClosedError(), { usage });
|
|
989
1035
|
}
|
|
990
1036
|
this.maybeEmitAuthRequired(error?.message || "", error?.message || "");
|
|
1037
|
+
if (error && typeof error === "object") {
|
|
1038
|
+
error.usage = usage;
|
|
1039
|
+
}
|
|
991
1040
|
throw error;
|
|
992
1041
|
}
|
|
993
1042
|
finally {
|
|
@@ -1113,6 +1162,46 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
1113
1162
|
},
|
|
1114
1163
|
};
|
|
1115
1164
|
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Manually compact the conversation via Claude's native `/compact` slash
|
|
1167
|
+
* command. Custom focus instructions are forwarded as the command argument.
|
|
1168
|
+
* No assistant message is emitted; the caller renders the confirmation.
|
|
1169
|
+
*
|
|
1170
|
+
* @param {import("../shared.js").CompactRequest} [request]
|
|
1171
|
+
* @param {{ onProgress?: Function }} [options]
|
|
1172
|
+
* @returns {Promise<import("../shared.js").CompactResult>}
|
|
1173
|
+
*/
|
|
1174
|
+
async runCompact(request = {}, { onProgress = null } = {}) {
|
|
1175
|
+
const instructions = normalizeText(request?.instructions).trim();
|
|
1176
|
+
if (!this.sessionId || this.pendingHistorySeed) {
|
|
1177
|
+
return { compact: { status: "noop", instructionsApplied: false }, usage: null, metadata: {} };
|
|
1178
|
+
}
|
|
1179
|
+
const turnResult = await this.runTurn(instructions ? `/compact ${instructions}` : "/compact", {
|
|
1180
|
+
onProgress,
|
|
1181
|
+
suppressReply: true,
|
|
1182
|
+
});
|
|
1183
|
+
if (/not enough messages/i.test(turnResult.compactError || "")) {
|
|
1184
|
+
return { compact: { status: "noop", instructionsApplied: false }, usage: turnResult.usage, metadata: turnResult.metadata };
|
|
1185
|
+
}
|
|
1186
|
+
if (turnResult.compactError) {
|
|
1187
|
+
throw createTurnError(`Claude compaction failed: ${turnResult.compactError}`, {
|
|
1188
|
+
reason: "compact_failed",
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
const boundary = turnResult.compactMetadata;
|
|
1192
|
+
const preTokens = Number(boundary?.pre_tokens);
|
|
1193
|
+
const postTokens = Number(boundary?.post_tokens);
|
|
1194
|
+
return {
|
|
1195
|
+
compact: {
|
|
1196
|
+
status: boundary ? "compacted" : "noop",
|
|
1197
|
+
instructionsApplied: Boolean(boundary && instructions),
|
|
1198
|
+
preTokens: Number.isFinite(preTokens) ? preTokens : undefined,
|
|
1199
|
+
postTokens: Number.isFinite(postTokens) ? postTokens : undefined,
|
|
1200
|
+
},
|
|
1201
|
+
usage: turnResult.usage,
|
|
1202
|
+
metadata: turnResult.metadata,
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1116
1205
|
async close() {
|
|
1117
1206
|
if (this.closed) {
|
|
1118
1207
|
return;
|
|
@@ -31,6 +31,8 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
31
31
|
nativeSessionId: string;
|
|
32
32
|
rateLimits: any;
|
|
33
33
|
tokenUsage: any;
|
|
34
|
+
turnTokenBaseline: number | null;
|
|
35
|
+
turnTokensSeen: boolean;
|
|
34
36
|
currentTurnStatus: any;
|
|
35
37
|
currentTurnActivityAt: number;
|
|
36
38
|
now: any;
|
|
@@ -42,6 +44,14 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
42
44
|
activeAssistantMessageText: string;
|
|
43
45
|
resolve: null;
|
|
44
46
|
reject: null;
|
|
47
|
+
} | {
|
|
48
|
+
turnId: string;
|
|
49
|
+
fullText: string;
|
|
50
|
+
activeAssistantMessageId: string;
|
|
51
|
+
activeAssistantMessageText: string;
|
|
52
|
+
resolve: null;
|
|
53
|
+
reject: null;
|
|
54
|
+
suppressReply: boolean;
|
|
45
55
|
} | {
|
|
46
56
|
turnId: string;
|
|
47
57
|
fullText: string;
|
|
@@ -127,6 +137,15 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
127
137
|
resumeReady?: boolean | undefined;
|
|
128
138
|
}): void;
|
|
129
139
|
applySessionConfigured(params: any): void;
|
|
140
|
+
/** Start counting a turn's tokens from the thread total known right now. */
|
|
141
|
+
beginTurnTokenAccounting(): void;
|
|
142
|
+
/**
|
|
143
|
+
* Thread token usage plus `turnTotalTokens`: tokens spent since the current
|
|
144
|
+
* turn (or goal) started. Codex only reports thread-cumulative totals, and
|
|
145
|
+
* only when a response completes, so a turn that ended before any response
|
|
146
|
+
* did has unknown usage.
|
|
147
|
+
*/
|
|
148
|
+
snapshotTokenUsage(): any;
|
|
130
149
|
resolveContextUsagePercent(): number | undefined;
|
|
131
150
|
/**
|
|
132
151
|
* Whether a turn failure was caused by the provider thread outgrowing the
|
|
@@ -175,6 +194,14 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
175
194
|
activeAssistantMessageText: string;
|
|
176
195
|
resolve: null;
|
|
177
196
|
reject: null;
|
|
197
|
+
} | {
|
|
198
|
+
turnId: string;
|
|
199
|
+
fullText: string;
|
|
200
|
+
activeAssistantMessageId: string;
|
|
201
|
+
activeAssistantMessageText: string;
|
|
202
|
+
resolve: null;
|
|
203
|
+
reject: null;
|
|
204
|
+
suppressReply: boolean;
|
|
178
205
|
} | {
|
|
179
206
|
turnId: string;
|
|
180
207
|
fullText: string;
|
|
@@ -207,6 +234,14 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
207
234
|
activeAssistantMessageText: string;
|
|
208
235
|
resolve: null;
|
|
209
236
|
reject: null;
|
|
237
|
+
} | {
|
|
238
|
+
turnId: string;
|
|
239
|
+
fullText: string;
|
|
240
|
+
activeAssistantMessageId: string;
|
|
241
|
+
activeAssistantMessageText: string;
|
|
242
|
+
resolve: null;
|
|
243
|
+
reject: null;
|
|
244
|
+
suppressReply: boolean;
|
|
210
245
|
} | {
|
|
211
246
|
turnId: string;
|
|
212
247
|
fullText: string;
|
|
@@ -244,6 +279,15 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
244
279
|
useInitialImages?: boolean | undefined;
|
|
245
280
|
jsonSchema?: null | undefined;
|
|
246
281
|
}): any;
|
|
282
|
+
/**
|
|
283
|
+
* Manually compact the thread via `thread/compact/start`. The app-server
|
|
284
|
+
* runs compaction as a regular turn, so completion is the usual
|
|
285
|
+
* `turn/completed`. The protocol takes no focus instructions.
|
|
286
|
+
*
|
|
287
|
+
* @param {import("../shared.js").CompactRequest} [request]
|
|
288
|
+
* @returns {Promise<import("../shared.js").CompactResult>}
|
|
289
|
+
*/
|
|
290
|
+
runCompact(request?: import("../shared.js").CompactRequest): Promise<import("../shared.js").CompactResult>;
|
|
247
291
|
/**
|
|
248
292
|
* Normalize a goal payload from a codex `thread/goal/*` notification into
|
|
249
293
|
* the cross-layer {@link GoalState} shape.
|