sidekick-shared 0.28.2 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +83 -0
- package/README.md +176 -30
- package/dist/authStatus.d.ts +54 -0
- package/dist/authStatus.js +91 -0
- package/dist/browser.d.ts +14 -6
- package/dist/browser.js +26 -1
- 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/codexQuota.js +17 -4
- package/dist/esm/authStatus.js +87 -0
- package/dist/esm/browser.js +6 -2
- package/dist/esm/codex.js +18 -0
- package/dist/esm/codexProfiles.js +8 -11
- package/dist/esm/codexQuota.js +18 -5
- package/dist/esm/formatting.js +10 -1
- package/dist/esm/index.js +12 -9
- package/dist/esm/modelCatalog.js +2 -1
- package/dist/esm/modelInfo.js +422 -45
- package/dist/esm/multiProviderQuotaService.js +1 -1
- package/dist/esm/node.js +9 -3
- package/dist/esm/parsers/codexParser.js +175 -54
- package/dist/esm/parsers/codexRolloutOrigin.js +107 -0
- package/dist/esm/parsers/openCodeParser.js +83 -21
- package/dist/esm/pricing.js +18 -0
- package/dist/esm/pricingCatalog.js +14 -7
- package/dist/esm/promptHistory.js +66 -4
- package/dist/esm/providerFailure.js +124 -3
- package/dist/esm/providers/codex.js +2 -24
- 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/quota.js +356 -24
- package/dist/esm/quotaHistory.js +33 -8
- package/dist/esm/quotaPoller.js +5 -2
- package/dist/esm/quotaResolver.js +31 -6
- package/dist/esm/quotaSnapshots.js +65 -10
- package/dist/esm/quotaThresholds.js +7 -2
- package/dist/esm/report/htmlReportGenerator.js +42 -5
- package/dist/esm/report/transcriptParser.js +13 -11
- package/dist/esm/schemas/index.js +2 -2
- package/dist/esm/schemas/providerFailure.js +3 -1
- package/dist/esm/schemas/quota.js +30 -0
- package/dist/esm/schemas/quotaHistory.js +10 -2
- package/dist/esm/schemas/stateFile.js +9 -0
- package/dist/esm/schemas/transcript.js +4 -0
- package/dist/esm/sessionPromptHistory.js +2 -2
- package/dist/esm/stateFile.js +18 -3
- package/dist/esm/statusline/claudeStatuslinePayload.js +16 -4
- package/dist/esm/statusline/formatter.js +57 -18
- package/dist/esm/statusline/index.js +2 -2
- package/dist/esm/statusline/payload.js +9 -0
- package/dist/esm/testing/credentialGuard.js +87 -5
- package/dist/esm/toolCallOrigins.js +310 -0
- package/dist/esm/transcript.js +42 -3
- package/dist/esm/usage/usageEvents.js +146 -17
- package/dist/esm/usage/usageReports.js +39 -6
- package/dist/esm/watchers/jsonlTail.js +378 -59
- package/dist/esm/zaiQuotaApi.js +4 -2
- package/dist/formatting.d.ts +26 -1
- package/dist/formatting.js +11 -1
- package/dist/index.d.ts +22 -16
- package/dist/index.js +48 -12
- package/dist/modelCatalog.js +2 -1
- package/dist/modelInfo.d.ts +185 -16
- package/dist/modelInfo.js +431 -47
- package/dist/multiProviderQuotaService.js +1 -1
- package/dist/node.d.ts +16 -5
- package/dist/node.js +31 -2
- package/dist/parsers/codexParser.d.ts +55 -0
- package/dist/parsers/codexParser.js +175 -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 +86 -21
- package/dist/pricing.d.ts +22 -0
- package/dist/pricing.js +50 -0
- package/dist/pricingCatalog.d.ts +10 -0
- package/dist/pricingCatalog.js +13 -6
- 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/codex.d.ts +3 -22
- package/dist/providers/codex.js +3 -26
- 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/quota.d.ts +131 -1
- package/dist/quota.js +366 -24
- package/dist/quotaHistory.d.ts +10 -0
- package/dist/quotaHistory.js +34 -8
- package/dist/quotaPoller.d.ts +11 -3
- package/dist/quotaPoller.js +5 -2
- package/dist/quotaResolver.js +30 -5
- package/dist/quotaSnapshots.js +64 -9
- package/dist/quotaThresholds.js +7 -2
- 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 +3 -3
- package/dist/schemas/index.js +5 -2
- package/dist/schemas/providerFailure.d.ts +39 -0
- package/dist/schemas/providerFailure.js +2 -0
- package/dist/schemas/quota.d.ts +258 -0
- package/dist/schemas/quota.js +31 -1
- package/dist/schemas/quotaHistory.d.ts +2 -0
- package/dist/schemas/quotaHistory.js +10 -2
- package/dist/schemas/stateFile.d.ts +34 -0
- package/dist/schemas/stateFile.js +10 -1
- 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/stateFile.d.ts +18 -0
- package/dist/stateFile.js +17 -2
- package/dist/statusline/claudeStatuslinePayload.d.ts +4 -3
- package/dist/statusline/claudeStatuslinePayload.js +16 -4
- package/dist/statusline/formatter.d.ts +5 -0
- package/dist/statusline/formatter.js +56 -17
- package/dist/statusline/index.d.ts +4 -4
- package/dist/statusline/index.js +5 -1
- package/dist/statusline/payload.d.ts +10 -0
- package/dist/statusline/payload.js +14 -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 +42 -3
- package/dist/types/opencode.d.ts +2 -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/jsonlTail.d.ts +101 -4
- package/dist/watchers/jsonlTail.js +380 -60
- package/dist/zaiQuotaApi.js +3 -1
- package/package.json +41 -1
|
@@ -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
|
/**
|
|
@@ -13,6 +13,8 @@ exports.bucketUsage = bucketUsage;
|
|
|
13
13
|
exports.summarizeUsageRows = summarizeUsageRows;
|
|
14
14
|
const costProvenance_1 = require("../aggregation/costProvenance");
|
|
15
15
|
const formatting_1 = require("../formatting");
|
|
16
|
+
const modelInfo_1 = require("../modelInfo");
|
|
17
|
+
const claudeUsageDedupe_1 = require("./claudeUsageDedupe");
|
|
16
18
|
function dayParts(ms, utc) {
|
|
17
19
|
const date = new Date(ms);
|
|
18
20
|
return utc
|
|
@@ -75,7 +77,8 @@ function addTokens(target, source) {
|
|
|
75
77
|
}
|
|
76
78
|
/**
|
|
77
79
|
* Group usage events into rows. Rows are sorted by key (sessions by first
|
|
78
|
-
* event), then provider, project, and model, so tables read oldest first
|
|
80
|
+
* event), then provider, project, and model, so tables read oldest first;
|
|
81
|
+
* rows grouped by `session` within one of those follow their first event.
|
|
79
82
|
*/
|
|
80
83
|
function bucketUsage(events, options) {
|
|
81
84
|
const groupBy = new Set(options.groupBy ?? ['provider']);
|
|
@@ -83,13 +86,21 @@ function bucketUsage(events, options) {
|
|
|
83
86
|
const byProvider = isSession || groupBy.has('provider');
|
|
84
87
|
const byProject = isSession || groupBy.has('project');
|
|
85
88
|
const byModel = groupBy.has('model');
|
|
89
|
+
const bySession = isSession || groupBy.has('session');
|
|
90
|
+
const keyModel = options.modelKey === 'canonical'
|
|
91
|
+
? (model) => (model === claudeUsageDedupe_1.SYNTHETIC_MODEL ? model : (0, modelInfo_1.canonicalModelId)(model) || model)
|
|
92
|
+
: (model) => model;
|
|
86
93
|
const buckets = new Map();
|
|
87
94
|
for (const event of events) {
|
|
95
|
+
if (options.excludeSynthetic && event.model === claudeUsageDedupe_1.SYNTHETIC_MODEL)
|
|
96
|
+
continue;
|
|
88
97
|
const key = usageBucketKey(event, options);
|
|
89
98
|
const provider = byProvider ? event.provider : null;
|
|
90
99
|
const project = byProject ? event.project : null;
|
|
91
|
-
const
|
|
92
|
-
const
|
|
100
|
+
const eventModel = keyModel(event.model);
|
|
101
|
+
const model = byModel ? eventModel : null;
|
|
102
|
+
const sessionId = bySession ? event.sessionId : null;
|
|
103
|
+
const id = JSON.stringify([key, provider, project, model, sessionId]);
|
|
93
104
|
let acc = buckets.get(id);
|
|
94
105
|
if (!acc) {
|
|
95
106
|
acc = {
|
|
@@ -99,15 +110,17 @@ function bucketUsage(events, options) {
|
|
|
99
110
|
provider,
|
|
100
111
|
project,
|
|
101
112
|
model,
|
|
102
|
-
sessionId
|
|
113
|
+
sessionId,
|
|
103
114
|
models: [],
|
|
104
115
|
calls: 0,
|
|
105
116
|
sessions: 0,
|
|
106
117
|
tokens: emptyTokens(),
|
|
107
118
|
totalTokens: 0,
|
|
108
119
|
costUsd: 0,
|
|
120
|
+
pricedCostUsd: null,
|
|
109
121
|
costProvenance: 'none',
|
|
110
122
|
unpricedCalls: 0,
|
|
123
|
+
reasoningTokens: 0,
|
|
111
124
|
firstTimestamp: event.timestamp,
|
|
112
125
|
lastTimestamp: event.timestamp,
|
|
113
126
|
},
|
|
@@ -122,12 +135,14 @@ function bucketUsage(events, options) {
|
|
|
122
135
|
row.calls += 1;
|
|
123
136
|
addTokens(row.tokens, event.tokens);
|
|
124
137
|
row.totalTokens += event.tokens.totalTokens;
|
|
138
|
+
row.reasoningTokens = (row.reasoningTokens ?? 0) + reasoningOf(event.tokens.reasoningTokens);
|
|
125
139
|
row.firstTimestamp = Math.min(row.firstTimestamp, event.timestamp);
|
|
126
140
|
row.lastTimestamp = Math.max(row.lastTimestamp, event.timestamp);
|
|
127
141
|
acc.sessionIds.add(event.sessionId);
|
|
128
|
-
acc.modelTokens.set(
|
|
142
|
+
acc.modelTokens.set(eventModel, (acc.modelTokens.get(eventModel) ?? 0) + event.tokens.totalTokens);
|
|
129
143
|
if (event.costUsd !== null) {
|
|
130
144
|
row.costUsd += event.costUsd;
|
|
145
|
+
row.pricedCostUsd = (row.pricedCostUsd ?? 0) + event.costUsd;
|
|
131
146
|
if (event.costProvenance === 'provider-reported')
|
|
132
147
|
acc.reportedCalls += 1;
|
|
133
148
|
else
|
|
@@ -153,7 +168,14 @@ function bucketUsage(events, options) {
|
|
|
153
168
|
return rows.sort((a, b) => (isSession ? a.firstTimestamp - b.firstTimestamp : a.key.localeCompare(b.key)) ||
|
|
154
169
|
(a.provider ?? '').localeCompare(b.provider ?? '') ||
|
|
155
170
|
(a.project ?? '').localeCompare(b.project ?? '') ||
|
|
156
|
-
(a.model ?? '').localeCompare(b.model ?? '')
|
|
171
|
+
(a.model ?? '').localeCompare(b.model ?? '') ||
|
|
172
|
+
(bySession && !isSession
|
|
173
|
+
? a.firstTimestamp - b.firstTimestamp ||
|
|
174
|
+
(a.sessionId ?? '').localeCompare(b.sessionId ?? '')
|
|
175
|
+
: 0));
|
|
176
|
+
}
|
|
177
|
+
function reasoningOf(value) {
|
|
178
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : 0;
|
|
157
179
|
}
|
|
158
180
|
/**
|
|
159
181
|
* Sum rows that do not overlap (for example every row of one `bucketUsage`
|
|
@@ -167,8 +189,10 @@ function summarizeUsageRows(rows, events) {
|
|
|
167
189
|
tokens: emptyTokens(),
|
|
168
190
|
totalTokens: 0,
|
|
169
191
|
costUsd: 0,
|
|
192
|
+
pricedCostUsd: null,
|
|
170
193
|
costProvenance: 'none',
|
|
171
194
|
unpricedCalls: 0,
|
|
195
|
+
reasoningTokens: 0,
|
|
172
196
|
};
|
|
173
197
|
let reportedCalls = 0;
|
|
174
198
|
let estimatedCalls = 0;
|
|
@@ -178,6 +202,15 @@ function summarizeUsageRows(rows, events) {
|
|
|
178
202
|
addTokens(totals.tokens, row.tokens);
|
|
179
203
|
totals.totalTokens += row.totalTokens;
|
|
180
204
|
totals.costUsd += row.costUsd;
|
|
205
|
+
// Rows from before `pricedCostUsd` existed: any priced call means a priced cost.
|
|
206
|
+
const rowPriced = row.pricedCostUsd !== undefined
|
|
207
|
+
? row.pricedCostUsd
|
|
208
|
+
: row.calls > row.unpricedCalls
|
|
209
|
+
? row.costUsd
|
|
210
|
+
: null;
|
|
211
|
+
if (rowPriced !== null)
|
|
212
|
+
totals.pricedCostUsd = (totals.pricedCostUsd ?? 0) + rowPriced;
|
|
213
|
+
totals.reasoningTokens = (totals.reasoningTokens ?? 0) + reasoningOf(row.reasoningTokens);
|
|
181
214
|
totals.unpricedCalls += row.unpricedCalls;
|
|
182
215
|
const priced = row.calls - row.unpricedCalls;
|
|
183
216
|
if (row.costProvenance === 'reported')
|
|
@@ -7,6 +7,8 @@ import type { ZodType } from 'zod';
|
|
|
7
7
|
export declare const DEFAULT_JSONL_TAIL_READ_BYTES: number;
|
|
8
8
|
/** Default longest line parsed; longer lines are skipped and reported. */
|
|
9
9
|
export declare const DEFAULT_JSONL_TAIL_MAX_LINE_BYTES: number;
|
|
10
|
+
/** The `version` written into, and accepted from, {@link JsonlTailCheckpoint}. */
|
|
11
|
+
export declare const JSONL_TAIL_CHECKPOINT_VERSION = 1;
|
|
10
12
|
export interface JsonlTailBatch {
|
|
11
13
|
bytesRead: number;
|
|
12
14
|
eventsRead: number;
|
|
@@ -20,12 +22,68 @@ export interface JsonlTailLine {
|
|
|
20
22
|
length: number;
|
|
21
23
|
}
|
|
22
24
|
export type JsonlTailResetReason = 'truncated' | 'rewritten' | 'replaced';
|
|
23
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* What the tail does when the file it follows shrank, was rewritten, or was
|
|
27
|
+
* replaced: `'restart'` replays it from offset 0; `'end'` continues from its
|
|
28
|
+
* current end, skipping what it already holds.
|
|
29
|
+
*/
|
|
30
|
+
export type JsonlTailRewritePolicy = 'restart' | 'end';
|
|
31
|
+
/** The tail started over from the beginning, or (with `onRewrite: 'end'`) the end, of the file. */
|
|
24
32
|
export interface JsonlTailReset {
|
|
25
33
|
/** `truncated`: the file shrank; `rewritten`: its first bytes or the resume point changed; `replaced`: another file now has the path. */
|
|
26
34
|
reason: JsonlTailResetReason;
|
|
27
35
|
/** The committed offset before the reset. */
|
|
28
36
|
previousOffset: number;
|
|
37
|
+
/**
|
|
38
|
+
* Where reading continues. Present only with `onRewrite: 'end'`; with the
|
|
39
|
+
* default `'restart'` policy reading always continues from 0.
|
|
40
|
+
*/
|
|
41
|
+
offset?: number;
|
|
42
|
+
}
|
|
43
|
+
/** An event parsed by {@link JsonlTail.read}, with its line's position. */
|
|
44
|
+
export interface JsonlTailEntry<T> {
|
|
45
|
+
event: T;
|
|
46
|
+
line: JsonlTailLine;
|
|
47
|
+
}
|
|
48
|
+
/** What one {@link JsonlTail.read} call read. */
|
|
49
|
+
export interface JsonlTailReadResult<T> extends JsonlTailBatch {
|
|
50
|
+
/** Events from the lines that became complete, in file order: the same ones `onEvent` received. */
|
|
51
|
+
events: JsonlTailEntry<T>[];
|
|
52
|
+
/** Present when the tail reset before reading; `onReset` received the same object. */
|
|
53
|
+
reset?: JsonlTailReset;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A JSON-serializable resume point from {@link JsonlTail.checkpoint}. It
|
|
57
|
+
* identifies the file by device and inode plus a digest of its first bytes,
|
|
58
|
+
* so a new tail (in this or a later process) can verify it is still reading
|
|
59
|
+
* the same, append-only file before continuing from `offset`.
|
|
60
|
+
*/
|
|
61
|
+
export interface JsonlTailCheckpoint {
|
|
62
|
+
version: typeof JSONL_TAIL_CHECKPOINT_VERSION;
|
|
63
|
+
/** Resume-safe byte offset: 0, or just after a newline. */
|
|
64
|
+
offset: number;
|
|
65
|
+
/** Device and inode of the file the offset refers to; `null` before the first read. */
|
|
66
|
+
identity: {
|
|
67
|
+
dev: number;
|
|
68
|
+
ino: number;
|
|
69
|
+
} | null;
|
|
70
|
+
/**
|
|
71
|
+
* Length (at most 4096) and truncated SHA-256 hex of the file's first
|
|
72
|
+
* bytes when last read; `null` before the first read.
|
|
73
|
+
*/
|
|
74
|
+
prefixDigest: {
|
|
75
|
+
bytes: number;
|
|
76
|
+
hash: string;
|
|
77
|
+
} | null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Reported to `onError` (and nothing is read) when `noFollow` is set and the
|
|
81
|
+
* tailed path is a symbolic link.
|
|
82
|
+
*/
|
|
83
|
+
export declare class JsonlTailSymlinkError extends Error {
|
|
84
|
+
readonly path: string;
|
|
85
|
+
readonly code = "JSONL_TAIL_SYMLINK";
|
|
86
|
+
constructor(path: string);
|
|
29
87
|
}
|
|
30
88
|
export interface JsonlTailOptions<T> {
|
|
31
89
|
path: string;
|
|
@@ -37,20 +95,59 @@ export interface JsonlTailOptions<T> {
|
|
|
37
95
|
onEvent: (event: T, line: JsonlTailLine) => void;
|
|
38
96
|
onBatchComplete?: (batch: JsonlTailBatch) => void;
|
|
39
97
|
onError?: (error: Error, line?: string) => void;
|
|
40
|
-
/** Called after the tail resets
|
|
98
|
+
/** Called after the tail resets (see `onRewrite`); events from the new position follow. */
|
|
41
99
|
onReset?: (reset: JsonlTailReset) => void;
|
|
42
100
|
/** Bytes read per chunk (default 1 MiB); one poll reads chunk by chunk to the end. */
|
|
43
101
|
maxReadBytes?: number;
|
|
44
102
|
/** Longest line parsed (default 16 MiB); a longer line is skipped and reported to `onError`. */
|
|
45
103
|
maxLineBytes?: number;
|
|
104
|
+
/**
|
|
105
|
+
* Where to continue after the file is truncated, rewritten, or replaced,
|
|
106
|
+
* and after `resumeFrom()` rejects a checkpoint. `'restart'` (default)
|
|
107
|
+
* replays from offset 0. `'end'` continues from the file's current end, or
|
|
108
|
+
* from the start of an unfinished final line as `startAtEnd` does. `onReset`
|
|
109
|
+
* fires either way.
|
|
110
|
+
*/
|
|
111
|
+
onRewrite?: JsonlTailRewritePolicy;
|
|
112
|
+
/**
|
|
113
|
+
* Refuse to follow a symbolic link at `path`: the file is opened with
|
|
114
|
+
* `O_NOFOLLOW` where the platform defines it (a no-op where it does not,
|
|
115
|
+
* such as Windows). A symlinked path is reported to `onError` as a
|
|
116
|
+
* {@link JsonlTailSymlinkError} and not read or watched. Only the final
|
|
117
|
+
* path component is checked; symlinked parent directories still resolve.
|
|
118
|
+
*/
|
|
119
|
+
noFollow?: boolean;
|
|
46
120
|
}
|
|
47
|
-
export interface JsonlTail {
|
|
121
|
+
export interface JsonlTail<T = unknown> {
|
|
48
122
|
readonly isActive: boolean;
|
|
49
123
|
start(): void;
|
|
50
124
|
stop(): void;
|
|
51
125
|
dispose(): void;
|
|
126
|
+
/**
|
|
127
|
+
* Reads every newly complete line now, synchronously, through the
|
|
128
|
+
* callbacks. While `read()` calls are queued or running, the call is
|
|
129
|
+
* deferred until the last of them settles.
|
|
130
|
+
*/
|
|
52
131
|
readNow(): void;
|
|
132
|
+
/**
|
|
133
|
+
* Reads like `readNow()`, with asynchronous I/O, and resolves with what it
|
|
134
|
+
* read; the callbacks fire as they do for `readNow()`. Never rejects for
|
|
135
|
+
* I/O or parse problems (those go to `onError`). Concurrent calls run one
|
|
136
|
+
* after another.
|
|
137
|
+
*/
|
|
138
|
+
read(): Promise<JsonlTailReadResult<T>>;
|
|
53
139
|
getOffset(): number;
|
|
54
140
|
seekTo(offset: number): void;
|
|
141
|
+
/** A JSON-serializable resume point at the committed offset; does no I/O. */
|
|
142
|
+
checkpoint(): JsonlTailCheckpoint;
|
|
143
|
+
/**
|
|
144
|
+
* Validates `checkpoint` (shape, file identity, prefix digest, and that its
|
|
145
|
+
* offset still follows a newline) against the file now at the path. On
|
|
146
|
+
* success reading continues from its offset and this returns true.
|
|
147
|
+
* Otherwise the `onRewrite` policy picks the position (0, or the file's
|
|
148
|
+
* current end) and this returns false; `onReset` is not called. Either way
|
|
149
|
+
* the position overrides `startAtEnd` on a later `start()`.
|
|
150
|
+
*/
|
|
151
|
+
resumeFrom(checkpoint: unknown): boolean;
|
|
55
152
|
}
|
|
56
|
-
export declare function createJsonlTail<T>(options: JsonlTailOptions<T>): JsonlTail
|
|
153
|
+
export declare function createJsonlTail<T>(options: JsonlTailOptions<T>): JsonlTail<T>;
|