@50arms/session-telemetry 0.0.0-stage → 0.1.66
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/README.md +68 -2
- package/dist/.source-hash +1 -0
- package/dist/activity-observer.d.ts +186 -0
- package/dist/activity-observer.js +917 -0
- package/dist/api-tool-names.d.ts +12 -0
- package/dist/api-tool-names.js +19 -0
- package/dist/browser.d.ts +29 -0
- package/dist/browser.js +10 -0
- package/dist/catalog-prices.d.ts +27 -0
- package/dist/catalog-prices.js +31 -0
- package/dist/claim-tracker.d.ts +53 -0
- package/dist/claim-tracker.js +213 -0
- package/dist/dispatch-usage.d.ts +321 -0
- package/dist/dispatch-usage.js +657 -0
- package/dist/git-head.d.ts +1 -0
- package/dist/git-head.js +19 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +49 -0
- package/dist/internal-logger.d.ts +16 -0
- package/dist/internal-logger.js +27 -0
- package/dist/ledger-timing.d.ts +26 -0
- package/dist/ledger-timing.js +31 -0
- package/dist/pricing.d.ts +93 -0
- package/dist/pricing.js +77 -0
- package/dist/real-work.d.ts +13 -0
- package/dist/real-work.js +57 -0
- package/dist/session-log-watcher.d.ts +616 -0
- package/dist/session-log-watcher.js +1285 -0
- package/dist/session-title-marks.d.ts +2 -0
- package/dist/session-title-marks.js +16 -0
- package/dist/stats-wire.d.ts +345 -0
- package/dist/stats-wire.js +636 -0
- package/dist/subagent-live.d.ts +36 -0
- package/dist/subagent-live.js +153 -0
- package/dist/tool-summary.d.ts +19 -0
- package/dist/tool-summary.js +58 -0
- package/dist/transcript-timing.d.ts +100 -0
- package/dist/transcript-timing.js +231 -0
- package/dist/types.d.ts +25 -0
- package/dist/types.js +1 -0
- package/dist/usage-ledger.d.ts +256 -0
- package/dist/usage-ledger.js +581 -0
- package/dist/write-queue.d.ts +9 -0
- package/dist/write-queue.js +16 -0
- package/package.json +43 -4
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DX-2963 — the ONE usage counter. Turns Claude Code transcript lines into
|
|
3
|
+
* per-reply usage-ledger entries; every counter in danxbot (the worker's
|
|
4
|
+
* accumulator, `claude-probe.ts`'s usage summer, the stall sweep's productivity signal,
|
|
5
|
+
* the cross-consumer tests) uses it instead of its own summing.
|
|
6
|
+
*
|
|
7
|
+
* The rule it implements, measured on real transcripts (DX-2963 card,
|
|
8
|
+
* 2026-09-18): Claude Code writes one assistant reply as SEVERAL lines that
|
|
9
|
+
* share a `message.id`, and the later lines carry the real (larger) usage —
|
|
10
|
+
* the last line held the maximum in 28,121 of 28,124 multi-line replies. A
|
|
11
|
+
* reply's usage is therefore the per-FIELD MAXIMUM over its lines, never its
|
|
12
|
+
* first line (the old rule every counter shared, which undercounted output
|
|
13
|
+
* 3.17x on sub-agent transcripts) and never a sum (which over-counts).
|
|
14
|
+
* Far-later repeats of a `message.id` are replays of re-written history and
|
|
15
|
+
* never raise usage, so a max-based counter credits them nothing for free.
|
|
16
|
+
*
|
|
17
|
+
* Pure arithmetic — no `node:` imports — so it is exported through the
|
|
18
|
+
* browser-safe `./browser` subpath as well as the main entry.
|
|
19
|
+
*/
|
|
20
|
+
import type { PricedTokens } from "./pricing.js";
|
|
21
|
+
import { type LedgerTiming } from "./ledger-timing.js";
|
|
22
|
+
import { type TranscriptLine } from "./transcript-timing.js";
|
|
23
|
+
import type { AgentLogEntry } from "./types.js";
|
|
24
|
+
/** Per-reply usage, both cache-write lifetimes kept apart (priced apart). */
|
|
25
|
+
export type LedgerUsage = PricedTokens;
|
|
26
|
+
/**
|
|
27
|
+
* One reply's ledger entry as the collector reports it: the per-field
|
|
28
|
+
* MAXIMUM usage seen over the reply's lines so far, plus every `tool_use`
|
|
29
|
+
* id seen in it.
|
|
30
|
+
*/
|
|
31
|
+
export interface LedgerEntry {
|
|
32
|
+
/** `message.id`, or the synthetic `nomsg:<source>:<byteOffset>` key. */
|
|
33
|
+
messageId: string;
|
|
34
|
+
/** `"main"` for the dispatch's own transcript, else the sub-agent id. */
|
|
35
|
+
source: string;
|
|
36
|
+
/** First non-empty `message.model` seen for the reply; `null` if none. */
|
|
37
|
+
model: string | null;
|
|
38
|
+
/** `usage.speed` (`standard` / `fast`); `"standard"` when the line has none. */
|
|
39
|
+
speed: string;
|
|
40
|
+
usage: LedgerUsage;
|
|
41
|
+
/** DX-3964 - the reply's measured time and lines; see {@link LedgerTiming}. */
|
|
42
|
+
timing: LedgerTiming;
|
|
43
|
+
/** The reply's own transcript timestamp (its FIRST line's), epoch ms. */
|
|
44
|
+
repliedAt: number;
|
|
45
|
+
toolUseIds: string[];
|
|
46
|
+
/**
|
|
47
|
+
* DX-3962 - the card this reply is credited to, or `null`. Fixed by the
|
|
48
|
+
* reply's FIRST line and never changed by a later one. Only an operator-session
|
|
49
|
+
* collector (`DispatchUsage`, `"operator-bridge"` mode) ever sets it; every
|
|
50
|
+
* other producer of an entry carries `null`.
|
|
51
|
+
*/
|
|
52
|
+
issueId: string | null;
|
|
53
|
+
/**
|
|
54
|
+
* DX-4939 - whether the reply did real work: any tool use of any of its lines edits or writes a file, writes a card or spawns
|
|
55
|
+
* a sub-agent (`real-work.ts`). It only ever rises as later lines of the reply arrive.
|
|
56
|
+
*/
|
|
57
|
+
realWork: boolean;
|
|
58
|
+
}
|
|
59
|
+
/** DX-4922 - what started a compaction: Claude Code's own threshold (`auto`) or the user's `/compact` (`manual`). */
|
|
60
|
+
export declare const COMPACTION_TRIGGERS: readonly ["auto", "manual"];
|
|
61
|
+
export type CompactionTrigger = (typeof COMPACTION_TRIGGERS)[number];
|
|
62
|
+
/**
|
|
63
|
+
* DX-4922 - the facts of one `compact_boundary` transcript line (`type: "system"`, `subtype: "compact_boundary"`;
|
|
64
|
+
* shape taken from real transcripts): its `uuid`, `compactMetadata.trigger` and `compactMetadata.preTokens`, the
|
|
65
|
+
* context size Claude Code measured just before it replaced the history with a summary.
|
|
66
|
+
*/
|
|
67
|
+
export interface CompactionBoundary {
|
|
68
|
+
/** The boundary line's own `uuid`: the compaction's identity, stable across a re-read of the file. */
|
|
69
|
+
id: string;
|
|
70
|
+
trigger: CompactionTrigger;
|
|
71
|
+
contextBefore: number;
|
|
72
|
+
/**
|
|
73
|
+
* DX-4939 - `compactMetadata.postTokens`: the context size Claude Code measured just after the compaction, summary and preserved
|
|
74
|
+
* messages together, or `null` when the line does not carry it (Claude Code versions differ; a sub-agent's boundary may lack it).
|
|
75
|
+
* `null` means not recorded, never zero.
|
|
76
|
+
*/
|
|
77
|
+
postTokens: number | null;
|
|
78
|
+
/** DX-4939 - `compactMetadata.durationMs`: how long the compaction paused the session, or `null` when the line does not carry it. */
|
|
79
|
+
durationMs: number | null;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* DX-4922 - one compaction as the collector reports it. A resend of the same `id` is an idempotent upsert on the server,
|
|
83
|
+
* which also derives the restart point (the context size of the source's first reply after the boundary) from the
|
|
84
|
+
* replies it stores, so a collector restart can never lose it.
|
|
85
|
+
*/
|
|
86
|
+
export interface CompactionEntry extends CompactionBoundary {
|
|
87
|
+
/** `"main"` or the sub-agent id, as {@link LedgerEntry.source}. */
|
|
88
|
+
source: string;
|
|
89
|
+
/** The boundary line's transcript timestamp, epoch ms. */
|
|
90
|
+
at: number;
|
|
91
|
+
/**
|
|
92
|
+
* DX-4939 - the length in characters of the compaction's summary text (the `isCompactSummary` user line that follows the boundary),
|
|
93
|
+
* or `null` until that line is read. `null` means NOT OBSERVED, and it can stay so for good: a transcript with no summary line, or a collector
|
|
94
|
+
* restarted between the boundary line and the summary line (it re-reads from its cursor, which is past the boundary, so it never learns
|
|
95
|
+
* which compaction the summary belongs to). The cost report leaves such a compaction out of its medians. The summary call's output tokens are not in the
|
|
96
|
+
* transcript; the summary's text is, and its length is the truest figure there is (`compactMetadata.postTokens` also counts the
|
|
97
|
+
* preserved messages and fixed prompt, about 6.8x the summary on the local transcripts of 2026-10-12, DX-4939).
|
|
98
|
+
*/
|
|
99
|
+
summaryChars: number | null;
|
|
100
|
+
}
|
|
101
|
+
/** The fields of one assistant line the counter needs. */
|
|
102
|
+
export interface LedgerLine {
|
|
103
|
+
messageId: string | undefined;
|
|
104
|
+
source: string;
|
|
105
|
+
/** Byte offset of the line's first byte in its file — keys a line with no id. */
|
|
106
|
+
byteOffset: number;
|
|
107
|
+
model: string | undefined;
|
|
108
|
+
/** The line's raw `message.usage` block, if it carries one. */
|
|
109
|
+
usage: Record<string, unknown> | undefined;
|
|
110
|
+
/** The line's transcript timestamp, epoch ms. */
|
|
111
|
+
timestamp: number;
|
|
112
|
+
/** `tool_use` block ids on this line. */
|
|
113
|
+
toolUseIds: readonly string[];
|
|
114
|
+
/** DX-3962 - the card the line's reply is credited to when this is its first line; see {@link LedgerEntry.issueId}. */
|
|
115
|
+
issueId: string | null;
|
|
116
|
+
/** DX-4939 - whether this line's tool uses include real work; see {@link LedgerEntry.realWork}. */
|
|
117
|
+
realWork: boolean;
|
|
118
|
+
}
|
|
119
|
+
export declare const MAIN_SOURCE = "main";
|
|
120
|
+
/**
|
|
121
|
+
* DX-3261 — a producer handed this module a line it cannot safely key or
|
|
122
|
+
* date: no valid `byte_offset` on a no-`message.id` line (would collapse
|
|
123
|
+
* every such line in the source onto the same synthetic key, crediting only
|
|
124
|
+
* the largest — see {@link syntheticMessageKey}), or a `timestamp` that
|
|
125
|
+
* doesn't parse (would read as `repliedAt: 0`, invisible to every
|
|
126
|
+
* account-window lookback). Both were silent `0` defaults before this card;
|
|
127
|
+
* thrown here instead, so a broken producer fails immediately and visibly
|
|
128
|
+
* rather than quietly losing or mis-crediting usage.
|
|
129
|
+
*/
|
|
130
|
+
export declare class LedgerLineDataError extends Error {
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* DX-2963 — a usage line with no `message.id` (never observed in the 22,087
|
|
134
|
+
* usage lines of the 20 newest local transcripts, 2026-09-18) is keyed by its
|
|
135
|
+
* source and the byte offset of its line, so it is credited exactly once
|
|
136
|
+
* however often the file is re-read, instead of once per read.
|
|
137
|
+
*/
|
|
138
|
+
export declare function syntheticMessageKey(source: string, byteOffset: number): string;
|
|
139
|
+
export declare function isSyntheticMessageKey(key: string): boolean;
|
|
140
|
+
/**
|
|
141
|
+
* Read a raw `message.usage` block into {@link LedgerUsage}. The two
|
|
142
|
+
* cache-write lifetimes come from `usage.cache_creation.ephemeral_5m_input_tokens`
|
|
143
|
+
* / `ephemeral_1h_input_tokens`. A line that carries only the flat
|
|
144
|
+
* `cache_creation_input_tokens` (no split object) wrote with the API's
|
|
145
|
+
* default 5-minute lifetime, so the whole amount is a 5-minute write — that
|
|
146
|
+
* is what the flat field meant before the split existed.
|
|
147
|
+
*/
|
|
148
|
+
export declare function parseUsageBlock(raw: Record<string, unknown>): LedgerUsage;
|
|
149
|
+
export declare function zeroUsage(): LedgerUsage;
|
|
150
|
+
export declare const LEDGER_USAGE_FIELDS: readonly ["input", "output", "cacheRead", "cacheWrite5m", "cacheWrite1h"];
|
|
151
|
+
export declare function maxUsage(a: LedgerUsage, b: LedgerUsage): LedgerUsage;
|
|
152
|
+
export declare function addUsage(a: LedgerUsage, b: LedgerUsage): LedgerUsage;
|
|
153
|
+
/** Per-field `max(0, a - b)`. */
|
|
154
|
+
export declare function excessUsage(a: LedgerUsage, b: LedgerUsage): LedgerUsage;
|
|
155
|
+
export declare function isZeroUsage(u: LedgerUsage): boolean;
|
|
156
|
+
/** Window tokens: input + output + both cache writes (the account-window unit). */
|
|
157
|
+
export declare function windowTokensOfUsage(u: LedgerUsage): number;
|
|
158
|
+
/** All five fields summed. */
|
|
159
|
+
export declare function totalTokensOfUsage(u: LedgerUsage): number;
|
|
160
|
+
/**
|
|
161
|
+
* The per-process counter: one entry per reply, updated line by line.
|
|
162
|
+
* `observe` returns true when the line CHANGED the reply's entry (a new
|
|
163
|
+
* reply, a raised field, or a new tool-use id) — a replay returns false —
|
|
164
|
+
* and the entry is queued for {@link takeChanged}. Holds one small entry per
|
|
165
|
+
* reply for the life of the process; no transcript text is kept.
|
|
166
|
+
*/
|
|
167
|
+
export declare class UsageLedgerCounter {
|
|
168
|
+
private readonly entries;
|
|
169
|
+
private readonly changed;
|
|
170
|
+
/** DX-3964 - one timing reducer per source (transcript file); see `transcript-timing.ts`. */
|
|
171
|
+
private readonly reducers;
|
|
172
|
+
/** DX-4922 - every compaction boundary read, by its id. */
|
|
173
|
+
private readonly compactions;
|
|
174
|
+
private readonly compactionsChanged;
|
|
175
|
+
/** DX-4939 - per source, the compaction whose summary line has not been read yet. */
|
|
176
|
+
private readonly openSummary;
|
|
177
|
+
/**
|
|
178
|
+
* DX-3964 - feed one line of the fed set (assistant, user, attachment, system) in FILE ORDER. An assistant
|
|
179
|
+
* line credits usage exactly as {@link observe} does; every kind also feeds its source's timing reducer,
|
|
180
|
+
* and a reply's timing that RISES - including one raised by a later tool_result, after the reply's last line,
|
|
181
|
+
* with no token growth - counts as a change, so {@link takeChanged} re-sends it.
|
|
182
|
+
*/
|
|
183
|
+
observeLine(t: TranscriptLine): boolean;
|
|
184
|
+
private observeCompaction;
|
|
185
|
+
/**
|
|
186
|
+
* DX-4939 - the summary text follows its boundary in the same source's file: the next `isCompactSummary` user line belongs to the
|
|
187
|
+
* compaction last read there. A compaction is reported at its boundary and again (a changed entry) once its summary length is known,
|
|
188
|
+
* so a session that ends right after compacting still reports it.
|
|
189
|
+
*/
|
|
190
|
+
private observeCompactSummary;
|
|
191
|
+
/** DX-4922 - the compactions read since the last call, oldest first, as snapshots; clears the queue. */
|
|
192
|
+
takeChangedCompactions(): CompactionEntry[];
|
|
193
|
+
/** DX-4922 - put compactions back on the changed queue (a write that failed). */
|
|
194
|
+
requeueCompactions(ids: readonly string[]): void;
|
|
195
|
+
/**
|
|
196
|
+
* DX-3964 - a source's file was truncated and is read again from 0: its timing reducer starts fresh and
|
|
197
|
+
* first-line-anchored, so a stale `hwm` of the old file can never credit a gap at the reset. Entries keep their
|
|
198
|
+
* values (max semantics); the re-read can only raise them.
|
|
199
|
+
*/
|
|
200
|
+
resetSource(source: string): void;
|
|
201
|
+
observe(line: LedgerLine): boolean;
|
|
202
|
+
/** Entries changed since the last call, as snapshots; clears the queue. */
|
|
203
|
+
takeChanged(): LedgerEntry[];
|
|
204
|
+
/** Put entries back on the changed queue (a write that failed). */
|
|
205
|
+
requeue(messageIds: readonly string[]): void;
|
|
206
|
+
get(messageId: string): LedgerEntry | undefined;
|
|
207
|
+
all(): LedgerEntry[];
|
|
208
|
+
/** Per-field sum of every reply's maximum — the transcript's true total. */
|
|
209
|
+
totals(): LedgerUsage;
|
|
210
|
+
/** Distinct tool-use ids across all replies. */
|
|
211
|
+
toolUseCount(): number;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* DX-4922 - whether a reader reports the compactions it reads. A reader that ships its ledger entries to the server
|
|
215
|
+
* (`DispatchUsage`) reports the compactions of the session it reads; one that only measures locally (the sub-agent live
|
|
216
|
+
* view, the probe, the bench attempt telemetry) says so by name, and a boundary it meets is then a plain system line.
|
|
217
|
+
*/
|
|
218
|
+
export type CompactionReading = {
|
|
219
|
+
readonly report: false;
|
|
220
|
+
} | {
|
|
221
|
+
readonly report: true;
|
|
222
|
+
readonly sessionId: string;
|
|
223
|
+
};
|
|
224
|
+
/** The reader ships nothing, so it reports no compactions. */
|
|
225
|
+
export declare const REPORT_NO_COMPACTIONS: CompactionReading;
|
|
226
|
+
/** The reader reports the compactions of `sessionId`: the session its transcript FILE belongs to. */
|
|
227
|
+
export declare function reportCompactionsOf(sessionId: string): CompactionReading;
|
|
228
|
+
/**
|
|
229
|
+
* The reading for a transcript of a FRESH session read as text: the session id its own lines carry. Only right where
|
|
230
|
+
* nothing is inherited (a bench attempt starts a new session); a fork's file starts with its parent's lines, so a
|
|
231
|
+
* live reader takes the session from the file's location instead (`transcriptSessionId`). A transcript with no line
|
|
232
|
+
* carrying a session id has no compaction boundary either, so it reports none.
|
|
233
|
+
*/
|
|
234
|
+
export declare function reportCompactionsOfFreshTranscript(text: string): CompactionReading;
|
|
235
|
+
/**
|
|
236
|
+
* Parse one raw transcript line (a parsed JSONL object) of the fed set - assistant, user (tool_result and human
|
|
237
|
+
* prompts), attachment, system - into a {@link TranscriptLine}. Returns `null` for every other `type`
|
|
238
|
+
* (`queue-operation`, `last-prompt`, `file-history-*`, `custom-title`, ...). THE one parser for the usage feed:
|
|
239
|
+
* the live watcher subscription, `observeTranscriptText` (claude-probe) and the tests all go through it.
|
|
240
|
+
*/
|
|
241
|
+
export declare function ledgerLineFromRaw(raw: Record<string, unknown>, source: string, byteOffset: number, compactions: CompactionReading): TranscriptLine | null;
|
|
242
|
+
/**
|
|
243
|
+
* Count a whole transcript text (one file) into a counter. Byte offsets are
|
|
244
|
+
* computed on UTF-8 bytes so a no-id line keys identically to the live
|
|
245
|
+
* watcher's own offset for the same line.
|
|
246
|
+
*/
|
|
247
|
+
export declare function observeTranscriptText(counter: UsageLedgerCounter, text: string, source: string, compactions: CompactionReading, startByteOffset?: number): void;
|
|
248
|
+
/**
|
|
249
|
+
* The counter's {@link LedgerLine} for one converted watcher entry
|
|
250
|
+
* (`AgentLogEntry`, as `SessionLogWatcher` emits it: `data.messageId`,
|
|
251
|
+
* `data.model`, `data.usage`, `data.content`, `data.subagent_id`,
|
|
252
|
+
* `data.byte_offset`). `null` for anything that is not an assistant entry.
|
|
253
|
+
* Shared by every counter fed from watcher entries (the worker's dispatch
|
|
254
|
+
* usage, the stall sweep's productivity signal).
|
|
255
|
+
*/
|
|
256
|
+
export declare function ledgerLineFromLogEntry(entry: AgentLogEntry): LedgerLine | null;
|