portable-agent-layer 0.95.0 → 0.95.1
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/package.json
CHANGED
|
@@ -10,7 +10,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
11
|
import { resolve } from "node:path";
|
|
12
12
|
import { costOfUsage } from "../../hooks/lib/models";
|
|
13
|
-
import { cacheWritesOf, type TranscriptUsage } from "./transcript-usage";
|
|
13
|
+
import { cacheWritesOf, finalPerMessage, type TranscriptUsage } from "./transcript-usage";
|
|
14
14
|
|
|
15
15
|
export interface SessionUsage {
|
|
16
16
|
input: number;
|
|
@@ -94,7 +94,7 @@ interface TranscriptLine {
|
|
|
94
94
|
type?: string;
|
|
95
95
|
timestamp?: string;
|
|
96
96
|
sessionId?: string;
|
|
97
|
-
message?: { model?: string; usage?: TranscriptUsage };
|
|
97
|
+
message?: { id?: string; model?: string; usage?: TranscriptUsage };
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
function emptyUsage(): SessionUsage {
|
|
@@ -113,6 +113,7 @@ function emptyUsage(): SessionUsage {
|
|
|
113
113
|
|
|
114
114
|
export function accumulateUsage(transcript: string, sessionId: string): SessionUsage {
|
|
115
115
|
const total = emptyUsage();
|
|
116
|
+
const assistantTurns: TranscriptLine[] = [];
|
|
116
117
|
let firstTs = "";
|
|
117
118
|
let lastTs = "";
|
|
118
119
|
|
|
@@ -133,7 +134,10 @@ export function accumulateUsage(transcript: string, sessionId: string): SessionU
|
|
|
133
134
|
lastTs = entry.timestamp;
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
if (entry.type
|
|
137
|
+
if (entry.type === "assistant") assistantTurns.push(entry);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
for (const entry of finalPerMessage(assistantTurns, (e) => e.message?.id)) {
|
|
137
141
|
const usage = entry.message?.usage;
|
|
138
142
|
const model = entry.message?.model;
|
|
139
143
|
if (!usage || !model) continue;
|
|
@@ -18,6 +18,20 @@ export interface TranscriptUsage {
|
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Claude Code writes one transcript line per content block of a message, each
|
|
23
|
+
* repeating the message's usage — and an early line can carry the usage while
|
|
24
|
+
* the message was still streaming. A message is billed once, at its last line.
|
|
25
|
+
*/
|
|
26
|
+
export function finalPerMessage<T>(
|
|
27
|
+
calls: Iterable<T>,
|
|
28
|
+
messageIdOf: (call: T) => string | undefined
|
|
29
|
+
): T[] {
|
|
30
|
+
const byMessage = new Map<unknown, T>();
|
|
31
|
+
for (const call of calls) byMessage.set(messageIdOf(call) ?? call, call);
|
|
32
|
+
return [...byMessage.values()];
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
export interface CacheWrites {
|
|
22
36
|
cacheWrite5m: number;
|
|
23
37
|
cacheWrite1h: number;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
8
8
|
import { resolve } from "node:path";
|
|
9
9
|
import { costOfUsage } from "../../hooks/lib/models";
|
|
10
|
-
import { cacheWritesOf, type TranscriptUsage } from "./transcript-usage";
|
|
10
|
+
import { cacheWritesOf, finalPerMessage, type TranscriptUsage } from "./transcript-usage";
|
|
11
11
|
|
|
12
12
|
export interface Bucket {
|
|
13
13
|
input: number;
|
|
@@ -180,7 +180,7 @@ export function usageTally(projectFilter?: string, now: Date = new Date()): Usag
|
|
|
180
180
|
interface TranscriptLine {
|
|
181
181
|
type?: string;
|
|
182
182
|
timestamp?: string;
|
|
183
|
-
message?: { model?: string; usage?: TranscriptUsage };
|
|
183
|
+
message?: { id?: string; model?: string; usage?: TranscriptUsage };
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
/**
|
|
@@ -209,6 +209,7 @@ function transcriptsIn(projPath: string): string[] {
|
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
interface PricedCall {
|
|
212
|
+
messageId?: string;
|
|
212
213
|
ts: string;
|
|
213
214
|
model: string;
|
|
214
215
|
tokens: Tokens;
|
|
@@ -230,6 +231,7 @@ function pricedCallOf(line: string): PricedCall | null {
|
|
|
230
231
|
if (!usage || !model || !ts) return null;
|
|
231
232
|
|
|
232
233
|
return {
|
|
234
|
+
messageId: entry.message?.id,
|
|
233
235
|
ts,
|
|
234
236
|
model,
|
|
235
237
|
tokens: {
|
|
@@ -248,6 +250,7 @@ export function readClaudeCode(
|
|
|
248
250
|
): AgentUsage {
|
|
249
251
|
const tally = usageTally(projectFilter, now);
|
|
250
252
|
if (!existsSync(claudeDir)) return tally.usage;
|
|
253
|
+
const calls: (PricedCall & { project: string })[] = [];
|
|
251
254
|
|
|
252
255
|
const projectDirs = readdirSync(claudeDir, { withFileTypes: true })
|
|
253
256
|
.filter((entry) => entry.isDirectory())
|
|
@@ -269,16 +272,20 @@ export function readClaudeCode(
|
|
|
269
272
|
// Cheap reject before the parse: most lines are not model turns.
|
|
270
273
|
if (!line.includes('"usage"')) continue;
|
|
271
274
|
const call = pricedCallOf(line);
|
|
272
|
-
if (
|
|
273
|
-
tally.record({
|
|
274
|
-
...call,
|
|
275
|
-
project: projName,
|
|
276
|
-
cost: costOfUsage(call.model, call.tokens),
|
|
277
|
-
});
|
|
275
|
+
if (call) calls.push({ ...call, project: projName });
|
|
278
276
|
}
|
|
279
277
|
}
|
|
280
278
|
}
|
|
281
279
|
|
|
280
|
+
for (const call of finalPerMessage(calls, (c) => c.messageId)) {
|
|
281
|
+
tally.record({
|
|
282
|
+
ts: call.ts,
|
|
283
|
+
model: call.model,
|
|
284
|
+
tokens: call.tokens,
|
|
285
|
+
project: call.project,
|
|
286
|
+
cost: costOfUsage(call.model, call.tokens),
|
|
287
|
+
});
|
|
288
|
+
}
|
|
282
289
|
return tally.usage;
|
|
283
290
|
}
|
|
284
291
|
|