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.
Files changed (151) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +188 -27
  3. package/dist/aggregation/EventAggregator.d.ts +19 -0
  4. package/dist/aggregation/EventAggregator.js +174 -35
  5. package/dist/aggregation/snapshot.d.ts +37 -8
  6. package/dist/aggregation/snapshot.js +31 -9
  7. package/dist/aggregation/types.d.ts +13 -0
  8. package/dist/authStatus.d.ts +54 -0
  9. package/dist/authStatus.js +91 -0
  10. package/dist/browser.d.ts +16 -6
  11. package/dist/browser.js +24 -2
  12. package/dist/codex.d.ts +23 -0
  13. package/dist/codex.js +44 -0
  14. package/dist/codexProfiles.d.ts +1 -0
  15. package/dist/codexProfiles.js +8 -11
  16. package/dist/context/sessionContext.js +4 -0
  17. package/dist/esm/aggregation/EventAggregator.js +174 -35
  18. package/dist/esm/aggregation/snapshot.js +31 -9
  19. package/dist/esm/authStatus.js +87 -0
  20. package/dist/esm/browser.js +5 -2
  21. package/dist/esm/codex.js +18 -0
  22. package/dist/esm/codexProfiles.js +8 -11
  23. package/dist/esm/context/sessionContext.js +4 -0
  24. package/dist/esm/formatters/toolSummary.js +4 -1
  25. package/dist/esm/formatting.js +10 -1
  26. package/dist/esm/index.js +11 -6
  27. package/dist/esm/modelCatalog.js +7 -5
  28. package/dist/esm/modelContext.js +4 -3
  29. package/dist/esm/modelInfo.js +425 -47
  30. package/dist/esm/node.js +9 -3
  31. package/dist/esm/observedContextWindows.js +2 -1
  32. package/dist/esm/ownKey.js +26 -0
  33. package/dist/esm/parsers/codexParser.js +351 -54
  34. package/dist/esm/parsers/codexRolloutOrigin.js +107 -0
  35. package/dist/esm/parsers/openCodeParser.js +85 -22
  36. package/dist/esm/parsers/subagentScanner.js +33 -12
  37. package/dist/esm/parsers/subagentTraceParser.js +97 -37
  38. package/dist/esm/pricing.js +18 -0
  39. package/dist/esm/pricingCatalog.js +17 -9
  40. package/dist/esm/promptHistory.js +66 -4
  41. package/dist/esm/providerFailure.js +124 -3
  42. package/dist/esm/providers/claudeCode.js +10 -2
  43. package/dist/esm/providers/codex.js +84 -62
  44. package/dist/esm/providers/codexDatabase.js +3 -19
  45. package/dist/esm/providers/codexRollouts.js +24 -1
  46. package/dist/esm/providers/openCode.js +9 -17
  47. package/dist/esm/report/htmlReportGenerator.js +42 -5
  48. package/dist/esm/report/transcriptParser.js +13 -11
  49. package/dist/esm/schemas/providerFailure.js +3 -1
  50. package/dist/esm/schemas/sessionEvent.js +35 -14
  51. package/dist/esm/schemas/transcript.js +4 -0
  52. package/dist/esm/sessionPromptHistory.js +2 -2
  53. package/dist/esm/statusline/payload.js +9 -0
  54. package/dist/esm/subagentSpawn.js +163 -0
  55. package/dist/esm/testing/credentialGuard.js +87 -5
  56. package/dist/esm/toolCallOrigins.js +310 -0
  57. package/dist/esm/transcript.js +46 -3
  58. package/dist/esm/turns/assistantTurn.js +3 -2
  59. package/dist/esm/usage/claudeUsageDedupe.js +68 -0
  60. package/dist/esm/usage/usageEvents.js +146 -17
  61. package/dist/esm/usage/usageReports.js +39 -6
  62. package/dist/esm/watchers/eventBridge.js +15 -0
  63. package/dist/esm/watchers/factory.js +5 -3
  64. package/dist/esm/watchers/jsonlTail.js +442 -58
  65. package/dist/esm/watchers/jsonlWatcher.js +51 -4
  66. package/dist/esm/watchers/providerReaderWatcher.js +13 -0
  67. package/dist/esm/zaiQuota.js +2 -1
  68. package/dist/formatters/toolSummary.js +4 -1
  69. package/dist/formatting.d.ts +26 -1
  70. package/dist/formatting.js +11 -1
  71. package/dist/index.d.ts +22 -11
  72. package/dist/index.js +44 -12
  73. package/dist/modelCatalog.js +7 -5
  74. package/dist/modelContext.js +4 -3
  75. package/dist/modelInfo.d.ts +185 -16
  76. package/dist/modelInfo.js +434 -49
  77. package/dist/node.d.ts +16 -5
  78. package/dist/node.js +33 -2
  79. package/dist/observedContextWindows.js +2 -1
  80. package/dist/ownKey.d.ts +16 -0
  81. package/dist/ownKey.js +30 -0
  82. package/dist/parsers/codexParser.d.ts +118 -0
  83. package/dist/parsers/codexParser.js +352 -54
  84. package/dist/parsers/codexRolloutOrigin.d.ts +49 -0
  85. package/dist/parsers/codexRolloutOrigin.js +111 -0
  86. package/dist/parsers/openCodeParser.d.ts +38 -0
  87. package/dist/parsers/openCodeParser.js +88 -22
  88. package/dist/parsers/subagentScanner.d.ts +19 -6
  89. package/dist/parsers/subagentScanner.js +33 -12
  90. package/dist/parsers/subagentTraceParser.d.ts +25 -5
  91. package/dist/parsers/subagentTraceParser.js +97 -37
  92. package/dist/pricing.d.ts +22 -0
  93. package/dist/pricing.js +50 -0
  94. package/dist/pricingCatalog.d.ts +10 -0
  95. package/dist/pricingCatalog.js +16 -8
  96. package/dist/promptHistory.d.ts +65 -4
  97. package/dist/promptHistory.js +68 -5
  98. package/dist/providerFailure.d.ts +44 -0
  99. package/dist/providerFailure.js +126 -4
  100. package/dist/providers/claudeCode.js +9 -1
  101. package/dist/providers/codex.d.ts +3 -22
  102. package/dist/providers/codex.js +85 -64
  103. package/dist/providers/codexDatabase.d.ts +3 -2
  104. package/dist/providers/codexDatabase.js +4 -22
  105. package/dist/providers/codexRollouts.d.ts +22 -0
  106. package/dist/providers/codexRollouts.js +24 -0
  107. package/dist/providers/openCode.js +8 -16
  108. package/dist/providers/types.d.ts +14 -0
  109. package/dist/report/htmlReportGenerator.js +42 -5
  110. package/dist/report/transcriptParser.d.ts +10 -1
  111. package/dist/report/transcriptParser.js +13 -11
  112. package/dist/schemas/index.d.ts +1 -0
  113. package/dist/schemas/providerFailure.d.ts +39 -0
  114. package/dist/schemas/providerFailure.js +2 -0
  115. package/dist/schemas/sessionEvent.d.ts +27 -10
  116. package/dist/schemas/sessionEvent.js +35 -14
  117. package/dist/schemas/transcript.d.ts +2 -0
  118. package/dist/schemas/transcript.js +4 -0
  119. package/dist/sessionPromptHistory.d.ts +7 -1
  120. package/dist/sessionPromptHistory.js +1 -1
  121. package/dist/statusline/payload.d.ts +10 -0
  122. package/dist/statusline/payload.js +14 -0
  123. package/dist/subagentSpawn.d.ts +80 -0
  124. package/dist/subagentSpawn.js +174 -0
  125. package/dist/testing/credentialGuard.d.ts +14 -0
  126. package/dist/testing/credentialGuard.js +87 -5
  127. package/dist/toolCallOrigins.d.ts +70 -0
  128. package/dist/toolCallOrigins.js +346 -0
  129. package/dist/transcript.d.ts +12 -0
  130. package/dist/transcript.js +46 -3
  131. package/dist/turns/assistantTurn.d.ts +1 -0
  132. package/dist/turns/assistantTurn.js +3 -2
  133. package/dist/types/opencode.d.ts +2 -0
  134. package/dist/usage/claudeUsageDedupe.d.ts +31 -0
  135. package/dist/usage/claudeUsageDedupe.js +68 -0
  136. package/dist/usage/usageEvents.d.ts +54 -6
  137. package/dist/usage/usageEvents.js +146 -17
  138. package/dist/usage/usageReports.d.ts +35 -4
  139. package/dist/usage/usageReports.js +39 -6
  140. package/dist/watchers/eventBridge.js +15 -0
  141. package/dist/watchers/factory.d.ts +6 -0
  142. package/dist/watchers/factory.js +5 -3
  143. package/dist/watchers/jsonlTail.d.ts +124 -4
  144. package/dist/watchers/jsonlTail.js +446 -59
  145. package/dist/watchers/jsonlWatcher.d.ts +18 -1
  146. package/dist/watchers/jsonlWatcher.js +50 -3
  147. package/dist/watchers/providerReaderWatcher.d.ts +7 -0
  148. package/dist/watchers/providerReaderWatcher.js +13 -0
  149. package/dist/watchers/types.d.ts +12 -0
  150. package/dist/zaiQuota.js +2 -1
  151. 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 model = byModel ? event.model : null;
92
- const id = [key, provider ?? '', project ?? '', model ?? ''].join('|');
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: isSession ? event.sessionId : null,
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(event.model, (acc.modelTokens.get(event.model) ?? 0) + event.tokens.totalTokens);
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
@@ -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
- /** The tail started over from the beginning of the file. */
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 to offset 0; events from the start follow. */
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;