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
|
@@ -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')
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.toFollowEvents = toFollowEvents;
|
|
13
13
|
const toolSummary_1 = require("../formatters/toolSummary");
|
|
14
|
+
const subagentSpawn_1 = require("../subagentSpawn");
|
|
14
15
|
/**
|
|
15
16
|
* Converts a single SessionEvent to one or more FollowEvents.
|
|
16
17
|
*
|
|
@@ -151,6 +152,20 @@ function toFollowEvents(event, providerId) {
|
|
|
151
152
|
break;
|
|
152
153
|
}
|
|
153
154
|
case 'system': {
|
|
155
|
+
// A background task's notice (opt-in reader event): its summary, not the raw block.
|
|
156
|
+
const notification = (0, subagentSpawn_1.isTaskNotificationSystemEvent)(event)
|
|
157
|
+
? (0, subagentSpawn_1.taskNotificationFromEvent)(event)
|
|
158
|
+
: null;
|
|
159
|
+
if (notification) {
|
|
160
|
+
events.push({
|
|
161
|
+
providerId,
|
|
162
|
+
type: 'system',
|
|
163
|
+
timestamp: ts,
|
|
164
|
+
summary: notification.summary ?? `Background task ${notification.status ?? 'finished'}`,
|
|
165
|
+
raw: event,
|
|
166
|
+
});
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
154
169
|
const text = extractTextContent(event.message?.content);
|
|
155
170
|
const label = event.message?.sourceLabel || event.message?.role || 'system';
|
|
156
171
|
const summary = text ||
|
|
@@ -8,6 +8,12 @@ export interface CreateWatcherOptions {
|
|
|
8
8
|
workspacePath: string;
|
|
9
9
|
sessionId?: string;
|
|
10
10
|
callbacks: SessionWatcherCallbacks;
|
|
11
|
+
/**
|
|
12
|
+
* Claude Code only: also emit a `system` event for each mid-turn
|
|
13
|
+
* task-notification attachment, so an `EventAggregator` fed by this watcher
|
|
14
|
+
* can finish background subagents. Default `false`.
|
|
15
|
+
*/
|
|
16
|
+
includeTaskNotifications?: boolean;
|
|
11
17
|
}
|
|
12
18
|
/**
|
|
13
19
|
* Resolve the session a command should act on: an exact or unique-prefix
|
package/dist/watchers/factory.js
CHANGED
|
@@ -78,13 +78,15 @@ function resolveSessionPath(provider, workspacePath, sessionId) {
|
|
|
78
78
|
function createWatcher(options) {
|
|
79
79
|
const { provider, workspacePath, sessionId, callbacks } = options;
|
|
80
80
|
const sessionPath = resolveSessionPath(provider, workspacePath, sessionId);
|
|
81
|
-
const watcher = createWatcherForProvider(provider, sessionPath, callbacks
|
|
81
|
+
const watcher = createWatcherForProvider(provider, sessionPath, callbacks, {
|
|
82
|
+
includeTaskNotifications: options.includeTaskNotifications === true,
|
|
83
|
+
});
|
|
82
84
|
return { watcher, sessionPath };
|
|
83
85
|
}
|
|
84
|
-
function createWatcherForProvider(provider, sessionPath, callbacks) {
|
|
86
|
+
function createWatcherForProvider(provider, sessionPath, callbacks, jsonlOptions) {
|
|
85
87
|
switch (provider.id) {
|
|
86
88
|
case 'claude-code':
|
|
87
|
-
return new jsonlWatcher_1.JsonlSessionWatcher(provider.id, sessionPath, callbacks);
|
|
89
|
+
return new jsonlWatcher_1.JsonlSessionWatcher(provider.id, sessionPath, callbacks, jsonlOptions);
|
|
88
90
|
case 'codex':
|
|
89
91
|
return new providerReaderWatcher_1.ProviderReaderSessionWatcher(provider, sessionPath, callbacks);
|
|
90
92
|
case 'opencode': {
|
|
@@ -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,69 @@ 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} or
|
|
57
|
+
* {@link captureJsonlCheckpoint}. It identifies the file by device and inode
|
|
58
|
+
* plus a digest of its first bytes, so a new tail (in this or a later
|
|
59
|
+
* process) or {@link verifyJsonlCheckpoint} can confirm the file is still the
|
|
60
|
+
* same, append-only one before anything continues from `offset`.
|
|
61
|
+
*/
|
|
62
|
+
export interface JsonlTailCheckpoint {
|
|
63
|
+
version: typeof JSONL_TAIL_CHECKPOINT_VERSION;
|
|
64
|
+
/** Resume-safe byte offset: 0, or just after a newline. */
|
|
65
|
+
offset: number;
|
|
66
|
+
/** Device and inode of the file the offset refers to; `null` before the first read. */
|
|
67
|
+
identity: {
|
|
68
|
+
dev: number;
|
|
69
|
+
ino: number;
|
|
70
|
+
} | null;
|
|
71
|
+
/**
|
|
72
|
+
* Length (at most 4096) and truncated SHA-256 hex of the file's first
|
|
73
|
+
* bytes when last read; `null` before the first read.
|
|
74
|
+
*/
|
|
75
|
+
prefixDigest: {
|
|
76
|
+
bytes: number;
|
|
77
|
+
hash: string;
|
|
78
|
+
} | null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Reported to `onError` (and nothing is read) when `noFollow` is set and the
|
|
82
|
+
* tailed path is a symbolic link.
|
|
83
|
+
*/
|
|
84
|
+
export declare class JsonlTailSymlinkError extends Error {
|
|
85
|
+
readonly path: string;
|
|
86
|
+
readonly code = "JSONL_TAIL_SYMLINK";
|
|
87
|
+
constructor(path: string);
|
|
29
88
|
}
|
|
30
89
|
export interface JsonlTailOptions<T> {
|
|
31
90
|
path: string;
|
|
@@ -37,20 +96,81 @@ export interface JsonlTailOptions<T> {
|
|
|
37
96
|
onEvent: (event: T, line: JsonlTailLine) => void;
|
|
38
97
|
onBatchComplete?: (batch: JsonlTailBatch) => void;
|
|
39
98
|
onError?: (error: Error, line?: string) => void;
|
|
40
|
-
/** Called after the tail resets
|
|
99
|
+
/** Called after the tail resets (see `onRewrite`); events from the new position follow. */
|
|
41
100
|
onReset?: (reset: JsonlTailReset) => void;
|
|
42
101
|
/** Bytes read per chunk (default 1 MiB); one poll reads chunk by chunk to the end. */
|
|
43
102
|
maxReadBytes?: number;
|
|
44
103
|
/** Longest line parsed (default 16 MiB); a longer line is skipped and reported to `onError`. */
|
|
45
104
|
maxLineBytes?: number;
|
|
105
|
+
/**
|
|
106
|
+
* Where to continue after the file is truncated, rewritten, or replaced,
|
|
107
|
+
* and after `resumeFrom()` rejects a checkpoint. `'restart'` (default)
|
|
108
|
+
* replays from offset 0. `'end'` continues from the file's current end, or
|
|
109
|
+
* from the start of an unfinished final line as `startAtEnd` does. `onReset`
|
|
110
|
+
* fires either way.
|
|
111
|
+
*/
|
|
112
|
+
onRewrite?: JsonlTailRewritePolicy;
|
|
113
|
+
/**
|
|
114
|
+
* Refuse to follow a symbolic link at `path`: the file is opened with
|
|
115
|
+
* `O_NOFOLLOW` where the platform defines it (a no-op where it does not,
|
|
116
|
+
* such as Windows). A symlinked path is reported to `onError` as a
|
|
117
|
+
* {@link JsonlTailSymlinkError} and not read or watched. Only the final
|
|
118
|
+
* path component is checked; symlinked parent directories still resolve.
|
|
119
|
+
*/
|
|
120
|
+
noFollow?: boolean;
|
|
46
121
|
}
|
|
47
|
-
export interface JsonlTail {
|
|
122
|
+
export interface JsonlTail<T = unknown> {
|
|
48
123
|
readonly isActive: boolean;
|
|
49
124
|
start(): void;
|
|
50
125
|
stop(): void;
|
|
51
126
|
dispose(): void;
|
|
127
|
+
/**
|
|
128
|
+
* Reads every newly complete line now, synchronously, through the
|
|
129
|
+
* callbacks. While `read()` calls are queued or running, the call is
|
|
130
|
+
* deferred until the last of them settles.
|
|
131
|
+
*/
|
|
52
132
|
readNow(): void;
|
|
133
|
+
/**
|
|
134
|
+
* Reads like `readNow()`, with asynchronous I/O, and resolves with what it
|
|
135
|
+
* read; the callbacks fire as they do for `readNow()`. Never rejects for
|
|
136
|
+
* I/O or parse problems (those go to `onError`). Concurrent calls run one
|
|
137
|
+
* after another.
|
|
138
|
+
*/
|
|
139
|
+
read(): Promise<JsonlTailReadResult<T>>;
|
|
53
140
|
getOffset(): number;
|
|
54
141
|
seekTo(offset: number): void;
|
|
142
|
+
/** A JSON-serializable resume point at the committed offset; does no I/O. */
|
|
143
|
+
checkpoint(): JsonlTailCheckpoint;
|
|
144
|
+
/**
|
|
145
|
+
* Validates `checkpoint` (shape, file identity, prefix digest, and that its
|
|
146
|
+
* offset still follows a newline) against the file now at the path. On
|
|
147
|
+
* success reading continues from its offset and this returns true.
|
|
148
|
+
* Otherwise the `onRewrite` policy picks the position (0, or the file's
|
|
149
|
+
* current end) and this returns false; `onReset` is not called. Either way
|
|
150
|
+
* the position overrides `startAtEnd` on a later `start()`.
|
|
151
|
+
*/
|
|
152
|
+
resumeFrom(checkpoint: unknown): boolean;
|
|
55
153
|
}
|
|
56
|
-
export declare function createJsonlTail<T>(options: JsonlTailOptions<T>): JsonlTail
|
|
154
|
+
export declare function createJsonlTail<T>(options: JsonlTailOptions<T>): JsonlTail<T>;
|
|
155
|
+
/**
|
|
156
|
+
* Records a {@link JsonlTailCheckpoint} for byte `offset` of `filePath`
|
|
157
|
+
* without a tail, for readers that keep their own offset (a snapshot that
|
|
158
|
+
* stores a reader position, for example). The checkpoint carries the file's
|
|
159
|
+
* current device and inode and a digest of its first bytes, so
|
|
160
|
+
* {@link verifyJsonlCheckpoint} (or {@link JsonlTail.resumeFrom}) can later
|
|
161
|
+
* tell whether `offset` still refers to the same, append-only content.
|
|
162
|
+
*
|
|
163
|
+
* Returns `null` when the file is missing or unreadable, or when `offset` is
|
|
164
|
+
* not a resume-safe position in it: negative, past the end, or not 0 and not
|
|
165
|
+
* just after a newline. Follows symbolic links.
|
|
166
|
+
*/
|
|
167
|
+
export declare function captureJsonlCheckpoint(filePath: string, offset: number): JsonlTailCheckpoint | null;
|
|
168
|
+
/**
|
|
169
|
+
* Whether `checkpoint` (any value, typically parsed from JSON) is a valid
|
|
170
|
+
* {@link JsonlTailCheckpoint} that still describes `filePath`: same device
|
|
171
|
+
* and inode, the recorded first bytes unchanged, and its offset within the
|
|
172
|
+
* file and just after a newline (or 0). This is the check
|
|
173
|
+
* {@link JsonlTail.resumeFrom} applies. `false` on any error, including a
|
|
174
|
+
* missing file. Follows symbolic links.
|
|
175
|
+
*/
|
|
176
|
+
export declare function verifyJsonlCheckpoint(filePath: string, checkpoint: unknown): boolean;
|