mossbear 0.1.0-alpha.57 → 0.1.0-alpha.58
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/dist/usage/adapters.d.ts +11 -0
- package/dist/usage/adapters.d.ts.map +1 -0
- package/dist/usage/adapters.js +31 -0
- package/dist/usage/adapters.js.map +1 -0
- package/dist/usage/claude.d.ts +38 -0
- package/dist/usage/claude.d.ts.map +1 -0
- package/dist/usage/claude.js +497 -0
- package/dist/usage/claude.js.map +1 -0
- package/dist/usage/codex.d.ts +4 -0
- package/dist/usage/codex.d.ts.map +1 -0
- package/dist/usage/codex.js +155 -0
- package/dist/usage/codex.js.map +1 -0
- package/dist/usage/extract.d.ts +4 -97
- package/dist/usage/extract.d.ts.map +1 -1
- package/dist/usage/extract.js +17 -505
- package/dist/usage/extract.js.map +1 -1
- package/dist/usage/roots.d.ts +0 -6
- package/dist/usage/roots.d.ts.map +1 -1
- package/dist/usage/roots.js +11 -17
- package/dist/usage/roots.js.map +1 -1
- package/dist/usage/types.d.ts +52 -0
- package/dist/usage/types.d.ts.map +1 -0
- package/dist/usage/types.js +2 -0
- package/dist/usage/types.js.map +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ExtractedSessionUsage, ResolvedTranscript } from './types.js';
|
|
2
|
+
/** One agent's transcript location, filename convention, and usage parser. */
|
|
3
|
+
export type TranscriptUsageAdapter = {
|
|
4
|
+
id: string;
|
|
5
|
+
roots(env: NodeJS.ProcessEnv): string[];
|
|
6
|
+
matchesPath(path: string): boolean;
|
|
7
|
+
sessionIdFromFilename(filename: string): string | null;
|
|
8
|
+
extract(files: readonly ResolvedTranscript[], cachePath?: string | null): Promise<ExtractedSessionUsage>;
|
|
9
|
+
};
|
|
10
|
+
export declare const TRANSCRIPT_USAGE_ADAPTERS: readonly TranscriptUsageAdapter[];
|
|
11
|
+
//# sourceMappingURL=adapters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../../src/usage/adapters.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAE3E,8EAA8E;AAC9E,MAAM,MAAM,sBAAsB,GAAG;IACnC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,EAAE,CAAA;IACvC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IAClC,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IACtD,OAAO,CACL,KAAK,EAAE,SAAS,kBAAkB,EAAE,EACpC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GACxB,OAAO,CAAC,qBAAqB,CAAC,CAAA;CAClC,CAAA;AAID,eAAO,MAAM,yBAAyB,EAAE,SAAS,sBAAsB,EA0BtE,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { basename, extname, join } from 'node:path';
|
|
3
|
+
import { extractClaudeUsage } from './claude.js';
|
|
4
|
+
import { extractCodexUsage } from './codex.js';
|
|
5
|
+
const codexRollout = /^rollout-.*-([0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12})\.jsonl$/;
|
|
6
|
+
export const TRANSCRIPT_USAGE_ADAPTERS = [
|
|
7
|
+
{
|
|
8
|
+
id: 'claude',
|
|
9
|
+
roots: (env) => [
|
|
10
|
+
...(env.CLAUDE_CONFIG_DIR?.trim()
|
|
11
|
+
? [join(env.CLAUDE_CONFIG_DIR.trim(), 'projects')]
|
|
12
|
+
: []),
|
|
13
|
+
join(homedir(), '.claude', 'projects'),
|
|
14
|
+
],
|
|
15
|
+
matchesPath: (path) => extname(path) === '.jsonl' && !basename(path).startsWith('rollout-'),
|
|
16
|
+
sessionIdFromFilename: (filename) => filename.endsWith('.jsonl') && !filename.startsWith('rollout-')
|
|
17
|
+
? basename(filename, '.jsonl')
|
|
18
|
+
: null,
|
|
19
|
+
extract: extractClaudeUsage,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'codex',
|
|
23
|
+
roots: (env) => [
|
|
24
|
+
join(env.CODEX_HOME?.trim() || join(homedir(), '.codex'), 'sessions'),
|
|
25
|
+
],
|
|
26
|
+
matchesPath: (path) => codexRollout.test(basename(path)),
|
|
27
|
+
sessionIdFromFilename: (filename) => codexRollout.exec(filename)?.[1] ?? null,
|
|
28
|
+
extract: extractCodexUsage,
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
//# sourceMappingURL=adapters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapters.js","sourceRoot":"","sources":["../../src/usage/adapters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAgB9C,MAAM,YAAY,GAAG,mEAAmE,CAAA;AAExF,MAAM,CAAC,MAAM,yBAAyB,GAAsC;IAC1E;QACE,EAAE,EAAE,QAAQ;QACZ,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC;YACd,GAAG,CAAC,GAAG,CAAC,iBAAiB,EAAE,IAAI,EAAE;gBAC/B,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAC,IAAI,EAAE,EAAE,UAAU,CAAC,CAAC;gBAClD,CAAC,CAAC,EAAE,CAAC;YACP,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,UAAU,CAAC;SACvC;QACD,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CACpB,OAAO,CAAC,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;QACtE,qBAAqB,EAAE,CAAC,QAAQ,EAAE,EAAE,CAClC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC;YAC7D,CAAC,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;YAC9B,CAAC,CAAC,IAAI;QACV,OAAO,EAAE,kBAAkB;KAC5B;IACD;QACE,EAAE,EAAE,OAAO;QACX,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC;YACd,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,EAAE,UAAU,CAAC;SACtE;QACD,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACxD,qBAAqB,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI;QAC7E,OAAO,EAAE,iBAAiB;KAC3B;CACF,CAAA"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ExtractedSessionUsage, ResolvedTranscript } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Extracts per-session, per-model token usage from Claude Code JSONL
|
|
4
|
+
* transcripts, plus per-skill
|
|
5
|
+
* Tier-1 "load overhead" usage (§13.3): when an assistant message requests the
|
|
6
|
+
* `Skill` tool, the *next* assistant message's usage is the first-load cost of
|
|
7
|
+
* that skill (v1 approximation — see §13.3's caching nuance). Opening a
|
|
8
|
+
* `SKILL.md` no longer counts, and `detectSkillLoadSignal` says why. Reads only
|
|
9
|
+
* `usage`/`model`/`agentSessionId` and tool_use `name`/`input` off assistant
|
|
10
|
+
* messages — never message text.
|
|
11
|
+
*
|
|
12
|
+
* Each message carries its own (non-cumulative) usage, so we sum every
|
|
13
|
+
* assistant message's usage per (session, model). Messages are deduped by
|
|
14
|
+
* their id so re-reading a transcript that grew since the last sync does not
|
|
15
|
+
* double-count lines already summed.
|
|
16
|
+
*
|
|
17
|
+
* **Dedup is scoped to the session, not the whole extraction.** Claude Code
|
|
18
|
+
* writes one record per *content block* of an assistant message, all sharing a
|
|
19
|
+
* `message.id` and an identical `usage` object, so dropping the repeats is
|
|
20
|
+
* load-bearing — one measured session sums 2.5× too high without it. But the
|
|
21
|
+
* set used to be global across files and was tested *before* the session id was
|
|
22
|
+
* resolved, over lexicographically sorted paths: a message id appearing in two
|
|
23
|
+
* transcripts silently dropped the later session's tokens, with filename sort
|
|
24
|
+
* as the tiebreak. Per-run cost is the only dedup semantics a session-scoped
|
|
25
|
+
* extractor can compute — it never sees the sessions it would have to dedup
|
|
26
|
+
* against — so the account rollup is the sum of these per-run totals, and
|
|
27
|
+
* over-counts by any replayed overlap between sessions.
|
|
28
|
+
*
|
|
29
|
+
* **Given a cache path, only bytes appended since the last call are read.** The
|
|
30
|
+
* fold is a pure left fold over append-only lines, so resuming from a byte
|
|
31
|
+
* offset with the accumulated state produces the totals a cold read would —
|
|
32
|
+
* which matters because `ingestSessionUsage` replaces per `(sessionId, model, source)`
|
|
33
|
+
* with full-session totals, so the totals must still be complete, they just
|
|
34
|
+
* need not be recomputed from the whole file. Every failure mode here degrades
|
|
35
|
+
* to a re-parse rather than a wrong number.
|
|
36
|
+
*/
|
|
37
|
+
export declare function extractClaudeUsage(files: readonly ResolvedTranscript[], cachePath?: string | null): Promise<ExtractedSessionUsage>;
|
|
38
|
+
//# sourceMappingURL=claude.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/usage/claude.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAmM3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,SAAS,kBAAkB,EAAE,EACpC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GACxB,OAAO,CAAC,qBAAqB,CAAC,CAGhC"}
|
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { open, readFile, stat } from 'node:fs/promises';
|
|
3
|
+
import { basename, extname } from 'node:path';
|
|
4
|
+
import { inferProviderFromModel } from './providers.js';
|
|
5
|
+
import { emptyTranscriptUsageCache, readTranscriptUsageCache, touchCacheOrder, writeTranscriptUsageCache, } from './usage-cache.js';
|
|
6
|
+
function asRecord(value) {
|
|
7
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Detects a skill-load signal in one assistant message's content blocks:
|
|
14
|
+
* an explicit `Skill` tool_use,
|
|
15
|
+
* whose `input.skill` names the skill. Only the first signal in a message is
|
|
16
|
+
* used — a message that requests two skill loads at once is a rare edge case
|
|
17
|
+
* not worth the double-counting risk of attributing the next message's usage to
|
|
18
|
+
* both.
|
|
19
|
+
*
|
|
20
|
+
* **A `SKILL.md` in a tool call's path used to count too, and must not.** It
|
|
21
|
+
* matched on basename alone across *any* tool carrying a path — `Edit` and
|
|
22
|
+
* `Write` included — so writing `/repo/docs/SKILL.md` recorded a load of a
|
|
23
|
+
* skill named `docs`, and reading a real skill's file to edit it recorded a
|
|
24
|
+
* load of that skill. The second is the defect `skill-load-vs-file-edit` fixed
|
|
25
|
+
* on the dashboard side, and it reached grading from here as well as from the
|
|
26
|
+
* hook: `resolveSessionSkillContext` unioned these rows' `skill_name` into the
|
|
27
|
+
* identities a run is graded against. **It no longer does**
|
|
28
|
+
* (`skill-load-source-drift`) — grading reads `skill_load` actions only,
|
|
29
|
+
* because this producer never inspects a `Skill` call's result and so cannot
|
|
30
|
+
* tell an invocation from a permission denial. These rows are cost attribution
|
|
31
|
+
* now, and must not be read back into grading.
|
|
32
|
+
* Restricting to read-shaped tools would not have been enough —
|
|
33
|
+
* opening a skill's file is what editing it starts with. Only an invocation
|
|
34
|
+
* says the instructions were taken as instructions.
|
|
35
|
+
*
|
|
36
|
+
* The cost this pays, deliberately: tokens spent by an agent that followed a
|
|
37
|
+
* `SKILL.md` it opened directly are no longer attributed to that skill. A
|
|
38
|
+
* missing attribution understates a skill's cost; a fabricated one both
|
|
39
|
+
* overstates it and grades a session against a rubric it never invoked.
|
|
40
|
+
*
|
|
41
|
+
* The name is returned exactly as the agent asked for it, so it is *usually*
|
|
42
|
+
* but not always a bare directory segment — `plugin:skill` and `apps/web:deploy`
|
|
43
|
+
* are valid, which `hook-logger.ts:46-52` already accounts for on the other
|
|
44
|
+
* producer. The path-derived branch used to normalize those to a directory
|
|
45
|
+
* name; nothing does now, and the server derives the skill name from the
|
|
46
|
+
* origin as it arrives.
|
|
47
|
+
*/
|
|
48
|
+
function detectSkillLoadSignal(message) {
|
|
49
|
+
const content = message.content;
|
|
50
|
+
if (!Array.isArray(content)) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
for (const block of content) {
|
|
54
|
+
const blockRecord = asRecord(block);
|
|
55
|
+
if (!blockRecord || blockRecord.type !== 'tool_use') {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (blockRecord.name !== 'Skill') {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const input = asRecord(blockRecord.input);
|
|
62
|
+
const skillName = typeof input?.skill === 'string' ? input.skill : null;
|
|
63
|
+
if (skillName) {
|
|
64
|
+
return skillName;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
function toNonNegativeInt(value) {
|
|
70
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0
|
|
71
|
+
? Math.floor(value)
|
|
72
|
+
: 0;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Reads one transcript's records, degrading to none when the file cannot be
|
|
76
|
+
* read at all.
|
|
77
|
+
*
|
|
78
|
+
* Usage is *optional* data riding a sync whose real job is uploading actions,
|
|
79
|
+
* and since discovery went automatic this runs on every default sync rather
|
|
80
|
+
* than only under `--transcripts-dir`. An unreadable transcript — permissions,
|
|
81
|
+
* or the file being cleaned up between directory discovery and this read —
|
|
82
|
+
* must therefore not reject: an uncaught rejection here aborts the command
|
|
83
|
+
* before `postSyncBatch`, so a transient problem with a token count would take
|
|
84
|
+
* the session's perfectly valid pending actions down with it. Same contract as
|
|
85
|
+
* the cursor file: a failure costs a re-read on the next sync, never a wrong
|
|
86
|
+
* number, because `ingestSessionUsage` replaces with whatever full-session totals
|
|
87
|
+
* the next successful extraction sends.
|
|
88
|
+
*/
|
|
89
|
+
async function readJsonlRecords(path) {
|
|
90
|
+
const raw = await readFile(path, 'utf8').catch(() => '');
|
|
91
|
+
return parseJsonlText(raw);
|
|
92
|
+
}
|
|
93
|
+
function parseJsonlText(raw) {
|
|
94
|
+
return raw
|
|
95
|
+
.split('\n')
|
|
96
|
+
.map((line) => line.trim())
|
|
97
|
+
.filter(Boolean)
|
|
98
|
+
.flatMap((line) => {
|
|
99
|
+
try {
|
|
100
|
+
return [JSON.parse(line)];
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Read `[start, end)` and return the records in it, plus how many bytes were
|
|
109
|
+
* actually consumed.
|
|
110
|
+
*
|
|
111
|
+
* Two properties the cursor depends on:
|
|
112
|
+
*
|
|
113
|
+
* - **The read is bounded by `end`, not by EOF.** `end` comes from a `stat`
|
|
114
|
+
* taken before the read, so anything the agent appends while we are reading
|
|
115
|
+
* is simply left for the next sync instead of being consumed by an offset
|
|
116
|
+
* that does not describe it.
|
|
117
|
+
* - **Consumption stops at the last newline.** The Stop hook reads a transcript
|
|
118
|
+
* the agent is actively appending to, so the tail is routinely a half-written
|
|
119
|
+
* line. The scan is done on the `Buffer`, not the decoded string, because a
|
|
120
|
+
* string index counts UTF-16 code units while the offset must be bytes — a
|
|
121
|
+
* single non-ASCII character anywhere in the slice would desynchronise them.
|
|
122
|
+
*/
|
|
123
|
+
async function readRecordRange(path, start, end) {
|
|
124
|
+
if (end <= start) {
|
|
125
|
+
return { records: [], consumed: start, ok: true };
|
|
126
|
+
}
|
|
127
|
+
const handle = await open(path, 'r').catch(() => null);
|
|
128
|
+
if (!handle) {
|
|
129
|
+
return { records: [], consumed: start, ok: false };
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const length = end - start;
|
|
133
|
+
const buffer = Buffer.alloc(length);
|
|
134
|
+
const { bytesRead } = await handle.read(buffer, 0, length, start);
|
|
135
|
+
const slice = buffer.subarray(0, bytesRead);
|
|
136
|
+
const lastNewline = slice.lastIndexOf(0x0a);
|
|
137
|
+
if (lastNewline < 0) {
|
|
138
|
+
return { records: [], consumed: start, ok: true };
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
records: parseJsonlText(slice.subarray(0, lastNewline + 1).toString('utf8')),
|
|
142
|
+
consumed: start + lastNewline + 1,
|
|
143
|
+
ok: true,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return { records: [], consumed: start, ok: false };
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
await handle.close().catch(() => { });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/** How many bytes before the cursor identify the consumed prefix. */
|
|
154
|
+
const TAIL_FINGERPRINT_BYTES = 64;
|
|
155
|
+
/**
|
|
156
|
+
* Fingerprint the last bytes of the consumed prefix, or `null` if they cannot
|
|
157
|
+
* be read — which must not be mistaken for "no prefix", since that is exactly
|
|
158
|
+
* the empty-file fingerprint a fresh entry carries.
|
|
159
|
+
*/
|
|
160
|
+
async function readTailFingerprint(path, offset) {
|
|
161
|
+
const length = Math.min(TAIL_FINGERPRINT_BYTES, offset);
|
|
162
|
+
if (length === 0) {
|
|
163
|
+
return createHash('sha256').update('').digest('hex').slice(0, 16);
|
|
164
|
+
}
|
|
165
|
+
const handle = await open(path, 'r').catch(() => null);
|
|
166
|
+
if (!handle) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
const buffer = Buffer.alloc(length);
|
|
171
|
+
const { bytesRead } = await handle.read(buffer, 0, length, offset - length);
|
|
172
|
+
if (bytesRead < length) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
return createHash('sha256').update(buffer).digest('hex').slice(0, 16);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
await handle.close().catch(() => { });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Extracts per-session, per-model token usage from Claude Code JSONL
|
|
186
|
+
* transcripts, plus per-skill
|
|
187
|
+
* Tier-1 "load overhead" usage (§13.3): when an assistant message requests the
|
|
188
|
+
* `Skill` tool, the *next* assistant message's usage is the first-load cost of
|
|
189
|
+
* that skill (v1 approximation — see §13.3's caching nuance). Opening a
|
|
190
|
+
* `SKILL.md` no longer counts, and `detectSkillLoadSignal` says why. Reads only
|
|
191
|
+
* `usage`/`model`/`agentSessionId` and tool_use `name`/`input` off assistant
|
|
192
|
+
* messages — never message text.
|
|
193
|
+
*
|
|
194
|
+
* Each message carries its own (non-cumulative) usage, so we sum every
|
|
195
|
+
* assistant message's usage per (session, model). Messages are deduped by
|
|
196
|
+
* their id so re-reading a transcript that grew since the last sync does not
|
|
197
|
+
* double-count lines already summed.
|
|
198
|
+
*
|
|
199
|
+
* **Dedup is scoped to the session, not the whole extraction.** Claude Code
|
|
200
|
+
* writes one record per *content block* of an assistant message, all sharing a
|
|
201
|
+
* `message.id` and an identical `usage` object, so dropping the repeats is
|
|
202
|
+
* load-bearing — one measured session sums 2.5× too high without it. But the
|
|
203
|
+
* set used to be global across files and was tested *before* the session id was
|
|
204
|
+
* resolved, over lexicographically sorted paths: a message id appearing in two
|
|
205
|
+
* transcripts silently dropped the later session's tokens, with filename sort
|
|
206
|
+
* as the tiebreak. Per-run cost is the only dedup semantics a session-scoped
|
|
207
|
+
* extractor can compute — it never sees the sessions it would have to dedup
|
|
208
|
+
* against — so the account rollup is the sum of these per-run totals, and
|
|
209
|
+
* over-counts by any replayed overlap between sessions.
|
|
210
|
+
*
|
|
211
|
+
* **Given a cache path, only bytes appended since the last call are read.** The
|
|
212
|
+
* fold is a pure left fold over append-only lines, so resuming from a byte
|
|
213
|
+
* offset with the accumulated state produces the totals a cold read would —
|
|
214
|
+
* which matters because `ingestSessionUsage` replaces per `(sessionId, model, source)`
|
|
215
|
+
* with full-session totals, so the totals must still be complete, they just
|
|
216
|
+
* need not be recomputed from the whole file. Every failure mode here degrades
|
|
217
|
+
* to a re-parse rather than a wrong number.
|
|
218
|
+
*/
|
|
219
|
+
export async function extractClaudeUsage(files, cachePath) {
|
|
220
|
+
if (!cachePath)
|
|
221
|
+
return foldColdAcrossFiles(files);
|
|
222
|
+
return extractIncrementally(files, cachePath);
|
|
223
|
+
}
|
|
224
|
+
const emptyFoldState = () => ({
|
|
225
|
+
sessionUsage: new Map(),
|
|
226
|
+
sessionSkillUsage: new Map(),
|
|
227
|
+
pendingSkillLoads: new Map(),
|
|
228
|
+
seenMessageKeys: new Set(),
|
|
229
|
+
});
|
|
230
|
+
/** Fold one transcript's records into `state`, in file order. */
|
|
231
|
+
function foldRecords(records, fallbackSessionId, state) {
|
|
232
|
+
for (const recordValue of records) {
|
|
233
|
+
const record = asRecord(recordValue);
|
|
234
|
+
if (!record || record.type !== 'assistant') {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
const message = asRecord(record.message);
|
|
238
|
+
if (!message) {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
const usage = asRecord(message.usage);
|
|
242
|
+
if (!usage) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const model = typeof message.model === 'string' ? message.model : null;
|
|
246
|
+
if (!model) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
// Resolve the session *before* the dedup check — the key is scoped to it,
|
|
250
|
+
// and testing first is what let one session's records suppress another's.
|
|
251
|
+
// `record.sessionId` is the harness's own spelling, in a transcript file
|
|
252
|
+
// Mossbear does not write — renaming this key would read a field Claude Code
|
|
253
|
+
// never emits and silently fall back to the file-level hint for every
|
|
254
|
+
// record. Our name for the value is `agentSessionId`; the wire key is not
|
|
255
|
+
// ours to rename.
|
|
256
|
+
const agentSessionId = typeof record.sessionId === 'string' && record.sessionId.length > 0
|
|
257
|
+
? record.sessionId
|
|
258
|
+
: fallbackSessionId;
|
|
259
|
+
// The `m:`/`u:` prefixes keep a record uuid from colliding with a message
|
|
260
|
+
// id when a record carries no `message.id` to dedup on.
|
|
261
|
+
const messageKey = typeof message.id === 'string'
|
|
262
|
+
? `${agentSessionId}|m:${message.id}`
|
|
263
|
+
: typeof record.uuid === 'string'
|
|
264
|
+
? `${agentSessionId}|u:${record.uuid}`
|
|
265
|
+
: null;
|
|
266
|
+
if (messageKey) {
|
|
267
|
+
if (state.seenMessageKeys.has(messageKey)) {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
state.seenMessageKeys.add(messageKey);
|
|
271
|
+
}
|
|
272
|
+
const modelMap = state.sessionUsage.get(agentSessionId) ?? new Map();
|
|
273
|
+
state.sessionUsage.set(agentSessionId, modelMap);
|
|
274
|
+
const existing = modelMap.get(model) ?? {
|
|
275
|
+
model,
|
|
276
|
+
provider: inferProviderFromModel(model),
|
|
277
|
+
inputTokens: 0,
|
|
278
|
+
outputTokens: 0,
|
|
279
|
+
cacheReadTokens: 0,
|
|
280
|
+
cacheWriteTokens: 0,
|
|
281
|
+
};
|
|
282
|
+
modelMap.set(model, {
|
|
283
|
+
...existing,
|
|
284
|
+
inputTokens: existing.inputTokens + toNonNegativeInt(usage.input_tokens),
|
|
285
|
+
outputTokens: existing.outputTokens + toNonNegativeInt(usage.output_tokens),
|
|
286
|
+
cacheReadTokens: (existing.cacheReadTokens ?? 0) + toNonNegativeInt(usage.cache_read_input_tokens),
|
|
287
|
+
cacheWriteTokens: (existing.cacheWriteTokens ?? 0) +
|
|
288
|
+
toNonNegativeInt(usage.cache_creation_input_tokens),
|
|
289
|
+
});
|
|
290
|
+
// Attribute this message's usage to a skill load requested by the
|
|
291
|
+
// *previous* assistant message, if any — this message is the first turn
|
|
292
|
+
// to see that skill's content in context. Must run before detecting a
|
|
293
|
+
// new signal below, so a message that both closes one load and opens
|
|
294
|
+
// another handles both correctly.
|
|
295
|
+
const pendingSkillName = state.pendingSkillLoads.get(agentSessionId);
|
|
296
|
+
if (pendingSkillName) {
|
|
297
|
+
state.pendingSkillLoads.delete(agentSessionId);
|
|
298
|
+
const skillMap = state.sessionSkillUsage.get(agentSessionId) ?? new Map();
|
|
299
|
+
state.sessionSkillUsage.set(agentSessionId, skillMap);
|
|
300
|
+
const skillKey = `${pendingSkillName}::${model}`;
|
|
301
|
+
const existingSkillUsage = skillMap.get(skillKey) ?? {
|
|
302
|
+
skillName: pendingSkillName,
|
|
303
|
+
model,
|
|
304
|
+
inputTokens: 0,
|
|
305
|
+
cacheWriteTokens: 0,
|
|
306
|
+
};
|
|
307
|
+
skillMap.set(skillKey, {
|
|
308
|
+
...existingSkillUsage,
|
|
309
|
+
inputTokens: existingSkillUsage.inputTokens + toNonNegativeInt(usage.input_tokens),
|
|
310
|
+
cacheWriteTokens: (existingSkillUsage.cacheWriteTokens ?? 0) +
|
|
311
|
+
toNonNegativeInt(usage.cache_creation_input_tokens),
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
const skillLoadSignal = detectSkillLoadSignal(message);
|
|
315
|
+
if (skillLoadSignal) {
|
|
316
|
+
state.pendingSkillLoads.set(agentSessionId, skillLoadSignal);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const fallbackSessionIdFor = (file) =>
|
|
321
|
+
// The session this file was resolved for, when the resolver knew it;
|
|
322
|
+
// otherwise the filename convention both extractors already lean on.
|
|
323
|
+
file.agentSessionId ?? basename(file.path, extname(file.path));
|
|
324
|
+
function toExtracted(state) {
|
|
325
|
+
return {
|
|
326
|
+
runUsage: Array.from(state.sessionUsage.entries()).map(([agentSessionId, modelMap]) => ({
|
|
327
|
+
agentSessionId,
|
|
328
|
+
usage: Array.from(modelMap.values()),
|
|
329
|
+
})),
|
|
330
|
+
skillUsage: Array.from(state.sessionSkillUsage.entries()).map(([agentSessionId, skillMap]) => ({
|
|
331
|
+
agentSessionId,
|
|
332
|
+
usage: Array.from(skillMap.values()),
|
|
333
|
+
})),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Read every file in full and fold them into one shared state — the behaviour
|
|
338
|
+
* that predates the cursor, kept as the answer whenever the cache cannot be
|
|
339
|
+
* used (scan mode, no cache path, or the cross-file session overlap below).
|
|
340
|
+
*
|
|
341
|
+
* The shared `seenMessageKeys` is what makes this distinct from folding each
|
|
342
|
+
* file alone: two transcripts carrying the same session's records dedup against
|
|
343
|
+
* each other here.
|
|
344
|
+
*/
|
|
345
|
+
async function foldColdAcrossFiles(files) {
|
|
346
|
+
const state = emptyFoldState();
|
|
347
|
+
for (const file of files) {
|
|
348
|
+
foldRecords(await readJsonlRecords(file.path), fallbackSessionIdFor(file), state);
|
|
349
|
+
}
|
|
350
|
+
return toExtracted(state);
|
|
351
|
+
}
|
|
352
|
+
async function stateToCacheEntry(state, stats, offset, fallbackSessionId, path) {
|
|
353
|
+
const tailHash = await readTailFingerprint(path, offset);
|
|
354
|
+
if (tailHash === null) {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
const sessionIds = new Set([
|
|
358
|
+
...state.sessionUsage.keys(),
|
|
359
|
+
...state.sessionSkillUsage.keys(),
|
|
360
|
+
]);
|
|
361
|
+
return {
|
|
362
|
+
size: stats.size,
|
|
363
|
+
mtimeMs: stats.mtimeMs,
|
|
364
|
+
offset,
|
|
365
|
+
sessions: Array.from(sessionIds).map((agentSessionId) => ({
|
|
366
|
+
agentSessionId,
|
|
367
|
+
usage: Array.from(state.sessionUsage.get(agentSessionId)?.values() ?? []),
|
|
368
|
+
skillUsage: Array.from(state.sessionSkillUsage.get(agentSessionId)?.values() ?? []),
|
|
369
|
+
})),
|
|
370
|
+
pendingSkillLoads: Array.from(state.pendingSkillLoads.entries()).map(([agentSessionId, skillName]) => ({ agentSessionId, skillName })),
|
|
371
|
+
seenMessageKeys: Array.from(state.seenMessageKeys),
|
|
372
|
+
tailHash,
|
|
373
|
+
fallbackSessionId,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
function cacheEntryToState(entry) {
|
|
377
|
+
const state = emptyFoldState();
|
|
378
|
+
for (const session of entry.sessions) {
|
|
379
|
+
if (session.usage.length > 0) {
|
|
380
|
+
state.sessionUsage.set(session.agentSessionId, new Map(session.usage.map((usage) => [usage.model, usage])));
|
|
381
|
+
}
|
|
382
|
+
if (session.skillUsage.length > 0) {
|
|
383
|
+
state.sessionSkillUsage.set(session.agentSessionId, new Map(session.skillUsage.map((u) => [`${u.skillName}::${u.model}`, u])));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
for (const pending of entry.pendingSkillLoads) {
|
|
387
|
+
state.pendingSkillLoads.set(pending.agentSessionId, pending.skillName);
|
|
388
|
+
}
|
|
389
|
+
state.seenMessageKeys = new Set(entry.seenMessageKeys);
|
|
390
|
+
return state;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Fold each transcript independently, resuming from the byte offset the last
|
|
394
|
+
* sync stopped at, and merge the per-file results.
|
|
395
|
+
*
|
|
396
|
+
* **Why per-file folding is safe here, and when it is not.** The cold path
|
|
397
|
+
* dedups across files through one shared key set, so two transcripts carrying
|
|
398
|
+
* the same session's records cannot both count it. Per-file state cannot see
|
|
399
|
+
* that, and a cached file's totals were computed without knowledge of its
|
|
400
|
+
* neighbours — there is no way to subtract the overlap after the fact. So the
|
|
401
|
+
* overlap is *detected* and the whole extraction falls back to the cold fold,
|
|
402
|
+
* which is exactly today's behaviour and today's cost. In the ordinary case
|
|
403
|
+
* every file carries one session that no other file carries, and the fallback
|
|
404
|
+
* never fires. Whether a replayed session can carry `usage` at all is the open
|
|
405
|
+
* question slice 0a exists to measure; this degrades correctly either way
|
|
406
|
+
* rather than betting on the answer.
|
|
407
|
+
*/
|
|
408
|
+
async function extractIncrementally(files, cachePath) {
|
|
409
|
+
const cache = await readTranscriptUsageCache(cachePath);
|
|
410
|
+
const next = {
|
|
411
|
+
...emptyTranscriptUsageCache(),
|
|
412
|
+
order: cache.order,
|
|
413
|
+
entries: { ...cache.entries },
|
|
414
|
+
};
|
|
415
|
+
const folded = [];
|
|
416
|
+
for (const file of files) {
|
|
417
|
+
const stats = await stat(file.path).catch(() => null);
|
|
418
|
+
if (!stats?.isFile()) {
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
const fallbackSessionId = fallbackSessionIdFor(file);
|
|
422
|
+
const entry = cache.entries[file.path];
|
|
423
|
+
// An append leaves the earlier bytes untouched, so the cached fold over
|
|
424
|
+
// them still holds — but only once three things are established: the size
|
|
425
|
+
// did not shrink, the fold was made under the same fallback session, and
|
|
426
|
+
// the consumed prefix is still the same prefix. A larger file is not
|
|
427
|
+
// evidence of an append on its own; the path may have been replaced.
|
|
428
|
+
const candidate = entry &&
|
|
429
|
+
entry.fallbackSessionId === fallbackSessionId &&
|
|
430
|
+
stats.size >= entry.size &&
|
|
431
|
+
stats.size >= entry.offset;
|
|
432
|
+
const reusable = candidate && (await readTailFingerprint(file.path, entry.offset)) === entry.tailHash;
|
|
433
|
+
// Same size *and* mtime means nothing was appended, so there is nothing to
|
|
434
|
+
// read at all.
|
|
435
|
+
const unchanged = reusable && stats.size === entry.size && stats.mtimeMs === entry.mtimeMs;
|
|
436
|
+
let state;
|
|
437
|
+
let offset;
|
|
438
|
+
let readOk = true;
|
|
439
|
+
if (unchanged) {
|
|
440
|
+
state = cacheEntryToState(entry);
|
|
441
|
+
offset = entry.offset;
|
|
442
|
+
}
|
|
443
|
+
else if (reusable) {
|
|
444
|
+
state = cacheEntryToState(entry);
|
|
445
|
+
const read = await readRecordRange(file.path, entry.offset, stats.size);
|
|
446
|
+
foldRecords(read.records, fallbackSessionId, state);
|
|
447
|
+
offset = read.consumed;
|
|
448
|
+
readOk = read.ok;
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
state = emptyFoldState();
|
|
452
|
+
const read = await readRecordRange(file.path, 0, stats.size);
|
|
453
|
+
foldRecords(read.records, fallbackSessionId, state);
|
|
454
|
+
offset = read.consumed;
|
|
455
|
+
readOk = read.ok;
|
|
456
|
+
}
|
|
457
|
+
folded.push(state);
|
|
458
|
+
if (readOk) {
|
|
459
|
+
const written = await stateToCacheEntry(state, stats, offset, fallbackSessionId, file.path);
|
|
460
|
+
if (written) {
|
|
461
|
+
next.entries[file.path] = written;
|
|
462
|
+
next.order = touchCacheOrder(next.order, file.path);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// A failed read leaves the previous entry alone. Recording this `stat` as
|
|
466
|
+
// successfully folded would let the `unchanged` branch trust an empty fold
|
|
467
|
+
// for as long as the file does not change — a permanent silent zero for
|
|
468
|
+
// that session, which is the failure this whole item exists to remove.
|
|
469
|
+
}
|
|
470
|
+
const seenSessions = new Set();
|
|
471
|
+
for (const state of folded) {
|
|
472
|
+
for (const agentSessionId of new Set([
|
|
473
|
+
...state.sessionUsage.keys(),
|
|
474
|
+
...state.sessionSkillUsage.keys(),
|
|
475
|
+
])) {
|
|
476
|
+
if (seenSessions.has(agentSessionId)) {
|
|
477
|
+
// Two transcripts claim one session — see the doc comment. Leave the
|
|
478
|
+
// cache as it was: the per-file entries are still individually valid,
|
|
479
|
+
// they just cannot be summed.
|
|
480
|
+
return foldColdAcrossFiles(files);
|
|
481
|
+
}
|
|
482
|
+
seenSessions.add(agentSessionId);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
await writeTranscriptUsageCache(cachePath, next);
|
|
486
|
+
const merged = emptyFoldState();
|
|
487
|
+
for (const state of folded) {
|
|
488
|
+
for (const [agentSessionId, modelMap] of state.sessionUsage) {
|
|
489
|
+
merged.sessionUsage.set(agentSessionId, modelMap);
|
|
490
|
+
}
|
|
491
|
+
for (const [agentSessionId, skillMap] of state.sessionSkillUsage) {
|
|
492
|
+
merged.sessionSkillUsage.set(agentSessionId, skillMap);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return toExtracted(merged);
|
|
496
|
+
}
|
|
497
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/usage/claude.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ExtractedSessionUsage, ResolvedTranscript } from './types.js';
|
|
2
|
+
/** Extracts per-response usage from either Codex rollout format, once per turn. */
|
|
3
|
+
export declare function extractCodexUsage(files: readonly ResolvedTranscript[]): Promise<ExtractedSessionUsage>;
|
|
4
|
+
//# sourceMappingURL=codex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/usage/codex.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,qBAAqB,EAAE,kBAAkB,EAAgB,MAAM,YAAY,CAAA;AA6FzF,mFAAmF;AACnF,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,SAAS,kBAAkB,EAAE,GACnC,OAAO,CAAC,qBAAqB,CAAC,CAoEhC"}
|