sidekick-shared 0.28.3 → 0.29.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/CHANGELOG.md +86 -0
- package/README.md +188 -27
- package/dist/aggregation/EventAggregator.d.ts +19 -0
- package/dist/aggregation/EventAggregator.js +174 -35
- package/dist/aggregation/snapshot.d.ts +37 -8
- package/dist/aggregation/snapshot.js +31 -9
- package/dist/aggregation/types.d.ts +13 -0
- package/dist/authStatus.d.ts +54 -0
- package/dist/authStatus.js +91 -0
- package/dist/browser.d.ts +16 -6
- package/dist/browser.js +24 -2
- package/dist/codex.d.ts +23 -0
- package/dist/codex.js +44 -0
- package/dist/codexProfiles.d.ts +1 -0
- package/dist/codexProfiles.js +8 -11
- package/dist/context/sessionContext.js +4 -0
- package/dist/esm/aggregation/EventAggregator.js +174 -35
- package/dist/esm/aggregation/snapshot.js +31 -9
- package/dist/esm/authStatus.js +87 -0
- package/dist/esm/browser.js +5 -2
- package/dist/esm/codex.js +18 -0
- package/dist/esm/codexProfiles.js +8 -11
- package/dist/esm/context/sessionContext.js +4 -0
- package/dist/esm/formatters/toolSummary.js +4 -1
- package/dist/esm/formatting.js +10 -1
- package/dist/esm/index.js +11 -6
- package/dist/esm/modelCatalog.js +7 -5
- package/dist/esm/modelContext.js +4 -3
- package/dist/esm/modelInfo.js +425 -47
- package/dist/esm/node.js +9 -3
- package/dist/esm/observedContextWindows.js +2 -1
- package/dist/esm/ownKey.js +26 -0
- package/dist/esm/parsers/codexParser.js +351 -54
- package/dist/esm/parsers/codexRolloutOrigin.js +107 -0
- package/dist/esm/parsers/openCodeParser.js +85 -22
- package/dist/esm/parsers/subagentScanner.js +33 -12
- package/dist/esm/parsers/subagentTraceParser.js +97 -37
- package/dist/esm/pricing.js +18 -0
- package/dist/esm/pricingCatalog.js +17 -9
- package/dist/esm/promptHistory.js +66 -4
- package/dist/esm/providerFailure.js +124 -3
- package/dist/esm/providers/claudeCode.js +10 -2
- package/dist/esm/providers/codex.js +84 -62
- package/dist/esm/providers/codexDatabase.js +3 -19
- package/dist/esm/providers/codexRollouts.js +24 -1
- package/dist/esm/providers/openCode.js +9 -17
- package/dist/esm/report/htmlReportGenerator.js +42 -5
- package/dist/esm/report/transcriptParser.js +13 -11
- package/dist/esm/schemas/providerFailure.js +3 -1
- package/dist/esm/schemas/sessionEvent.js +35 -14
- package/dist/esm/schemas/transcript.js +4 -0
- package/dist/esm/sessionPromptHistory.js +2 -2
- package/dist/esm/statusline/payload.js +9 -0
- package/dist/esm/subagentSpawn.js +163 -0
- package/dist/esm/testing/credentialGuard.js +87 -5
- package/dist/esm/toolCallOrigins.js +310 -0
- package/dist/esm/transcript.js +46 -3
- package/dist/esm/turns/assistantTurn.js +3 -2
- package/dist/esm/usage/claudeUsageDedupe.js +68 -0
- package/dist/esm/usage/usageEvents.js +146 -17
- package/dist/esm/usage/usageReports.js +39 -6
- package/dist/esm/watchers/eventBridge.js +15 -0
- package/dist/esm/watchers/factory.js +5 -3
- package/dist/esm/watchers/jsonlTail.js +442 -58
- package/dist/esm/watchers/jsonlWatcher.js +51 -4
- package/dist/esm/watchers/providerReaderWatcher.js +13 -0
- package/dist/esm/zaiQuota.js +2 -1
- package/dist/formatters/toolSummary.js +4 -1
- package/dist/formatting.d.ts +26 -1
- package/dist/formatting.js +11 -1
- package/dist/index.d.ts +22 -11
- package/dist/index.js +44 -12
- package/dist/modelCatalog.js +7 -5
- package/dist/modelContext.js +4 -3
- package/dist/modelInfo.d.ts +185 -16
- package/dist/modelInfo.js +434 -49
- package/dist/node.d.ts +16 -5
- package/dist/node.js +33 -2
- package/dist/observedContextWindows.js +2 -1
- package/dist/ownKey.d.ts +16 -0
- package/dist/ownKey.js +30 -0
- package/dist/parsers/codexParser.d.ts +118 -0
- package/dist/parsers/codexParser.js +352 -54
- package/dist/parsers/codexRolloutOrigin.d.ts +49 -0
- package/dist/parsers/codexRolloutOrigin.js +111 -0
- package/dist/parsers/openCodeParser.d.ts +38 -0
- package/dist/parsers/openCodeParser.js +88 -22
- package/dist/parsers/subagentScanner.d.ts +19 -6
- package/dist/parsers/subagentScanner.js +33 -12
- package/dist/parsers/subagentTraceParser.d.ts +25 -5
- package/dist/parsers/subagentTraceParser.js +97 -37
- package/dist/pricing.d.ts +22 -0
- package/dist/pricing.js +50 -0
- package/dist/pricingCatalog.d.ts +10 -0
- package/dist/pricingCatalog.js +16 -8
- package/dist/promptHistory.d.ts +65 -4
- package/dist/promptHistory.js +68 -5
- package/dist/providerFailure.d.ts +44 -0
- package/dist/providerFailure.js +126 -4
- package/dist/providers/claudeCode.js +9 -1
- package/dist/providers/codex.d.ts +3 -22
- package/dist/providers/codex.js +85 -64
- package/dist/providers/codexDatabase.d.ts +3 -2
- package/dist/providers/codexDatabase.js +4 -22
- package/dist/providers/codexRollouts.d.ts +22 -0
- package/dist/providers/codexRollouts.js +24 -0
- package/dist/providers/openCode.js +8 -16
- package/dist/providers/types.d.ts +14 -0
- package/dist/report/htmlReportGenerator.js +42 -5
- package/dist/report/transcriptParser.d.ts +10 -1
- package/dist/report/transcriptParser.js +13 -11
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/providerFailure.d.ts +39 -0
- package/dist/schemas/providerFailure.js +2 -0
- package/dist/schemas/sessionEvent.d.ts +27 -10
- package/dist/schemas/sessionEvent.js +35 -14
- package/dist/schemas/transcript.d.ts +2 -0
- package/dist/schemas/transcript.js +4 -0
- package/dist/sessionPromptHistory.d.ts +7 -1
- package/dist/sessionPromptHistory.js +1 -1
- package/dist/statusline/payload.d.ts +10 -0
- package/dist/statusline/payload.js +14 -0
- package/dist/subagentSpawn.d.ts +80 -0
- package/dist/subagentSpawn.js +174 -0
- package/dist/testing/credentialGuard.d.ts +14 -0
- package/dist/testing/credentialGuard.js +87 -5
- package/dist/toolCallOrigins.d.ts +70 -0
- package/dist/toolCallOrigins.js +346 -0
- package/dist/transcript.d.ts +12 -0
- package/dist/transcript.js +46 -3
- package/dist/turns/assistantTurn.d.ts +1 -0
- package/dist/turns/assistantTurn.js +3 -2
- package/dist/types/opencode.d.ts +2 -0
- package/dist/usage/claudeUsageDedupe.d.ts +31 -0
- package/dist/usage/claudeUsageDedupe.js +68 -0
- package/dist/usage/usageEvents.d.ts +54 -6
- package/dist/usage/usageEvents.js +146 -17
- package/dist/usage/usageReports.d.ts +35 -4
- package/dist/usage/usageReports.js +39 -6
- package/dist/watchers/eventBridge.js +15 -0
- package/dist/watchers/factory.d.ts +6 -0
- package/dist/watchers/factory.js +5 -3
- package/dist/watchers/jsonlTail.d.ts +124 -4
- package/dist/watchers/jsonlTail.js +446 -59
- package/dist/watchers/jsonlWatcher.d.ts +18 -1
- package/dist/watchers/jsonlWatcher.js +50 -3
- package/dist/watchers/providerReaderWatcher.d.ts +7 -0
- package/dist/watchers/providerReaderWatcher.js +13 -0
- package/dist/watchers/types.d.ts +12 -0
- package/dist/zaiQuota.js +2 -1
- package/package.json +41 -1
|
@@ -6,12 +6,12 @@ exports.segmentAssistantTurn = segmentAssistantTurn;
|
|
|
6
6
|
exports.assistantTurnEventsFromSessionEvents = assistantTurnEventsFromSessionEvents;
|
|
7
7
|
exports.extractTurnSubagents = extractTurnSubagents;
|
|
8
8
|
const toolSummary_1 = require("../formatters/toolSummary");
|
|
9
|
+
const subagentSpawn_1 = require("../subagentSpawn");
|
|
9
10
|
const DEFAULT_NARRATION_CHARS = 4000;
|
|
10
11
|
const DEFAULT_REASONING_CHARS = 8000;
|
|
11
12
|
const DEFAULT_PROCESS_STEPS = 60;
|
|
12
13
|
const DEFAULT_REASONING_BLOCKS = 24;
|
|
13
14
|
const DEFAULT_TOOL_INPUT_CHARS = 200;
|
|
14
|
-
const SUBAGENT_TOOL_NAMES = new Set(['Task']);
|
|
15
15
|
const LABEL_MAX = 80;
|
|
16
16
|
const TITLE_RE = /^\s*\*\*([^*\n]+)\*\*[ \t]*(?:\n+([\s\S]*))?$/;
|
|
17
17
|
const TITLE_MAX = 100;
|
|
@@ -29,8 +29,9 @@ function reasoningSummary(text) {
|
|
|
29
29
|
}
|
|
30
30
|
return { title: null, body: trimmed };
|
|
31
31
|
}
|
|
32
|
+
/** Whether a tool call spawns a subagent (`Agent`, or its earlier name `Task`). */
|
|
32
33
|
function isAssistantTurnSubagentTool(toolName) {
|
|
33
|
-
return
|
|
34
|
+
return (0, subagentSpawn_1.isSubagentSpawnTool)(toolName);
|
|
34
35
|
}
|
|
35
36
|
function segmentAssistantTurn(events, options = {}) {
|
|
36
37
|
const maxNarrationChars = options.maxNarrationChars ?? DEFAULT_NARRATION_CHARS;
|
package/dist/types/opencode.d.ts
CHANGED
|
@@ -42,6 +42,8 @@ export interface OpenCodeMessage {
|
|
|
42
42
|
cacheRead?: number;
|
|
43
43
|
cacheWrite?: number;
|
|
44
44
|
reasoning?: number;
|
|
45
|
+
/** Provider-reported total; tells OpenCode's token layouts apart (see `normalizeOpenCodeUsage`). */
|
|
46
|
+
total?: number;
|
|
45
47
|
};
|
|
46
48
|
time: {
|
|
47
49
|
created: string | number;
|
|
@@ -22,6 +22,24 @@ import type { SessionEvent } from '../types/sessionEvent';
|
|
|
22
22
|
export declare const SYNTHETIC_MODEL = "<synthetic>";
|
|
23
23
|
/** Attach Anthropic-semantics normalized usage and Claude Code provenance to an event. */
|
|
24
24
|
export declare function normalizeClaudeUsage(event: SessionEvent): SessionEvent;
|
|
25
|
+
/** Tokens a {@link ClaudeUsageDeduper} has already counted for one message id. */
|
|
26
|
+
export interface ClaudeUsageDedupeBuckets {
|
|
27
|
+
input: number;
|
|
28
|
+
cacheRead: number;
|
|
29
|
+
cacheWrite: number;
|
|
30
|
+
output: number;
|
|
31
|
+
reasoning: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* JSON-serializable state of a {@link ClaudeUsageDeduper}, from
|
|
35
|
+
* `exportState()`, for `importState()` in a reader resumed at the same
|
|
36
|
+
* position (for example from a persisted session snapshot).
|
|
37
|
+
*/
|
|
38
|
+
export interface ClaudeUsageDedupeState {
|
|
39
|
+
version: 1;
|
|
40
|
+
/** `[message id, counted tokens]` pairs, oldest first. */
|
|
41
|
+
entries: Array<[string, ClaudeUsageDedupeBuckets]>;
|
|
42
|
+
}
|
|
25
43
|
/**
|
|
26
44
|
* Per-reader dedupe state. Split lines of one response are adjacent, so a
|
|
27
45
|
* bounded map (oldest keys evicted first) is enough.
|
|
@@ -40,6 +58,19 @@ export declare class ClaudeUsageDeduper {
|
|
|
40
58
|
/** Whether a message id has already been counted by this deduper. */
|
|
41
59
|
has(id: string): boolean;
|
|
42
60
|
reset(): void;
|
|
61
|
+
/**
|
|
62
|
+
* The most recent `limit` message ids and the tokens counted for each,
|
|
63
|
+
* oldest first. Split lines of one response are adjacent, so a reader
|
|
64
|
+
* resumed at the position this was taken at needs only the last few.
|
|
65
|
+
*/
|
|
66
|
+
exportState(limit?: number): ClaudeUsageDedupeState;
|
|
67
|
+
/**
|
|
68
|
+
* Replaces this deduper's state with one from {@link exportState}. Returns
|
|
69
|
+
* false, changing nothing, when `state` is not a valid version-1 state
|
|
70
|
+
* (wrong shape, empty ids, or counts that are not finite non-negative
|
|
71
|
+
* numbers). Entries beyond this deduper's capacity drop oldest first.
|
|
72
|
+
*/
|
|
73
|
+
importState(state: unknown): boolean;
|
|
43
74
|
private remember;
|
|
44
75
|
}
|
|
45
76
|
/**
|
|
@@ -64,6 +64,25 @@ function normalizeClaudeUsage(event) {
|
|
|
64
64
|
},
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
|
+
const BUCKET_KEYS = ['input', 'cacheRead', 'cacheWrite', 'output', 'reasoning'];
|
|
68
|
+
/** Entries {@link ClaudeUsageDeduper.exportState} keeps by default. */
|
|
69
|
+
const DEFAULT_EXPORT_LIMIT = 256;
|
|
70
|
+
function isCount(value) {
|
|
71
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0;
|
|
72
|
+
}
|
|
73
|
+
function bucketsFrom(value) {
|
|
74
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
75
|
+
return null;
|
|
76
|
+
const record = value;
|
|
77
|
+
const buckets = {};
|
|
78
|
+
for (const key of BUCKET_KEYS) {
|
|
79
|
+
const count = record[key];
|
|
80
|
+
if (!isCount(count))
|
|
81
|
+
return null;
|
|
82
|
+
buckets[key] = count;
|
|
83
|
+
}
|
|
84
|
+
return buckets;
|
|
85
|
+
}
|
|
67
86
|
function bucketsOf(usage) {
|
|
68
87
|
return {
|
|
69
88
|
input: usage.uncachedInputTokens,
|
|
@@ -168,6 +187,55 @@ class ClaudeUsageDeduper {
|
|
|
168
187
|
reset() {
|
|
169
188
|
this.seen.clear();
|
|
170
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* The most recent `limit` message ids and the tokens counted for each,
|
|
192
|
+
* oldest first. Split lines of one response are adjacent, so a reader
|
|
193
|
+
* resumed at the position this was taken at needs only the last few.
|
|
194
|
+
*/
|
|
195
|
+
exportState(limit = DEFAULT_EXPORT_LIMIT) {
|
|
196
|
+
const count = limit >= 0 ? Math.min(this.seen.size, Math.floor(limit)) : 0;
|
|
197
|
+
const entries = [];
|
|
198
|
+
if (count > 0) {
|
|
199
|
+
let skip = this.seen.size - count;
|
|
200
|
+
for (const [id, buckets] of this.seen) {
|
|
201
|
+
if (skip > 0) {
|
|
202
|
+
skip -= 1;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
entries.push([id, { ...buckets }]);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return { version: 1, entries };
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Replaces this deduper's state with one from {@link exportState}. Returns
|
|
212
|
+
* false, changing nothing, when `state` is not a valid version-1 state
|
|
213
|
+
* (wrong shape, empty ids, or counts that are not finite non-negative
|
|
214
|
+
* numbers). Entries beyond this deduper's capacity drop oldest first.
|
|
215
|
+
*/
|
|
216
|
+
importState(state) {
|
|
217
|
+
if (!state || typeof state !== 'object')
|
|
218
|
+
return false;
|
|
219
|
+
const { version, entries } = state;
|
|
220
|
+
if (version !== 1 || !Array.isArray(entries))
|
|
221
|
+
return false;
|
|
222
|
+
const parsed = [];
|
|
223
|
+
for (const entry of entries) {
|
|
224
|
+
if (!Array.isArray(entry) || entry.length !== 2)
|
|
225
|
+
return false;
|
|
226
|
+
const [id, value] = entry;
|
|
227
|
+
if (typeof id !== 'string' || id.length === 0)
|
|
228
|
+
return false;
|
|
229
|
+
const buckets = bucketsFrom(value);
|
|
230
|
+
if (!buckets)
|
|
231
|
+
return false;
|
|
232
|
+
parsed.push([id, buckets]);
|
|
233
|
+
}
|
|
234
|
+
this.seen.clear();
|
|
235
|
+
for (const [id, buckets] of parsed)
|
|
236
|
+
this.remember(id, buckets);
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
171
239
|
remember(id, buckets) {
|
|
172
240
|
this.seen.delete(id);
|
|
173
241
|
this.seen.set(id, buckets);
|
|
@@ -11,9 +11,19 @@
|
|
|
11
11
|
* every hit and pruned least-recently-used, once per collection.
|
|
12
12
|
*
|
|
13
13
|
* A Claude Code session's subagents write their own transcripts under
|
|
14
|
-
* `<sessionId>/subagents/`; their usage is attributed to the parent session
|
|
15
|
-
* and their sizes and mtimes join the fingerprint.
|
|
16
|
-
* are top-level rollouts, already listed
|
|
14
|
+
* `<sessionId>/subagents/`; their usage is attributed to the parent session,
|
|
15
|
+
* tagged with the subagent, and their sizes and mtimes join the fingerprint.
|
|
16
|
+
* (Codex spawned subagents and forks are top-level rollouts, already listed
|
|
17
|
+
* as sessions of their own; their events are tagged with the parent read
|
|
18
|
+
* from the rollout's first line.)
|
|
19
|
+
*
|
|
20
|
+
* Each event also carries the ordinal of the human prompt it answered, from
|
|
21
|
+
* the same scanner as `collectPromptHistory()`, so usage joins prompt history
|
|
22
|
+
* on `(provider, sessionId, ordinal)`.
|
|
23
|
+
*
|
|
24
|
+
* Cache files are named per cache version (`<provider>--<session>.v3.json`)
|
|
25
|
+
* so readers of different versions sharing the directory never overwrite each
|
|
26
|
+
* other's files; pruning ages out every version's files alike.
|
|
17
27
|
*
|
|
18
28
|
* Node-only: reads session files and the cache directory.
|
|
19
29
|
*
|
|
@@ -25,9 +35,11 @@ import type { PricingProvenance } from '../usageNormalization';
|
|
|
25
35
|
/**
|
|
26
36
|
* Bump when the cached record shape or the counting rules change; older files
|
|
27
37
|
* are re-read. v2: Claude split lines and duplicate Codex `token_count` events
|
|
28
|
-
* are counted once, and Claude subagent transcripts are included.
|
|
38
|
+
* are counted once, and Claude subagent transcripts are included. v3: events
|
|
39
|
+
* keep reasoning tokens, prompt ordinal, message id, and subagent, and files
|
|
40
|
+
* move to a versioned name (`<provider>--<session>.v3.json`).
|
|
29
41
|
*/
|
|
30
|
-
export declare const USAGE_CACHE_VERSION =
|
|
42
|
+
export declare const USAGE_CACHE_VERSION = 3;
|
|
31
43
|
/**
|
|
32
44
|
* Most-recently-used cache files kept on disk. Sized for a busy month of
|
|
33
45
|
* sessions (files are a few KB each); a collection larger than this still
|
|
@@ -47,11 +59,43 @@ export interface UsageEventRecord {
|
|
|
47
59
|
/** Four buckets plus the provider-semantics-aware total. */
|
|
48
60
|
tokens: TokenTotals & {
|
|
49
61
|
totalTokens: number;
|
|
62
|
+
/**
|
|
63
|
+
* Reasoning tokens the provider reported, when any. Informational, never
|
|
64
|
+
* added to a total: Codex counts them inside `outputTokens`; OpenCode
|
|
65
|
+
* reports them apart, inside `totalTokens` but not `outputTokens`.
|
|
66
|
+
*/
|
|
67
|
+
reasoningTokens?: number;
|
|
50
68
|
};
|
|
51
69
|
costUsd: number | null;
|
|
52
70
|
costProvenance: PricingProvenance;
|
|
53
71
|
/** Session file the event came from; tells apart two files with one session id. */
|
|
54
72
|
filePath?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Ordinal of the human prompt this call followed: the highest ordinal among
|
|
75
|
+
* the session's prompts timestamped at or before the event, as
|
|
76
|
+
* `collectPromptHistory()` numbers them with default bounds. Claude Code
|
|
77
|
+
* subagent calls map to the parent session's prompts. `null` when no prompt
|
|
78
|
+
* precedes the call, or the session has none (a Codex subagent or fork is a
|
|
79
|
+
* session of its own without human prompts). Absent when
|
|
80
|
+
* unknown: a provider without prompt history (OpenCode), or a cache entry
|
|
81
|
+
* written before prompt ordinals were recorded.
|
|
82
|
+
*/
|
|
83
|
+
promptOrdinal?: number | null;
|
|
84
|
+
/**
|
|
85
|
+
* The provider's message id for the call (Claude Code, OpenCode). Absent for
|
|
86
|
+
* Codex, whose `token_count` lines carry no request id (the reader's
|
|
87
|
+
* timestamp-derived placeholder is not passed through).
|
|
88
|
+
*/
|
|
89
|
+
requestId?: string;
|
|
90
|
+
/**
|
|
91
|
+
* Set when a subagent made the call. Claude Code: the agent id from its
|
|
92
|
+
* transcript name (`agent-<id>.jsonl`), parent = this session. Codex: the
|
|
93
|
+
* forked or spawned rollout's own session id and the session it came from.
|
|
94
|
+
*/
|
|
95
|
+
subagent?: {
|
|
96
|
+
agentId: string;
|
|
97
|
+
parentSessionId: string;
|
|
98
|
+
};
|
|
55
99
|
}
|
|
56
100
|
/** One session that contributed events. */
|
|
57
101
|
export interface UsageSessionRecord {
|
|
@@ -111,7 +155,11 @@ export interface CollectUsageEventsResult {
|
|
|
111
155
|
roots?: string[];
|
|
112
156
|
}
|
|
113
157
|
export declare function getUsageCacheDir(): string;
|
|
114
|
-
/**
|
|
158
|
+
/**
|
|
159
|
+
* Deletes all but the `keep` most recently used cache files (by mtime), of
|
|
160
|
+
* every cache version alike, so files no reader touches any more age out.
|
|
161
|
+
* Best-effort.
|
|
162
|
+
*/
|
|
115
163
|
export declare function pruneUsageCache(keep?: number, dir?: string): number;
|
|
116
164
|
/**
|
|
117
165
|
* Collect priced usage events for every session touched in the window.
|
|
@@ -12,9 +12,19 @@
|
|
|
12
12
|
* every hit and pruned least-recently-used, once per collection.
|
|
13
13
|
*
|
|
14
14
|
* A Claude Code session's subagents write their own transcripts under
|
|
15
|
-
* `<sessionId>/subagents/`; their usage is attributed to the parent session
|
|
16
|
-
* and their sizes and mtimes join the fingerprint.
|
|
17
|
-
* are top-level rollouts, already listed
|
|
15
|
+
* `<sessionId>/subagents/`; their usage is attributed to the parent session,
|
|
16
|
+
* tagged with the subagent, and their sizes and mtimes join the fingerprint.
|
|
17
|
+
* (Codex spawned subagents and forks are top-level rollouts, already listed
|
|
18
|
+
* as sessions of their own; their events are tagged with the parent read
|
|
19
|
+
* from the rollout's first line.)
|
|
20
|
+
*
|
|
21
|
+
* Each event also carries the ordinal of the human prompt it answered, from
|
|
22
|
+
* the same scanner as `collectPromptHistory()`, so usage joins prompt history
|
|
23
|
+
* on `(provider, sessionId, ordinal)`.
|
|
24
|
+
*
|
|
25
|
+
* Cache files are named per cache version (`<provider>--<session>.v3.json`)
|
|
26
|
+
* so readers of different versions sharing the directory never overwrite each
|
|
27
|
+
* other's files; pruning ages out every version's files alike.
|
|
18
28
|
*
|
|
19
29
|
* Node-only: reads session files and the cache directory.
|
|
20
30
|
*
|
|
@@ -60,7 +70,10 @@ exports.pruneUsageCache = pruneUsageCache;
|
|
|
60
70
|
exports.collectUsageEvents = collectUsageEvents;
|
|
61
71
|
const fs = __importStar(require("fs"));
|
|
62
72
|
const path = __importStar(require("path"));
|
|
73
|
+
const codexRolloutOrigin_1 = require("../parsers/codexRolloutOrigin");
|
|
63
74
|
const paths_1 = require("../paths");
|
|
75
|
+
const promptHistory_1 = require("../promptHistory");
|
|
76
|
+
const codexRollouts_1 = require("../providers/codexRollouts");
|
|
64
77
|
const sessionPreviews_1 = require("../sessionPreviews");
|
|
65
78
|
const usageNormalization_1 = require("../usageNormalization");
|
|
66
79
|
const atomic_1 = require("../writers/atomic");
|
|
@@ -68,9 +81,11 @@ const workspaceRoots_1 = require("../workspaceRoots");
|
|
|
68
81
|
/**
|
|
69
82
|
* Bump when the cached record shape or the counting rules change; older files
|
|
70
83
|
* are re-read. v2: Claude split lines and duplicate Codex `token_count` events
|
|
71
|
-
* are counted once, and Claude subagent transcripts are included.
|
|
84
|
+
* are counted once, and Claude subagent transcripts are included. v3: events
|
|
85
|
+
* keep reasoning tokens, prompt ordinal, message id, and subagent, and files
|
|
86
|
+
* move to a versioned name (`<provider>--<session>.v3.json`).
|
|
72
87
|
*/
|
|
73
|
-
exports.USAGE_CACHE_VERSION =
|
|
88
|
+
exports.USAGE_CACHE_VERSION = 3;
|
|
74
89
|
/**
|
|
75
90
|
* Most-recently-used cache files kept on disk. Sized for a busy month of
|
|
76
91
|
* sessions (files are a few KB each); a collection larger than this still
|
|
@@ -81,9 +96,15 @@ exports.MAX_USAGE_CACHE_FILES = 5000;
|
|
|
81
96
|
function getUsageCacheDir() {
|
|
82
97
|
return path.join((0, paths_1.getConfigDir)(), 'usage-cache');
|
|
83
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Cache file of one session. The name carries the cache version, so a reader
|
|
101
|
+
* of another version sharing the directory (which rejects this version, and
|
|
102
|
+
* versions before 3 use the unversioned `<provider>--<session>.json`) never
|
|
103
|
+
* overwrites it, and this version never overwrites theirs.
|
|
104
|
+
*/
|
|
84
105
|
function cacheFilePath(dir, provider, sessionId) {
|
|
85
106
|
const safe = sessionId.replace(/[^A-Za-z0-9._-]/g, '_');
|
|
86
|
-
return path.join(dir, `${provider}--${safe}.json`);
|
|
107
|
+
return path.join(dir, `${provider}--${safe}.v${exports.USAGE_CACHE_VERSION}.json`);
|
|
87
108
|
}
|
|
88
109
|
function toMs(value, label) {
|
|
89
110
|
if (value === undefined)
|
|
@@ -114,7 +135,11 @@ function readCache(filePath) {
|
|
|
114
135
|
return null;
|
|
115
136
|
}
|
|
116
137
|
}
|
|
117
|
-
/**
|
|
138
|
+
/**
|
|
139
|
+
* Deletes all but the `keep` most recently used cache files (by mtime), of
|
|
140
|
+
* every cache version alike, so files no reader touches any more age out.
|
|
141
|
+
* Best-effort.
|
|
142
|
+
*/
|
|
118
143
|
function pruneUsageCache(keep = exports.MAX_USAGE_CACHE_FILES, dir = getUsageCacheDir()) {
|
|
119
144
|
try {
|
|
120
145
|
const entries = fs
|
|
@@ -147,7 +172,7 @@ function pruneUsageCache(keep = exports.MAX_USAGE_CACHE_FILES, dir = getUsageCac
|
|
|
147
172
|
return 0;
|
|
148
173
|
}
|
|
149
174
|
}
|
|
150
|
-
function cachedEventFromUsage(usage, timestamp, model) {
|
|
175
|
+
function cachedEventFromUsage(usage, timestamp, model, messageId, agentId) {
|
|
151
176
|
const record = {
|
|
152
177
|
t: timestamp,
|
|
153
178
|
m: model,
|
|
@@ -160,16 +185,29 @@ function cachedEventFromUsage(usage, timestamp, model) {
|
|
|
160
185
|
};
|
|
161
186
|
if (usage.reportedCostUsd !== undefined)
|
|
162
187
|
record.rc = usage.reportedCostUsd;
|
|
188
|
+
if (usage.reasoningTokens > 0)
|
|
189
|
+
record.rt = usage.reasoningTokens;
|
|
190
|
+
if (messageId && !messageId.startsWith('token-count-'))
|
|
191
|
+
record.rid = messageId;
|
|
192
|
+
if (agentId)
|
|
193
|
+
record.sa = agentId;
|
|
163
194
|
return record;
|
|
164
195
|
}
|
|
165
|
-
/**
|
|
166
|
-
|
|
196
|
+
/**
|
|
197
|
+
* Price a cached event with the current catalog (or its provider-reported
|
|
198
|
+
* cost). Output is priced from the billable output (`bo`), which already
|
|
199
|
+
* holds reasoning however the provider reports it, so restoring the reasoning
|
|
200
|
+
* count never changes the cost.
|
|
201
|
+
*/
|
|
202
|
+
function priceCachedEvent(cached, provider, sessionId, project, sessionFile, sessionSubagent) {
|
|
203
|
+
const reasoningTokens = typeof cached.rt === 'number' && cached.rt > 0 ? cached.rt : 0;
|
|
167
204
|
const usage = {
|
|
168
205
|
uncachedInputTokens: cached.i,
|
|
169
206
|
cacheReadTokens: cached.cr,
|
|
170
207
|
cacheWriteTokens: cached.cw,
|
|
171
208
|
outputTokens: cached.o,
|
|
172
|
-
reasoningTokens
|
|
209
|
+
reasoningTokens,
|
|
210
|
+
// `bo` is already the billable output; nothing is added to it.
|
|
173
211
|
reasoningIncludedInOutput: true,
|
|
174
212
|
cacheInclusiveInputTokens: cached.i + cached.cr + cached.cw,
|
|
175
213
|
billableOutputTokens: cached.bo,
|
|
@@ -181,6 +219,9 @@ function priceCachedEvent(cached, provider, sessionId, project, sessionFile) {
|
|
|
181
219
|
usage,
|
|
182
220
|
modelId: cached.m === 'unknown' ? undefined : cached.m,
|
|
183
221
|
});
|
|
222
|
+
const subagent = typeof cached.sa === 'string' && cached.sa
|
|
223
|
+
? { agentId: cached.sa, parentSessionId: sessionId }
|
|
224
|
+
: sessionSubagent;
|
|
184
225
|
return {
|
|
185
226
|
timestamp: cached.t,
|
|
186
227
|
provider,
|
|
@@ -193,13 +234,19 @@ function priceCachedEvent(cached, provider, sessionId, project, sessionFile) {
|
|
|
193
234
|
cacheWriteTokens: cached.cw,
|
|
194
235
|
cacheReadTokens: cached.cr,
|
|
195
236
|
totalTokens: cached.tt,
|
|
237
|
+
...(reasoningTokens > 0 ? { reasoningTokens } : {}),
|
|
196
238
|
},
|
|
197
239
|
costUsd: priced.costUsd,
|
|
198
240
|
costProvenance: priced.source,
|
|
199
241
|
filePath: sessionFile,
|
|
242
|
+
...(cached.po === null || (typeof cached.po === 'number' && Number.isInteger(cached.po))
|
|
243
|
+
? { promptOrdinal: cached.po }
|
|
244
|
+
: {}),
|
|
245
|
+
...(typeof cached.rid === 'string' ? { requestId: cached.rid } : {}),
|
|
246
|
+
...(subagent ? { subagent: { ...subagent } } : {}),
|
|
200
247
|
};
|
|
201
248
|
}
|
|
202
|
-
const CLAUDE_AGENT_FILE = /^agent
|
|
249
|
+
const CLAUDE_AGENT_FILE = /^agent-(.+)\.jsonl$/;
|
|
203
250
|
/** Claude Code subagent transcripts of a session, sorted by name. */
|
|
204
251
|
function claudeSubagentFiles(preview) {
|
|
205
252
|
if (preview.provider !== 'claude-code')
|
|
@@ -219,6 +266,7 @@ function claudeSubagentFiles(preview) {
|
|
|
219
266
|
files.push({
|
|
220
267
|
path: path.join(dir, name),
|
|
221
268
|
name,
|
|
269
|
+
agentId: CLAUDE_AGENT_FILE.exec(name)?.[1] ?? name,
|
|
222
270
|
sizeBytes: stat.size,
|
|
223
271
|
modifiedAt: stat.mtimeMs,
|
|
224
272
|
});
|
|
@@ -235,7 +283,7 @@ function sessionFingerprint(preview, subagents) {
|
|
|
235
283
|
return base;
|
|
236
284
|
return `${base}|${subagents.map((f) => `${f.name}:${f.sizeBytes}:${f.modifiedAt}`).join(',')}`;
|
|
237
285
|
}
|
|
238
|
-
function readTranscriptUsage(provider, filePath, cached) {
|
|
286
|
+
function readTranscriptUsage(provider, filePath, cached, agentId) {
|
|
239
287
|
const reader = provider.createReader(filePath);
|
|
240
288
|
const events = reader.readAll();
|
|
241
289
|
reader.flush();
|
|
@@ -257,12 +305,14 @@ function readTranscriptUsage(provider, filePath, cached) {
|
|
|
257
305
|
target.cr += usage.cacheReadTokens;
|
|
258
306
|
target.bo += usage.billableOutputTokens;
|
|
259
307
|
target.tt += usage.totalTokens;
|
|
308
|
+
if (usage.reasoningTokens > 0)
|
|
309
|
+
target.rt = (target.rt ?? 0) + usage.reasoningTokens;
|
|
260
310
|
continue;
|
|
261
311
|
}
|
|
262
312
|
const timestamp = Date.parse(event.timestamp);
|
|
263
313
|
if (!Number.isFinite(timestamp))
|
|
264
314
|
continue;
|
|
265
|
-
const record = cachedEventFromUsage(usage, timestamp, event.message?.model ?? usage.model ?? 'unknown');
|
|
315
|
+
const record = cachedEventFromUsage(usage, timestamp, event.message?.model ?? usage.model ?? 'unknown', messageId, agentId);
|
|
266
316
|
cached.push(record);
|
|
267
317
|
if (messageId)
|
|
268
318
|
byMessageId.set(messageId, record);
|
|
@@ -272,10 +322,75 @@ function readSessionUsage(provider, preview, subagents) {
|
|
|
272
322
|
const cached = [];
|
|
273
323
|
readTranscriptUsage(provider, preview.filePath, cached);
|
|
274
324
|
for (const file of subagents)
|
|
275
|
-
readTranscriptUsage(provider, file.path, cached);
|
|
325
|
+
readTranscriptUsage(provider, file.path, cached, file.agentId);
|
|
276
326
|
cached.sort((a, b) => a.t - b.t);
|
|
277
327
|
return cached;
|
|
278
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Sets each event's `po` to the highest ordinal among the prompts timestamped
|
|
331
|
+
* at or before it, or null when none is. With prompts in time order (the
|
|
332
|
+
* usual case) that is the last prompt before the event.
|
|
333
|
+
*/
|
|
334
|
+
function assignPromptOrdinals(events, marks) {
|
|
335
|
+
const sorted = [...marks].sort((a, b) => a.timestampMs - b.timestampMs || a.ordinal - b.ordinal);
|
|
336
|
+
const highest = [];
|
|
337
|
+
let max = -1;
|
|
338
|
+
for (const mark of sorted) {
|
|
339
|
+
max = Math.max(max, mark.ordinal);
|
|
340
|
+
highest.push(max);
|
|
341
|
+
}
|
|
342
|
+
for (const event of events) {
|
|
343
|
+
let low = 0;
|
|
344
|
+
let high = sorted.length;
|
|
345
|
+
while (low < high) {
|
|
346
|
+
const middle = (low + high) >>> 1;
|
|
347
|
+
if (sorted[middle].timestampMs <= event.t)
|
|
348
|
+
low = middle + 1;
|
|
349
|
+
else
|
|
350
|
+
high = middle;
|
|
351
|
+
}
|
|
352
|
+
event.po = low === 0 ? null : highest[low - 1];
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Prompt ordinals for a freshly read session, from its main transcript.
|
|
357
|
+
* Returns false when the transcript could not be scanned (the events then
|
|
358
|
+
* keep no ordinal and the session is not cached, so the next run retries).
|
|
359
|
+
*/
|
|
360
|
+
async function addPromptOrdinals(preview, events) {
|
|
361
|
+
if (preview.provider !== 'claude-code' && preview.provider !== 'codex')
|
|
362
|
+
return true;
|
|
363
|
+
let marks;
|
|
364
|
+
try {
|
|
365
|
+
marks = await (0, promptHistory_1.readPromptOrdinalMarks)(preview.provider, preview.sessionId, preview.filePath);
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
assignPromptOrdinals(events, marks);
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
/** Parent of a Codex rollout that is a fork or a spawned subagent, from its first line only. */
|
|
374
|
+
async function codexSessionSubagent(preview) {
|
|
375
|
+
if (preview.provider !== 'codex')
|
|
376
|
+
return undefined;
|
|
377
|
+
const { line } = await (0, codexRollouts_1.readFirstLineAsync)(preview.filePath);
|
|
378
|
+
if (!line)
|
|
379
|
+
return undefined;
|
|
380
|
+
let first;
|
|
381
|
+
try {
|
|
382
|
+
first = JSON.parse(line);
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
return undefined;
|
|
386
|
+
}
|
|
387
|
+
const origin = (0, codexRolloutOrigin_1.codexRolloutOrigin)(first);
|
|
388
|
+
if (!origin.parentId || (!origin.isSubagent && !origin.forkedFromId))
|
|
389
|
+
return undefined;
|
|
390
|
+
if (origin.parentId === preview.sessionId)
|
|
391
|
+
return undefined;
|
|
392
|
+
return { agentId: preview.sessionId, parentSessionId: origin.parentId };
|
|
393
|
+
}
|
|
279
394
|
function yieldToEventLoop() {
|
|
280
395
|
return new Promise((resolve) => setImmediate(resolve));
|
|
281
396
|
}
|
|
@@ -315,12 +430,14 @@ async function collectUsageEvents(options) {
|
|
|
315
430
|
const fingerprint = sessionFingerprint(preview, subagentFiles);
|
|
316
431
|
const filePath = cacheFilePath(cacheDir, preview.provider, preview.sessionId);
|
|
317
432
|
let cachedEvents = null;
|
|
433
|
+
let sessionSubagent;
|
|
318
434
|
let project = preview.workspacePath;
|
|
319
435
|
let fromCache = false;
|
|
320
436
|
if (useCache) {
|
|
321
437
|
const cached = readCache(filePath);
|
|
322
438
|
if (cached && cached.fingerprint === fingerprint && cached.sessionId === preview.sessionId) {
|
|
323
439
|
cachedEvents = cached.events;
|
|
440
|
+
sessionSubagent = validSubagent(cached.subagent);
|
|
324
441
|
project = project ?? cached.project;
|
|
325
442
|
fromCache = true;
|
|
326
443
|
cacheHits += 1;
|
|
@@ -344,7 +461,10 @@ async function collectUsageEvents(options) {
|
|
|
344
461
|
options.onDiagnostic?.(diagnostic);
|
|
345
462
|
continue;
|
|
346
463
|
}
|
|
347
|
-
|
|
464
|
+
// Ordinals and origin are best-effort: a failure costs only the cache write.
|
|
465
|
+
const ordinalsRead = await addPromptOrdinals(preview, cachedEvents);
|
|
466
|
+
sessionSubagent = await codexSessionSubagent(preview).catch(() => undefined);
|
|
467
|
+
if (useCache && ordinalsRead) {
|
|
348
468
|
const record = {
|
|
349
469
|
version: exports.USAGE_CACHE_VERSION,
|
|
350
470
|
provider: preview.provider,
|
|
@@ -352,6 +472,7 @@ async function collectUsageEvents(options) {
|
|
|
352
472
|
fingerprint,
|
|
353
473
|
project,
|
|
354
474
|
events: cachedEvents,
|
|
475
|
+
...(sessionSubagent ? { subagent: sessionSubagent } : {}),
|
|
355
476
|
};
|
|
356
477
|
try {
|
|
357
478
|
await (0, atomic_1.atomicWriteJson)(filePath, record);
|
|
@@ -371,7 +492,7 @@ async function collectUsageEvents(options) {
|
|
|
371
492
|
continue;
|
|
372
493
|
if (untilMs !== null && cached.t > untilMs)
|
|
373
494
|
continue;
|
|
374
|
-
events.push(priceCachedEvent(cached, preview.provider, preview.sessionId, project, preview.filePath));
|
|
495
|
+
events.push(priceCachedEvent(cached, preview.provider, preview.sessionId, project, preview.filePath, sessionSubagent));
|
|
375
496
|
first = first === null ? cached.t : Math.min(first, cached.t);
|
|
376
497
|
last = last === null ? cached.t : Math.max(last, cached.t);
|
|
377
498
|
count += 1;
|
|
@@ -400,6 +521,14 @@ async function collectUsageEvents(options) {
|
|
|
400
521
|
...(scope ? { roots: scope.roots } : {}),
|
|
401
522
|
};
|
|
402
523
|
}
|
|
524
|
+
function validSubagent(value) {
|
|
525
|
+
if (typeof value !== 'object' || value === null)
|
|
526
|
+
return undefined;
|
|
527
|
+
const { agentId, parentSessionId } = value;
|
|
528
|
+
return typeof agentId === 'string' && typeof parentSessionId === 'string'
|
|
529
|
+
? { agentId, parentSessionId }
|
|
530
|
+
: undefined;
|
|
531
|
+
}
|
|
403
532
|
/** The roots to list, or undefined for an unscoped (or legacy single-root) call. */
|
|
404
533
|
function usageScope(options) {
|
|
405
534
|
if (options.workspacePaths) {
|
|
@@ -10,8 +10,11 @@ import type { ProviderId } from '../providers/types';
|
|
|
10
10
|
import type { TokenTotals } from '../types/historicalData';
|
|
11
11
|
import type { UsageEventRecord } from './usageEvents';
|
|
12
12
|
export type UsageGranularity = 'day' | 'week' | 'month' | 'session';
|
|
13
|
-
/**
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Extra dimensions a bucket is split by, beyond the time key. `session`
|
|
15
|
+
* splits each time bucket per session (rows then carry `sessionId`).
|
|
16
|
+
*/
|
|
17
|
+
export type UsageGroupDimension = 'provider' | 'project' | 'model' | 'session';
|
|
15
18
|
export interface BucketUsageOptions {
|
|
16
19
|
granularity: UsageGranularity;
|
|
17
20
|
/** Key by the UTC calendar instead of the local calendar (default false). */
|
|
@@ -20,6 +23,19 @@ export interface BucketUsageOptions {
|
|
|
20
23
|
weekStartsOn?: 0 | 1;
|
|
21
24
|
/** Dimensions to group by in addition to the time key (default `['provider']`). */
|
|
22
25
|
groupBy?: readonly UsageGroupDimension[];
|
|
26
|
+
/**
|
|
27
|
+
* Drop events whose model is Claude Code's `<synthetic>` placeholder
|
|
28
|
+
* (`SYNTHETIC_MODEL`: locally generated, unbilled messages). Default false.
|
|
29
|
+
*/
|
|
30
|
+
excludeSynthetic?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* How model ids are keyed in `model` rows and the `models` list. `'raw'`
|
|
33
|
+
* (default) keeps each id as recorded; `'canonical'` merges spellings of one
|
|
34
|
+
* model (date suffixes, provider/region prefixes, Bedrock forms) through
|
|
35
|
+
* `canonicalModelId()`, so `claude-opus-5-5-20261001` and `claude-opus-5-5`
|
|
36
|
+
* share a row. Pricing is unaffected: events arrive already priced.
|
|
37
|
+
*/
|
|
38
|
+
modelKey?: 'raw' | 'canonical';
|
|
23
39
|
}
|
|
24
40
|
export interface UsageBucketRow {
|
|
25
41
|
/** `YYYY-MM-DD` for days and week starts, `YYYY-MM` for months, the session id for sessions. */
|
|
@@ -29,7 +45,7 @@ export interface UsageBucketRow {
|
|
|
29
45
|
project: string | null;
|
|
30
46
|
/** Set when grouping by model; null for rows spanning every model. */
|
|
31
47
|
model: string | null;
|
|
32
|
-
/** Session id for `session` rows
|
|
48
|
+
/** Session id for `session` rows and rows grouped by `session`; null otherwise. */
|
|
33
49
|
sessionId: string | null;
|
|
34
50
|
/** Models seen in the row, most tokens first. */
|
|
35
51
|
models: string[];
|
|
@@ -41,8 +57,18 @@ export interface UsageBucketRow {
|
|
|
41
57
|
totalTokens: number;
|
|
42
58
|
/** Priced portion of the cost; read `costProvenance` before labelling it. */
|
|
43
59
|
costUsd: number;
|
|
60
|
+
/**
|
|
61
|
+
* Sum of the priced calls' costs, or null when no call in the row was
|
|
62
|
+
* priced (where `costUsd` reads 0).
|
|
63
|
+
*/
|
|
64
|
+
pricedCostUsd: number | null;
|
|
44
65
|
costProvenance: AggregatedCostProvenance;
|
|
45
66
|
unpricedCalls: number;
|
|
67
|
+
/**
|
|
68
|
+
* Reasoning tokens reported by the row's calls. Informational, already
|
|
69
|
+
* inside the token buckets and `totalTokens`. Always set by `bucketUsage`.
|
|
70
|
+
*/
|
|
71
|
+
reasoningTokens?: number;
|
|
46
72
|
/** First and last event in the row, ms since epoch. */
|
|
47
73
|
firstTimestamp: number;
|
|
48
74
|
lastTimestamp: number;
|
|
@@ -53,8 +79,12 @@ export interface UsageTotals {
|
|
|
53
79
|
tokens: TokenTotals;
|
|
54
80
|
totalTokens: number;
|
|
55
81
|
costUsd: number;
|
|
82
|
+
/** Sum of the rows' priced costs, or null when no row had a priced call. */
|
|
83
|
+
pricedCostUsd: number | null;
|
|
56
84
|
costProvenance: AggregatedCostProvenance;
|
|
57
85
|
unpricedCalls: number;
|
|
86
|
+
/** Sum of the rows' `reasoningTokens`. Always set by `summarizeUsageRows`. */
|
|
87
|
+
reasoningTokens?: number;
|
|
58
88
|
}
|
|
59
89
|
/** The `YYYY-MM-DD` key of the week containing `ms`, starting on `weekStartsOn`. */
|
|
60
90
|
export declare function weekKey(ms: number, options?: {
|
|
@@ -65,7 +95,8 @@ export declare function weekKey(ms: number, options?: {
|
|
|
65
95
|
export declare function usageBucketKey(event: Pick<UsageEventRecord, 'timestamp' | 'sessionId'>, options: BucketUsageOptions): string;
|
|
66
96
|
/**
|
|
67
97
|
* Group usage events into rows. Rows are sorted by key (sessions by first
|
|
68
|
-
* event), then provider, project, and model, so tables read oldest first
|
|
98
|
+
* event), then provider, project, and model, so tables read oldest first;
|
|
99
|
+
* rows grouped by `session` within one of those follow their first event.
|
|
69
100
|
*/
|
|
70
101
|
export declare function bucketUsage(events: readonly UsageEventRecord[], options: BucketUsageOptions): UsageBucketRow[];
|
|
71
102
|
/**
|