@love-moon/ai-sdk 0.15.2 → 0.16.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
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @love-moon/ai-sdk
|
|
2
2
|
|
|
3
|
+
## 0.16.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5c81d06: Claude turns that wait on background subagents now report their full token
|
|
8
|
+
usage. Such a turn emits one result per segment and the task card used to show
|
|
9
|
+
only the last segment; it now sums the result's `modelUsage`, which covers
|
|
10
|
+
every segment, every subagent and small side calls. `conductor serve-ai`'s
|
|
11
|
+
`prompt_tokens` / `completion_tokens` come from the same usage, so they now
|
|
12
|
+
include those too. A turn cut off after an earlier segment keeps that
|
|
13
|
+
segment's usage.
|
|
14
|
+
- b69d665: Codex turns that spawn sub-agents now include the sub-agents' tokens in the
|
|
15
|
+
turn usage. Their `thread/tokenUsage/updated` notifications carry the
|
|
16
|
+
sub-agent's own thread id and were dropped with its other events. A
|
|
17
|
+
sub-agent's replayed total is not counted, and whatever it spends after the
|
|
18
|
+
parent turn ends goes to the next turn.
|
|
19
|
+
|
|
3
20
|
## 0.15.2
|
|
4
21
|
|
|
5
22
|
## 0.15.1
|
|
@@ -32,12 +32,15 @@ function createTurnError(message, extras = {}) {
|
|
|
32
32
|
}
|
|
33
33
|
return error;
|
|
34
34
|
}
|
|
35
|
-
/**
|
|
36
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Per-turn usage summed from streamed assistant messages (one entry per API
|
|
37
|
+
* response), on top of `base` (what an earlier result of the turn covered).
|
|
38
|
+
*/
|
|
39
|
+
function sumStreamedUsage(usageByMessageId, base = null) {
|
|
37
40
|
if (!usageByMessageId.size) {
|
|
38
|
-
return
|
|
41
|
+
return base;
|
|
39
42
|
}
|
|
40
|
-
const total = {};
|
|
43
|
+
const total = { ...base };
|
|
41
44
|
for (const usage of usageByMessageId.values()) {
|
|
42
45
|
for (const key of ["input_tokens", "cache_creation_input_tokens", "cache_read_input_tokens", "output_tokens"]) {
|
|
43
46
|
const value = Number(usage?.[key]);
|
|
@@ -48,6 +51,31 @@ function sumStreamedUsage(usageByMessageId) {
|
|
|
48
51
|
}
|
|
49
52
|
return total;
|
|
50
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* A finished turn's usage. A turn that waits on background tasks emits one result per
|
|
56
|
+
* segment, and `usage` covers only the last one; `modelUsage` accumulates every API call
|
|
57
|
+
* the query's process made (all segments and subagents), so prefer its sum.
|
|
58
|
+
*/
|
|
59
|
+
function resultUsage(resultMessage) {
|
|
60
|
+
const total = {};
|
|
61
|
+
for (const entry of Object.values(resultMessage.modelUsage || {})) {
|
|
62
|
+
for (const [from, to] of [
|
|
63
|
+
["inputTokens", "input_tokens"],
|
|
64
|
+
["cacheCreationInputTokens", "cache_creation_input_tokens"],
|
|
65
|
+
["cacheReadInputTokens", "cache_read_input_tokens"],
|
|
66
|
+
["outputTokens", "output_tokens"],
|
|
67
|
+
]) {
|
|
68
|
+
const value = Number(entry?.[from]);
|
|
69
|
+
if (Number.isFinite(value)) {
|
|
70
|
+
total[to] = (total[to] || 0) + value;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (Object.keys(total).length) {
|
|
75
|
+
return total;
|
|
76
|
+
}
|
|
77
|
+
return resultMessage.usage ? { ...resultMessage.usage } : null;
|
|
78
|
+
}
|
|
51
79
|
function normalizeClaudeBackend(backend) {
|
|
52
80
|
const normalized = String(backend || "").trim().toLowerCase();
|
|
53
81
|
if (normalized === "claude-code") {
|
|
@@ -853,6 +881,8 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
853
881
|
this.updateSessionInfo(message.session_id || message.sessionId);
|
|
854
882
|
this.lastResult = message;
|
|
855
883
|
currentTurn.resultMessage = message;
|
|
884
|
+
// Its modelUsage covers everything streamed so far.
|
|
885
|
+
currentTurn.usageByMessageId.clear();
|
|
856
886
|
return;
|
|
857
887
|
default:
|
|
858
888
|
return;
|
|
@@ -982,7 +1012,7 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
982
1012
|
? [...resultMessage.permission_denials]
|
|
983
1013
|
: [],
|
|
984
1014
|
// A failed turn still spent tokens.
|
|
985
|
-
usage: resultMessage
|
|
1015
|
+
usage: resultUsage(resultMessage),
|
|
986
1016
|
});
|
|
987
1017
|
}
|
|
988
1018
|
const structuredOutput = jsonSchema && resultMessage.structured_output && typeof resultMessage.structured_output === "object"
|
|
@@ -1007,7 +1037,7 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
1007
1037
|
}, onProgress);
|
|
1008
1038
|
return {
|
|
1009
1039
|
text: responseText,
|
|
1010
|
-
usage: resultMessage
|
|
1040
|
+
usage: resultUsage(resultMessage),
|
|
1011
1041
|
items: currentTurn.items,
|
|
1012
1042
|
events: [],
|
|
1013
1043
|
provider: this.backend,
|
|
@@ -1027,8 +1057,9 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
1027
1057
|
}
|
|
1028
1058
|
catch (error) {
|
|
1029
1059
|
// A failed turn still spent tokens; an interrupted query ends without a
|
|
1030
|
-
// result, so fall back to the
|
|
1031
|
-
const usage = error?.usage ??
|
|
1060
|
+
// (final) result, so fall back to the last result plus what streamed since.
|
|
1061
|
+
const usage = error?.usage ??
|
|
1062
|
+
sumStreamedUsage(currentTurn.usageByMessageId, currentTurn.resultMessage ? resultUsage(currentTurn.resultMessage) : null);
|
|
1032
1063
|
if (error?.reason === "turn_timeout") {
|
|
1033
1064
|
await this.interruptCurrentTurn();
|
|
1034
1065
|
}
|
|
@@ -46,6 +46,10 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
46
46
|
totalTokens: number;
|
|
47
47
|
} | null;
|
|
48
48
|
turnTokensSeen: boolean;
|
|
49
|
+
subThreadTokenTotals: Map<any, any>;
|
|
50
|
+
subThreadTokenBaselines: Map<any, any>;
|
|
51
|
+
subThreadTokensReported: Map<any, any>;
|
|
52
|
+
subThreadTurnIds: Map<any, any>;
|
|
49
53
|
currentTurnStatus: any;
|
|
50
54
|
currentTurnActivityAt: number;
|
|
51
55
|
now: any;
|
|
@@ -160,6 +164,12 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
160
164
|
applySessionConfigured(params: any): void;
|
|
161
165
|
/** Start counting a turn's tokens from the thread total known right now. */
|
|
162
166
|
beginTurnTokenAccounting(): void;
|
|
167
|
+
/**
|
|
168
|
+
* Track a spawned sub-agent thread's total. Its first update fixes its
|
|
169
|
+
* baseline: `total - last`, or the whole total when it is a replay (a turn id
|
|
170
|
+
* we never saw start), like the main thread's after thread/resume.
|
|
171
|
+
*/
|
|
172
|
+
noteSubThreadTokenUsage(threadId: any, { turnId, tokenUsage }?: {}): void;
|
|
163
173
|
/**
|
|
164
174
|
* Thread token usage plus `turnTotalTokens` (and its `turnInputTokens` /
|
|
165
175
|
* `turnCachedInputTokens` split): tokens spent since the current turn (or
|
|
@@ -88,6 +88,15 @@ function subtractTokenCounts(a, b) {
|
|
|
88
88
|
: {}),
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
|
+
function addTokenCounts(a, b) {
|
|
92
|
+
const split = a.inputTokens !== undefined && b.inputTokens !== undefined;
|
|
93
|
+
return {
|
|
94
|
+
totalTokens: a.totalTokens + b.totalTokens,
|
|
95
|
+
...(split
|
|
96
|
+
? { inputTokens: a.inputTokens + b.inputTokens, cachedInputTokens: a.cachedInputTokens + b.cachedInputTokens }
|
|
97
|
+
: {}),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
91
100
|
/**
|
|
92
101
|
* Adds tokens spent outside the reported attempt (an overflowed first try) to a
|
|
93
102
|
* turn result or error. Only the total grows: the input split, and so the cache
|
|
@@ -276,6 +285,13 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
276
285
|
// its own usage has arrived since; see snapshotTokenUsage().
|
|
277
286
|
this.turnTokenBaseline = null;
|
|
278
287
|
this.turnTokensSeen = false;
|
|
288
|
+
// Spawned sub-agent threads report their own thread totals: the latest one
|
|
289
|
+
// per thread, the part already counted (or not theirs to count), what the
|
|
290
|
+
// last snapshot reported, and each thread's running turn id.
|
|
291
|
+
this.subThreadTokenTotals = new Map();
|
|
292
|
+
this.subThreadTokenBaselines = new Map();
|
|
293
|
+
this.subThreadTokensReported = new Map();
|
|
294
|
+
this.subThreadTurnIds = new Map();
|
|
279
295
|
this.currentTurnStatus = null;
|
|
280
296
|
this.currentTurnActivityAt = 0;
|
|
281
297
|
this.now = typeof options.now === "function" ? options.now : () => Date.now();
|
|
@@ -533,6 +549,28 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
533
549
|
beginTurnTokenAccounting() {
|
|
534
550
|
this.turnTokenBaseline = readTokenCounts(this.tokenUsage?.total);
|
|
535
551
|
this.turnTokensSeen = false;
|
|
552
|
+
// What the previous turn reported is counted; sub-agent spend after that
|
|
553
|
+
// (including between turns) goes to this one.
|
|
554
|
+
for (const [threadId, reported] of this.subThreadTokensReported) {
|
|
555
|
+
this.subThreadTokenBaselines.set(threadId, reported);
|
|
556
|
+
}
|
|
557
|
+
this.subThreadTokensReported = new Map();
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Track a spawned sub-agent thread's total. Its first update fixes its
|
|
561
|
+
* baseline: `total - last`, or the whole total when it is a replay (a turn id
|
|
562
|
+
* we never saw start), like the main thread's after thread/resume.
|
|
563
|
+
*/
|
|
564
|
+
noteSubThreadTokenUsage(threadId, { turnId, tokenUsage } = {}) {
|
|
565
|
+
const total = readTokenCounts(tokenUsage?.total);
|
|
566
|
+
if (!total) {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
if (!this.subThreadTokenBaselines.has(threadId)) {
|
|
570
|
+
const replay = !turnId || turnId !== this.subThreadTurnIds.get(threadId);
|
|
571
|
+
this.subThreadTokenBaselines.set(threadId, replay ? total : subtractTokenCounts(total, readTokenCounts(tokenUsage.last)));
|
|
572
|
+
}
|
|
573
|
+
this.subThreadTokenTotals.set(threadId, total);
|
|
536
574
|
}
|
|
537
575
|
/**
|
|
538
576
|
* Thread token usage plus `turnTotalTokens` (and its `turnInputTokens` /
|
|
@@ -545,9 +583,17 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
545
583
|
if (!this.tokenUsage) {
|
|
546
584
|
return null;
|
|
547
585
|
}
|
|
548
|
-
|
|
586
|
+
let turn = this.turnTokensSeen
|
|
549
587
|
? subtractTokenCounts(readTokenCounts(this.tokenUsage.total), this.turnTokenBaseline)
|
|
550
588
|
: null;
|
|
589
|
+
for (const [threadId, baseline] of this.subThreadTokenBaselines) {
|
|
590
|
+
const latest = this.subThreadTokenTotals.get(threadId);
|
|
591
|
+
const spent = subtractTokenCounts(latest, baseline);
|
|
592
|
+
if (spent) {
|
|
593
|
+
turn = turn ? addTokenCounts(turn, spent) : spent;
|
|
594
|
+
this.subThreadTokensReported.set(threadId, latest);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
551
597
|
return {
|
|
552
598
|
...this.tokenUsage,
|
|
553
599
|
...(turn
|
|
@@ -625,6 +671,10 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
625
671
|
this.tokenUsage = null;
|
|
626
672
|
this.turnTokenBaseline = null;
|
|
627
673
|
this.turnTokensSeen = false;
|
|
674
|
+
this.subThreadTokenTotals = new Map();
|
|
675
|
+
this.subThreadTokenBaselines = new Map();
|
|
676
|
+
this.subThreadTokensReported = new Map();
|
|
677
|
+
this.subThreadTurnIds = new Map();
|
|
628
678
|
this.booted = false;
|
|
629
679
|
this.bootPromise = null;
|
|
630
680
|
}
|
|
@@ -914,6 +964,12 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
914
964
|
const threadId = typeof params?.threadId === "string" ? params.threadId.trim() : "";
|
|
915
965
|
if (threadId && this.sessionId && threadId !== this.sessionId) {
|
|
916
966
|
this.touchTurnActivity();
|
|
967
|
+
if (method === "turn/started" && typeof params?.turn?.id === "string") {
|
|
968
|
+
this.subThreadTurnIds.set(threadId, params.turn.id);
|
|
969
|
+
}
|
|
970
|
+
else if (method === "thread/tokenUsage/updated") {
|
|
971
|
+
this.noteSubThreadTokenUsage(threadId, params);
|
|
972
|
+
}
|
|
917
973
|
return;
|
|
918
974
|
}
|
|
919
975
|
switch (method) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@love-moon/ai-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/lovemoon-ai/conductor.git"
|
|
@@ -53,12 +53,12 @@
|
|
|
53
53
|
"zod": "^4.1.5"
|
|
54
54
|
},
|
|
55
55
|
"optionalDependencies": {
|
|
56
|
-
"@love-moon/chat-web": "^0.
|
|
56
|
+
"@love-moon/chat-web": "^0.16.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/node": "^22.10.2",
|
|
60
60
|
"tsx": "^4.20.6",
|
|
61
61
|
"typescript": "^5.6.3"
|
|
62
62
|
},
|
|
63
|
-
"gitCommitId": "
|
|
63
|
+
"gitCommitId": "77239b9"
|
|
64
64
|
}
|