sidekick-shared 0.29.0 → 0.30.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 +48 -0
- package/README.md +198 -56
- package/dist/aggregation/EventAggregator.d.ts +96 -2
- package/dist/aggregation/EventAggregator.js +535 -42
- package/dist/aggregation/snapshot.d.ts +37 -8
- package/dist/aggregation/snapshot.js +31 -9
- package/dist/aggregation/types.d.ts +38 -1
- package/dist/browser.d.ts +9 -1
- package/dist/browser.js +29 -3
- package/dist/context/sessionContext.js +8 -0
- package/dist/esm/aggregation/EventAggregator.js +535 -42
- package/dist/esm/aggregation/snapshot.js +31 -9
- package/dist/esm/browser.js +3 -0
- package/dist/esm/context/sessionContext.js +8 -0
- package/dist/esm/fileContinuity.js +149 -0
- package/dist/esm/formatters/toolSummary.js +4 -1
- package/dist/esm/index.js +7 -2
- package/dist/esm/modelCatalog.js +5 -4
- package/dist/esm/modelContext.js +4 -3
- package/dist/esm/modelInfo.js +4 -3
- package/dist/esm/node.js +3 -1
- package/dist/esm/observedContextWindows.js +2 -1
- package/dist/esm/ownKey.js +26 -0
- package/dist/esm/parsers/codexParser.js +372 -7
- package/dist/esm/parsers/openCodeParser.js +2 -1
- package/dist/esm/parsers/subagentEndState.js +61 -0
- package/dist/esm/parsers/subagentScanner.js +37 -12
- package/dist/esm/parsers/subagentTraceParser.js +103 -37
- package/dist/esm/pricingCatalog.js +3 -2
- package/dist/esm/promptHistory.js +22 -11
- package/dist/esm/providers/claudeCode.js +60 -29
- package/dist/esm/providers/codex.js +235 -201
- package/dist/esm/providers/codexCompressedReader.js +184 -0
- package/dist/esm/providers/codexDatabase.js +47 -2
- package/dist/esm/providers/codexRollouts.js +20 -2
- package/dist/esm/providers/codexSubagents.js +456 -0
- package/dist/esm/providers/compressedRollouts.js +330 -0
- package/dist/esm/providers/factory.js +4 -1
- package/dist/esm/providers/rolloutWalker.js +32 -4
- package/dist/esm/schemas/sessionEvent.js +63 -14
- package/dist/esm/sessionMonitor.js +7 -1
- package/dist/esm/sessionPreviews.js +9 -0
- package/dist/esm/sessionPromptHistory.js +5 -3
- package/dist/esm/subagentActivity.js +125 -0
- package/dist/esm/subagentDisplayState.js +81 -0
- package/dist/esm/subagentSpawn.js +363 -0
- package/dist/esm/testing/codexSubagentFixtures.js +336 -0
- package/dist/esm/toolCallOrigins.js +17 -10
- package/dist/esm/toolCategory.js +6 -2
- package/dist/esm/transcript.js +8 -0
- package/dist/esm/turns/assistantTurn.js +6 -2
- package/dist/esm/usage/claudeUsageDedupe.js +68 -0
- package/dist/esm/usage/usageEvents.js +4 -1
- package/dist/esm/watchers/eventBridge.js +45 -0
- package/dist/esm/watchers/factory.js +5 -3
- package/dist/esm/watchers/jsonlTail.js +82 -48
- package/dist/esm/watchers/jsonlWatcher.js +61 -4
- package/dist/esm/watchers/providerReaderWatcher.js +18 -4
- package/dist/esm/zaiQuota.js +2 -1
- package/dist/fileContinuity.d.ts +95 -0
- package/dist/fileContinuity.js +187 -0
- package/dist/formatters/toolSummary.js +4 -1
- package/dist/index.d.ts +19 -7
- package/dist/index.js +48 -10
- package/dist/modelCatalog.js +5 -4
- package/dist/modelContext.js +4 -3
- package/dist/modelInfo.js +4 -3
- package/dist/node.d.ts +5 -1
- package/dist/node.js +18 -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 +107 -0
- package/dist/parsers/codexParser.js +373 -7
- package/dist/parsers/openCodeParser.js +2 -1
- package/dist/parsers/subagentEndState.d.ts +20 -0
- package/dist/parsers/subagentEndState.js +65 -0
- package/dist/parsers/subagentScanner.d.ts +19 -6
- package/dist/parsers/subagentScanner.js +37 -12
- package/dist/parsers/subagentTraceParser.d.ts +33 -5
- package/dist/parsers/subagentTraceParser.js +103 -37
- package/dist/pricingCatalog.js +3 -2
- package/dist/promptHistory.d.ts +13 -3
- package/dist/promptHistory.js +22 -11
- package/dist/providers/claudeCode.js +59 -28
- package/dist/providers/codex.d.ts +35 -5
- package/dist/providers/codex.js +232 -198
- package/dist/providers/codexCompressedReader.d.ts +58 -0
- package/dist/providers/codexCompressedReader.js +221 -0
- package/dist/providers/codexDatabase.d.ts +25 -0
- package/dist/providers/codexDatabase.js +48 -2
- package/dist/providers/codexRollouts.d.ts +16 -2
- package/dist/providers/codexRollouts.js +20 -2
- package/dist/providers/codexSubagents.d.ts +66 -0
- package/dist/providers/codexSubagents.js +495 -0
- package/dist/providers/compressedRollouts.d.ts +114 -0
- package/dist/providers/compressedRollouts.js +380 -0
- package/dist/providers/factory.d.ts +5 -0
- package/dist/providers/factory.js +4 -1
- package/dist/providers/rolloutWalker.d.ts +15 -2
- package/dist/providers/rolloutWalker.js +33 -4
- package/dist/providers/types.d.ts +36 -1
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/sessionEvent.d.ts +51 -10
- package/dist/schemas/sessionEvent.js +63 -14
- package/dist/sessionMonitor.d.ts +6 -0
- package/dist/sessionMonitor.js +7 -1
- package/dist/sessionPreviews.js +9 -0
- package/dist/sessionPromptHistory.d.ts +5 -0
- package/dist/sessionPromptHistory.js +5 -36
- package/dist/subagentActivity.d.ts +91 -0
- package/dist/subagentActivity.js +132 -0
- package/dist/subagentDisplayState.d.ts +81 -0
- package/dist/subagentDisplayState.js +86 -0
- package/dist/subagentSpawn.d.ts +175 -0
- package/dist/subagentSpawn.js +385 -0
- package/dist/testing/codexSubagentFixtures.d.ts +48 -0
- package/dist/testing/codexSubagentFixtures.js +344 -0
- package/dist/toolCallOrigins.d.ts +5 -0
- package/dist/toolCallOrigins.js +17 -10
- package/dist/toolCategory.js +6 -2
- package/dist/transcript.js +8 -0
- package/dist/turns/assistantTurn.d.ts +4 -0
- package/dist/turns/assistantTurn.js +6 -2
- package/dist/types/codex.d.ts +71 -2
- package/dist/types/sessionEvent.d.ts +38 -3
- package/dist/usage/claudeUsageDedupe.d.ts +31 -0
- package/dist/usage/claudeUsageDedupe.js +68 -0
- package/dist/usage/usageEvents.d.ts +4 -1
- package/dist/usage/usageEvents.js +4 -1
- package/dist/watchers/eventBridge.js +45 -0
- package/dist/watchers/factory.d.ts +6 -0
- package/dist/watchers/factory.js +5 -3
- package/dist/watchers/jsonlTail.d.ts +27 -4
- package/dist/watchers/jsonlTail.js +84 -48
- package/dist/watchers/jsonlWatcher.d.ts +22 -1
- package/dist/watchers/jsonlWatcher.js +60 -3
- package/dist/watchers/providerReaderWatcher.d.ts +7 -0
- package/dist/watchers/providerReaderWatcher.js +18 -4
- package/dist/watchers/types.d.ts +12 -0
- package/dist/zaiQuota.js +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Reads Codex rollouts that Codex compressed.
|
|
4
|
+
*
|
|
5
|
+
* Codex compresses older rollouts in place to `rollout-<ts>-<id>.jsonl.zst`
|
|
6
|
+
* (one Zstandard frame), while its state database keeps the `.jsonl` path.
|
|
7
|
+
* Node ships `zlib.zstdDecompressSync` from 22.15 / 23.8; on older runtimes
|
|
8
|
+
* (Node 20, older VS Code Electron builds) compressed rollouts cannot be read
|
|
9
|
+
* and are skipped, reported once as a `read_failed` diagnostic.
|
|
10
|
+
*
|
|
11
|
+
* Every reader here takes either form: a `.jsonl` path reads as before, a
|
|
12
|
+
* `.jsonl.zst` path is decompressed. Compressed rollouts are complete (Codex
|
|
13
|
+
* only compresses rollouts it no longer writes), so they are read whole and
|
|
14
|
+
* never tailed.
|
|
15
|
+
*
|
|
16
|
+
* Node-only: reads files.
|
|
17
|
+
*
|
|
18
|
+
* @module providers/compressedRollouts
|
|
19
|
+
*/
|
|
20
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
23
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
24
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
25
|
+
}
|
|
26
|
+
Object.defineProperty(o, k2, desc);
|
|
27
|
+
}) : (function(o, m, k, k2) {
|
|
28
|
+
if (k2 === undefined) k2 = k;
|
|
29
|
+
o[k2] = m[k];
|
|
30
|
+
}));
|
|
31
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
32
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
33
|
+
}) : function(o, v) {
|
|
34
|
+
o["default"] = v;
|
|
35
|
+
});
|
|
36
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
37
|
+
var ownKeys = function(o) {
|
|
38
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
39
|
+
var ar = [];
|
|
40
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
41
|
+
return ar;
|
|
42
|
+
};
|
|
43
|
+
return ownKeys(o);
|
|
44
|
+
};
|
|
45
|
+
return function (mod) {
|
|
46
|
+
if (mod && mod.__esModule) return mod;
|
|
47
|
+
var result = {};
|
|
48
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
49
|
+
__setModuleDefault(result, mod);
|
|
50
|
+
return result;
|
|
51
|
+
};
|
|
52
|
+
})();
|
|
53
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
|
+
exports.__compressedRolloutTesting = exports.COMPRESSED_ROLLOUT_SUFFIX = void 0;
|
|
55
|
+
exports.isCompressedRolloutPath = isCompressedRolloutPath;
|
|
56
|
+
exports.isZstdDecompressionAvailable = isZstdDecompressionAvailable;
|
|
57
|
+
exports.canReadCompressedRollouts = canReadCompressedRollouts;
|
|
58
|
+
exports.resetCompressedRolloutDiagnostics = resetCompressedRolloutDiagnostics;
|
|
59
|
+
exports.readRolloutBuffer = readRolloutBuffer;
|
|
60
|
+
exports.readRolloutText = readRolloutText;
|
|
61
|
+
exports.readRolloutBufferAsync = readRolloutBufferAsync;
|
|
62
|
+
exports.readRolloutHead = readRolloutHead;
|
|
63
|
+
exports.readRolloutHeadAsync = readRolloutHeadAsync;
|
|
64
|
+
exports.readRolloutFirstLines = readRolloutFirstLines;
|
|
65
|
+
exports.zstdFrameContentSize = zstdFrameContentSize;
|
|
66
|
+
exports.statRolloutContent = statRolloutContent;
|
|
67
|
+
exports.openRolloutReadHandle = openRolloutReadHandle;
|
|
68
|
+
exports.resolveRolloutPath = resolveRolloutPath;
|
|
69
|
+
const fs = __importStar(require("fs"));
|
|
70
|
+
const zlib = __importStar(require("zlib"));
|
|
71
|
+
/** File-name suffix of a compressed rollout. */
|
|
72
|
+
exports.COMPRESSED_ROLLOUT_SUFFIX = '.jsonl.zst';
|
|
73
|
+
/** `undefined`: use the runtime's; `null`: behave as if the runtime lacked it. */
|
|
74
|
+
let decompressOverride;
|
|
75
|
+
function zstdDecompressSync() {
|
|
76
|
+
if (decompressOverride !== undefined)
|
|
77
|
+
return decompressOverride;
|
|
78
|
+
const fn = zlib.zstdDecompressSync;
|
|
79
|
+
return typeof fn === 'function' ? fn.bind(zlib) : null;
|
|
80
|
+
}
|
|
81
|
+
/** Test hooks; not part of the public entry points. */
|
|
82
|
+
exports.__compressedRolloutTesting = {
|
|
83
|
+
/** Replace the runtime's `zstdDecompressSync` (`null` = unavailable, `undefined` = restore). */
|
|
84
|
+
setDecompress(fn) {
|
|
85
|
+
decompressOverride = fn;
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
/** Whether a path (or file name) names a compressed rollout (`*.jsonl.zst`). */
|
|
89
|
+
function isCompressedRolloutPath(filePath) {
|
|
90
|
+
return filePath.endsWith(exports.COMPRESSED_ROLLOUT_SUFFIX);
|
|
91
|
+
}
|
|
92
|
+
/** Whether this runtime can decompress Zstandard (`zlib.zstdDecompressSync`). */
|
|
93
|
+
function isZstdDecompressionAvailable() {
|
|
94
|
+
return zstdDecompressSync() !== null;
|
|
95
|
+
}
|
|
96
|
+
const UNAVAILABLE_MESSAGE = 'Compressed Codex rollouts (.jsonl.zst) were skipped: this Node.js runtime has no zlib.zstdDecompressSync (Node 22.15 or later).';
|
|
97
|
+
/** The runtime-limit diagnostic is reported once per process unless asked again. */
|
|
98
|
+
let reportedUnavailable = false;
|
|
99
|
+
function reportUnavailable(options) {
|
|
100
|
+
if (!options?.onDiagnostic || reportedUnavailable)
|
|
101
|
+
return;
|
|
102
|
+
reportedUnavailable = true;
|
|
103
|
+
options.onDiagnostic({
|
|
104
|
+
providerId: 'codex',
|
|
105
|
+
kind: 'read_failed',
|
|
106
|
+
severity: 'info',
|
|
107
|
+
phase: 'read',
|
|
108
|
+
message: UNAVAILABLE_MESSAGE,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
function reportCorrupt(options, error) {
|
|
112
|
+
options?.onDiagnostic?.({
|
|
113
|
+
providerId: 'codex',
|
|
114
|
+
kind: 'read_failed',
|
|
115
|
+
severity: 'warning',
|
|
116
|
+
phase: 'read',
|
|
117
|
+
message: `A compressed Codex rollout could not be decompressed: ${error instanceof Error ? error.message : String(error)}`,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Whether compressed rollouts can be read here; when they cannot, reports
|
|
122
|
+
* the runtime limit through `onDiagnostic` (once per process). Callers that
|
|
123
|
+
* list rollouts use it to leave `.jsonl.zst` files out instead of listing
|
|
124
|
+
* sessions they cannot read.
|
|
125
|
+
*/
|
|
126
|
+
function canReadCompressedRollouts(options) {
|
|
127
|
+
if (zstdDecompressSync())
|
|
128
|
+
return true;
|
|
129
|
+
reportUnavailable(options);
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
/** @internal Test hook: report the runtime limit again. */
|
|
133
|
+
function resetCompressedRolloutDiagnostics() {
|
|
134
|
+
reportedUnavailable = false;
|
|
135
|
+
}
|
|
136
|
+
function decompress(input, options) {
|
|
137
|
+
const fn = zstdDecompressSync();
|
|
138
|
+
if (!fn) {
|
|
139
|
+
reportUnavailable(options);
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
return fn(input);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
reportCorrupt(options, error);
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The whole content of a rollout: a `.jsonl` file as stored, a `.jsonl.zst`
|
|
152
|
+
* file decompressed. `null` when the file cannot be read or decompressed
|
|
153
|
+
* (the latter reported through `onDiagnostic`).
|
|
154
|
+
*/
|
|
155
|
+
function readRolloutBuffer(filePath, options) {
|
|
156
|
+
let raw;
|
|
157
|
+
try {
|
|
158
|
+
raw = fs.readFileSync(filePath);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
return isCompressedRolloutPath(filePath) ? decompress(raw, options) : raw;
|
|
164
|
+
}
|
|
165
|
+
/** {@link readRolloutBuffer} decoded as UTF-8. */
|
|
166
|
+
function readRolloutText(filePath, options) {
|
|
167
|
+
return readRolloutBuffer(filePath, options)?.toString('utf8') ?? null;
|
|
168
|
+
}
|
|
169
|
+
/** Async twin of {@link readRolloutBuffer}; decompression itself is synchronous. */
|
|
170
|
+
async function readRolloutBufferAsync(filePath, options) {
|
|
171
|
+
let raw;
|
|
172
|
+
try {
|
|
173
|
+
raw = await fs.promises.readFile(filePath);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
return isCompressedRolloutPath(filePath) ? decompress(raw, options) : raw;
|
|
179
|
+
}
|
|
180
|
+
/** Compressed bytes read per step while looking for a decompressed prefix. */
|
|
181
|
+
const HEAD_STEP_BYTES = 64 * 1024;
|
|
182
|
+
/**
|
|
183
|
+
* Decompresses only as much of a compressed prefix as is needed for
|
|
184
|
+
* `maxBytes` of content: a truncated Zstandard frame decompresses to the
|
|
185
|
+
* content of its complete blocks.
|
|
186
|
+
*/
|
|
187
|
+
function decompressHead(readCompressed, compressedSize, maxBytes, options) {
|
|
188
|
+
const fn = zstdDecompressSync();
|
|
189
|
+
if (!fn) {
|
|
190
|
+
reportUnavailable(options);
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
let length = Math.min(compressedSize, HEAD_STEP_BYTES);
|
|
194
|
+
for (;;) {
|
|
195
|
+
let out;
|
|
196
|
+
try {
|
|
197
|
+
out = fn(readCompressed(length));
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
if (length >= compressedSize) {
|
|
201
|
+
reportCorrupt(options, error);
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
out = Buffer.alloc(0);
|
|
205
|
+
}
|
|
206
|
+
if (out.length >= maxBytes || length >= compressedSize)
|
|
207
|
+
return out.subarray(0, maxBytes);
|
|
208
|
+
length = Math.min(compressedSize, length * 4);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Up to `maxBytes` of a rollout's content from its start (decompressed for
|
|
213
|
+
* a `.jsonl.zst`). An empty buffer when the file cannot be read.
|
|
214
|
+
*/
|
|
215
|
+
function readRolloutHead(filePath, maxBytes, options) {
|
|
216
|
+
let fd;
|
|
217
|
+
try {
|
|
218
|
+
fd = fs.openSync(filePath, 'r');
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return Buffer.alloc(0);
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
const size = fs.fstatSync(fd).size;
|
|
225
|
+
const readPrefix = (length) => {
|
|
226
|
+
const buffer = Buffer.alloc(length);
|
|
227
|
+
const bytesRead = fs.readSync(fd, buffer, 0, length, 0);
|
|
228
|
+
return buffer.subarray(0, bytesRead);
|
|
229
|
+
};
|
|
230
|
+
if (!isCompressedRolloutPath(filePath))
|
|
231
|
+
return readPrefix(Math.min(size, Math.max(0, maxBytes)));
|
|
232
|
+
return decompressHead(readPrefix, size, Math.max(0, maxBytes), options) ?? Buffer.alloc(0);
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return Buffer.alloc(0);
|
|
236
|
+
}
|
|
237
|
+
finally {
|
|
238
|
+
fs.closeSync(fd);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/** Async twin of {@link readRolloutHead}. */
|
|
242
|
+
async function readRolloutHeadAsync(filePath, maxBytes, options) {
|
|
243
|
+
if (!isCompressedRolloutPath(filePath)) {
|
|
244
|
+
let handle = null;
|
|
245
|
+
try {
|
|
246
|
+
handle = await fs.promises.open(filePath, 'r');
|
|
247
|
+
const size = (await handle.stat()).size;
|
|
248
|
+
const length = Math.min(size, Math.max(0, maxBytes));
|
|
249
|
+
const buffer = Buffer.alloc(length);
|
|
250
|
+
const { bytesRead } = await handle.read(buffer, 0, length, 0);
|
|
251
|
+
return buffer.subarray(0, bytesRead);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
return Buffer.alloc(0);
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
await handle?.close().catch(() => undefined);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// Compressed prefixes are small; one synchronous read keeps it simple.
|
|
261
|
+
return readRolloutHead(filePath, maxBytes, options);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* The first `maxLines` complete lines of a rollout (decompressed for a
|
|
265
|
+
* `.jsonl.zst`) within `maxBytes` of content; a trailing line without a
|
|
266
|
+
* newline counts when the whole content fit.
|
|
267
|
+
*/
|
|
268
|
+
function readRolloutFirstLines(filePath, maxLines, maxBytes = 256 * 1024, options) {
|
|
269
|
+
const head = readRolloutHead(filePath, maxBytes + 1, options);
|
|
270
|
+
const complete = head.length <= maxBytes;
|
|
271
|
+
const text = head.subarray(0, maxBytes).toString('utf8');
|
|
272
|
+
const parts = text.split('\n');
|
|
273
|
+
const tail = parts.pop() ?? '';
|
|
274
|
+
const lines = parts.slice(0, maxLines);
|
|
275
|
+
if (lines.length < maxLines && complete && tail.trim())
|
|
276
|
+
lines.push(tail);
|
|
277
|
+
return lines;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* The content size a Zstandard frame header declares (`Frame_Content_Size`),
|
|
281
|
+
* or `null` when the header omits it or is not a Zstandard frame. Skippable
|
|
282
|
+
* frames before it are not followed.
|
|
283
|
+
*/
|
|
284
|
+
function zstdFrameContentSize(header) {
|
|
285
|
+
if (header.length < 5 || header.readUInt32LE(0) !== 0xfd2fb528)
|
|
286
|
+
return null;
|
|
287
|
+
const descriptor = header[4];
|
|
288
|
+
const fcsFlag = descriptor >> 6;
|
|
289
|
+
const singleSegment = (descriptor >> 5) & 1;
|
|
290
|
+
const dictionaryIdFlag = descriptor & 3;
|
|
291
|
+
const offset = 5 + (singleSegment ? 0 : 1) + [0, 1, 2, 4][dictionaryIdFlag];
|
|
292
|
+
const fcsBytes = fcsFlag === 0 ? (singleSegment ? 1 : 0) : [0, 2, 4, 8][fcsFlag];
|
|
293
|
+
if (fcsBytes === 0 || header.length < offset + fcsBytes)
|
|
294
|
+
return null;
|
|
295
|
+
switch (fcsBytes) {
|
|
296
|
+
case 1:
|
|
297
|
+
return header[offset];
|
|
298
|
+
case 2:
|
|
299
|
+
return header.readUInt16LE(offset) + 256;
|
|
300
|
+
case 4:
|
|
301
|
+
return header.readUInt32LE(offset);
|
|
302
|
+
default: {
|
|
303
|
+
const value = header.readBigUInt64LE(offset);
|
|
304
|
+
return value <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(value) : null;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Stat of a rollout's content: the file's own for a `.jsonl`; for a
|
|
310
|
+
* `.jsonl.zst`, the decompressed size (from the frame header, else by
|
|
311
|
+
* decompressing) with the compressed file's mtime. Rejects like `fs.stat`
|
|
312
|
+
* when the file is missing; a compressed rollout that cannot be read
|
|
313
|
+
* rejects too.
|
|
314
|
+
*/
|
|
315
|
+
async function statRolloutContent(filePath, options) {
|
|
316
|
+
const stat = await fs.promises.stat(filePath);
|
|
317
|
+
if (!isCompressedRolloutPath(filePath))
|
|
318
|
+
return { size: stat.size, mtimeMs: stat.mtimeMs };
|
|
319
|
+
let declared = null;
|
|
320
|
+
let handle = null;
|
|
321
|
+
try {
|
|
322
|
+
handle = await fs.promises.open(filePath, 'r');
|
|
323
|
+
const header = Buffer.alloc(18);
|
|
324
|
+
const { bytesRead } = await handle.read(header, 0, header.length, 0);
|
|
325
|
+
declared = zstdFrameContentSize(header.subarray(0, bytesRead));
|
|
326
|
+
}
|
|
327
|
+
finally {
|
|
328
|
+
await handle?.close().catch(() => undefined);
|
|
329
|
+
}
|
|
330
|
+
if (declared !== null && isZstdDecompressionAvailable()) {
|
|
331
|
+
return { size: declared, mtimeMs: stat.mtimeMs };
|
|
332
|
+
}
|
|
333
|
+
const content = await readRolloutBufferAsync(filePath, options);
|
|
334
|
+
if (!content)
|
|
335
|
+
throw new Error('compressed rollout could not be read');
|
|
336
|
+
return { size: content.length, mtimeMs: stat.mtimeMs };
|
|
337
|
+
}
|
|
338
|
+
/** A read-only handle over a buffer, for scanning decompressed content. */
|
|
339
|
+
function bufferHandle(content) {
|
|
340
|
+
return {
|
|
341
|
+
async read(buffer, offset, length, position) {
|
|
342
|
+
if (position >= content.length || length <= 0)
|
|
343
|
+
return { bytesRead: 0 };
|
|
344
|
+
const bytesRead = content.copy(buffer, offset, position, Math.min(content.length, position + length));
|
|
345
|
+
return { bytesRead };
|
|
346
|
+
},
|
|
347
|
+
async close() { },
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Opens a rollout for positional reads over its content: a file handle for
|
|
352
|
+
* a `.jsonl`, the decompressed content for a `.jsonl.zst` (with `size`, the
|
|
353
|
+
* content length). Rejects when the file cannot be opened or decompressed.
|
|
354
|
+
*/
|
|
355
|
+
async function openRolloutReadHandle(filePath, options) {
|
|
356
|
+
if (!isCompressedRolloutPath(filePath)) {
|
|
357
|
+
return { handle: await fs.promises.open(filePath, 'r'), size: null };
|
|
358
|
+
}
|
|
359
|
+
const content = await readRolloutBufferAsync(filePath, options);
|
|
360
|
+
if (!content)
|
|
361
|
+
throw new Error('compressed rollout could not be read');
|
|
362
|
+
return { handle: bufferHandle(content), size: content.length };
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* The path of a rollout as stored now: `filePath` when it exists, else its
|
|
366
|
+
* compressed sibling (`<filePath>.zst`) when `includeCompressed` is set and
|
|
367
|
+
* that exists. `null` when neither does.
|
|
368
|
+
*/
|
|
369
|
+
function resolveRolloutPath(filePath, options = {}) {
|
|
370
|
+
if (!filePath)
|
|
371
|
+
return null;
|
|
372
|
+
if (fs.existsSync(filePath))
|
|
373
|
+
return filePath;
|
|
374
|
+
if (options.includeCompressed && !isCompressedRolloutPath(filePath)) {
|
|
375
|
+
const compressed = `${filePath}.zst`;
|
|
376
|
+
if (filePath.endsWith('.jsonl') && fs.existsSync(compressed))
|
|
377
|
+
return compressed;
|
|
378
|
+
}
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
@@ -2,6 +2,11 @@ import type { ProviderId, SessionProviderBase, SessionProviderDiagnostic } from
|
|
|
2
2
|
export interface CreateSessionProvidersOptions {
|
|
3
3
|
providerIds?: readonly ProviderId[];
|
|
4
4
|
onDiagnostic?: (diagnostic: SessionProviderDiagnostic) => void;
|
|
5
|
+
/**
|
|
6
|
+
* Codex: also discover and read compressed rollouts (`….jsonl.zst`); see
|
|
7
|
+
* `CodexProviderOptions.includeCompressedRollouts`. Default false.
|
|
8
|
+
*/
|
|
9
|
+
includeCompressedRollouts?: boolean;
|
|
5
10
|
}
|
|
6
11
|
export interface CreateSessionProvidersResult {
|
|
7
12
|
providers: SessionProviderBase[];
|
|
@@ -40,7 +40,10 @@ function createSessionProviders(options = {}) {
|
|
|
40
40
|
? new claudeCode_1.ClaudeCodeProvider(providerOptions)
|
|
41
41
|
: providerId === 'opencode'
|
|
42
42
|
? new openCode_1.OpenCodeProvider(providerOptions)
|
|
43
|
-
: new codex_1.CodexProvider(
|
|
43
|
+
: new codex_1.CodexProvider({
|
|
44
|
+
...providerOptions,
|
|
45
|
+
includeCompressedRollouts: options.includeCompressedRollouts === true,
|
|
46
|
+
}));
|
|
44
47
|
}
|
|
45
48
|
catch {
|
|
46
49
|
const diagnostic = {
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
* order over the whole history omit both (the `maxFiles` cap still applies);
|
|
16
16
|
* a live session missed this way is picked up by its next watch event.
|
|
17
17
|
*
|
|
18
|
+
* Codex compresses older rollouts to `rollout-…jsonl.zst`. Those are skipped
|
|
19
|
+
* unless `includeCompressed` is set; a compressed rollout whose `.jsonl` is
|
|
20
|
+
* still beside it is skipped either way (the `.jsonl` is the one to read).
|
|
21
|
+
*
|
|
18
22
|
* The async walker accepts a {@link DirectoryListingCache}: unchanged
|
|
19
23
|
* directories are then one stat each instead of a readdir plus a stat per
|
|
20
24
|
* file, which is what makes repeated discovery over a large history cheap.
|
|
@@ -45,6 +49,13 @@ export interface WalkRolloutFilesOptions {
|
|
|
45
49
|
sessionId?: string;
|
|
46
50
|
/** Include zero-byte files (default false). */
|
|
47
51
|
includeEmpty?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Also return compressed rollouts (`rollout-…jsonl.zst`; default false).
|
|
54
|
+
* Reading them needs `zlib.zstdDecompressSync` (see
|
|
55
|
+
* `providers/compressedRollouts`). A `cache` remembers which names a walk
|
|
56
|
+
* accepted, so do not share one between walks that differ in this option.
|
|
57
|
+
*/
|
|
58
|
+
includeCompressed?: boolean;
|
|
48
59
|
/** Reuse directory listings across walks (async walker only). */
|
|
49
60
|
cache?: DirectoryListingCache;
|
|
50
61
|
/** With `cache`: re-stat recently modified files even in unchanged directories. */
|
|
@@ -54,11 +65,13 @@ export interface WalkRolloutFilesOptions {
|
|
|
54
65
|
}
|
|
55
66
|
export declare const DEFAULT_ROLLOUT_WALK_MAX_DEPTH = 6;
|
|
56
67
|
export declare const DEFAULT_ROLLOUT_WALK_MAX_FILES = 20000;
|
|
57
|
-
/** Test if a filename is a Codex rollout file. */
|
|
68
|
+
/** Test if a filename is a Codex rollout file (`rollout-….jsonl`; not compressed ones). */
|
|
58
69
|
export declare function isRolloutFile(filename: string): boolean;
|
|
70
|
+
/** Test if a filename is a compressed Codex rollout (`rollout-….jsonl.zst`). */
|
|
71
|
+
export declare function isCompressedRolloutFile(filename: string): boolean;
|
|
59
72
|
/**
|
|
60
73
|
* Extract the session UUID from a rollout filename.
|
|
61
|
-
* Format: rollout-<timestamp>-<uuid>.jsonl -> <uuid>
|
|
74
|
+
* Format: rollout-<timestamp>-<uuid>.jsonl (or .jsonl.zst) -> <uuid>
|
|
62
75
|
*/
|
|
63
76
|
export declare function extractRolloutSessionId(filename: string): string;
|
|
64
77
|
/**
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
* order over the whole history omit both (the `maxFiles` cap still applies);
|
|
17
17
|
* a live session missed this way is picked up by its next watch event.
|
|
18
18
|
*
|
|
19
|
+
* Codex compresses older rollouts to `rollout-…jsonl.zst`. Those are skipped
|
|
20
|
+
* unless `includeCompressed` is set; a compressed rollout whose `.jsonl` is
|
|
21
|
+
* still beside it is skipped either way (the `.jsonl` is the one to read).
|
|
22
|
+
*
|
|
19
23
|
* The async walker accepts a {@link DirectoryListingCache}: unchanged
|
|
20
24
|
* directories are then one stat each instead of a readdir plus a stat per
|
|
21
25
|
* file, which is what makes repeated discovery over a large history cheap.
|
|
@@ -58,6 +62,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
58
62
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
59
63
|
exports.DEFAULT_ROLLOUT_WALK_MAX_FILES = exports.DEFAULT_ROLLOUT_WALK_MAX_DEPTH = void 0;
|
|
60
64
|
exports.isRolloutFile = isRolloutFile;
|
|
65
|
+
exports.isCompressedRolloutFile = isCompressedRolloutFile;
|
|
61
66
|
exports.extractRolloutSessionId = extractRolloutSessionId;
|
|
62
67
|
exports.isDatedDirectoryBefore = isDatedDirectoryBefore;
|
|
63
68
|
exports.walkRolloutFiles = walkRolloutFiles;
|
|
@@ -66,16 +71,23 @@ const fs = __importStar(require("fs"));
|
|
|
66
71
|
const path = __importStar(require("path"));
|
|
67
72
|
exports.DEFAULT_ROLLOUT_WALK_MAX_DEPTH = 6;
|
|
68
73
|
exports.DEFAULT_ROLLOUT_WALK_MAX_FILES = 20_000;
|
|
69
|
-
/** Test if a filename is a Codex rollout file. */
|
|
74
|
+
/** Test if a filename is a Codex rollout file (`rollout-….jsonl`; not compressed ones). */
|
|
70
75
|
function isRolloutFile(filename) {
|
|
71
76
|
return filename.startsWith('rollout-') && filename.endsWith('.jsonl');
|
|
72
77
|
}
|
|
78
|
+
/** Test if a filename is a compressed Codex rollout (`rollout-….jsonl.zst`). */
|
|
79
|
+
function isCompressedRolloutFile(filename) {
|
|
80
|
+
return filename.startsWith('rollout-') && filename.endsWith('.jsonl.zst');
|
|
81
|
+
}
|
|
73
82
|
/**
|
|
74
83
|
* Extract the session UUID from a rollout filename.
|
|
75
|
-
* Format: rollout-<timestamp>-<uuid>.jsonl -> <uuid>
|
|
84
|
+
* Format: rollout-<timestamp>-<uuid>.jsonl (or .jsonl.zst) -> <uuid>
|
|
76
85
|
*/
|
|
77
86
|
function extractRolloutSessionId(filename) {
|
|
78
|
-
const
|
|
87
|
+
const name = path.basename(filename);
|
|
88
|
+
const base = name.endsWith('.jsonl.zst')
|
|
89
|
+
? name.slice(0, -'.jsonl.zst'.length)
|
|
90
|
+
: path.basename(name, '.jsonl');
|
|
79
91
|
// rollout-YYYYMMDD-HHMMSS-<uuid> or rollout-<timestamp>-<uuid>
|
|
80
92
|
const parts = base.split('-');
|
|
81
93
|
// The UUID is typically the last 5 segments (8-4-4-4-12)
|
|
@@ -99,6 +111,7 @@ function walkState(options) {
|
|
|
99
111
|
since: options.since !== undefined && Number.isFinite(options.since) ? options.since : null,
|
|
100
112
|
sessionId: options.sessionId ? options.sessionId.trim().toLowerCase() : null,
|
|
101
113
|
includeEmpty: options.includeEmpty ?? false,
|
|
114
|
+
includeCompressed: options.includeCompressed ?? false,
|
|
102
115
|
cache: options.cache ?? null,
|
|
103
116
|
revalidateRecent: options.revalidateRecent ?? false,
|
|
104
117
|
stats: options.stats,
|
|
@@ -109,10 +122,15 @@ function byNameDescending(a, b) {
|
|
|
109
122
|
return b.name.localeCompare(a.name);
|
|
110
123
|
}
|
|
111
124
|
function matchesFilter(state, name) {
|
|
112
|
-
if (!isRolloutFile(name))
|
|
125
|
+
if (!isRolloutFile(name) && !(state.includeCompressed && isCompressedRolloutFile(name))) {
|
|
113
126
|
return false;
|
|
127
|
+
}
|
|
114
128
|
return (state.sessionId === null || extractRolloutSessionId(name).toLowerCase() === state.sessionId);
|
|
115
129
|
}
|
|
130
|
+
/** A compressed rollout whose uncompressed rollout is listed beside it. */
|
|
131
|
+
function shadowedByPlain(name, has) {
|
|
132
|
+
return isCompressedRolloutFile(name) && has(name.slice(0, -'.zst'.length));
|
|
133
|
+
}
|
|
116
134
|
function record(state, fullPath, name, stat) {
|
|
117
135
|
if (!state.includeEmpty && stat.size <= 0)
|
|
118
136
|
return;
|
|
@@ -171,11 +189,14 @@ function visitSync(state, dir, depth, segments) {
|
|
|
171
189
|
return;
|
|
172
190
|
}
|
|
173
191
|
entries.sort(byNameDescending);
|
|
192
|
+
const names = state.includeCompressed ? new Set(entries.map((entry) => entry.name)) : null;
|
|
174
193
|
for (const entry of entries) {
|
|
175
194
|
if (state.results.length >= state.cap)
|
|
176
195
|
return;
|
|
177
196
|
if (!entry.isFile() || !matchesFilter(state, entry.name))
|
|
178
197
|
continue;
|
|
198
|
+
if (names && shadowedByPlain(entry.name, (name) => names.has(name)))
|
|
199
|
+
continue;
|
|
179
200
|
const fullPath = path.join(dir, entry.name);
|
|
180
201
|
try {
|
|
181
202
|
record(state, fullPath, entry.name, fs.statSync(fullPath));
|
|
@@ -226,6 +247,11 @@ async function visitCached(state, cache, dir, depth, segments) {
|
|
|
226
247
|
const stat = listing.files.get(name);
|
|
227
248
|
if (!stat)
|
|
228
249
|
continue;
|
|
250
|
+
// The listing may come from a walk with other options: filter again.
|
|
251
|
+
if (!matchesFilter(state, name))
|
|
252
|
+
continue;
|
|
253
|
+
if (shadowedByPlain(name, (other) => listing.files.has(other)))
|
|
254
|
+
continue;
|
|
229
255
|
record(state, path.join(dir, name), name, { size: stat.sizeBytes, mtimeMs: stat.mtimeMs });
|
|
230
256
|
}
|
|
231
257
|
await yieldToEventLoop();
|
|
@@ -250,12 +276,15 @@ async function visitAsync(state, dir, depth, segments) {
|
|
|
250
276
|
return;
|
|
251
277
|
}
|
|
252
278
|
entries.sort(byNameDescending);
|
|
279
|
+
const names = state.includeCompressed ? new Set(entries.map((entry) => entry.name)) : null;
|
|
253
280
|
let sinceYield = 0;
|
|
254
281
|
for (const entry of entries) {
|
|
255
282
|
if (state.results.length >= state.cap)
|
|
256
283
|
return;
|
|
257
284
|
if (!entry.isFile() || !matchesFilter(state, entry.name))
|
|
258
285
|
continue;
|
|
286
|
+
if (names && shadowedByPlain(entry.name, (name) => names.has(name)))
|
|
287
|
+
continue;
|
|
259
288
|
const fullPath = path.join(dir, entry.name);
|
|
260
289
|
try {
|
|
261
290
|
const stat = await fs.promises.stat(fullPath);
|
|
@@ -12,6 +12,7 @@ import type { ReadSessionContextSnapshotOptions, SessionContextSnapshot } from '
|
|
|
12
12
|
import type { SessionEvent, SubagentStats, TokenUsage, ContextAttribution } from '../types/sessionEvent';
|
|
13
13
|
import { SESSION_PROVIDER_IDS } from '../providerIds';
|
|
14
14
|
import type { AggregatedCostProvenance } from '../aggregation/types';
|
|
15
|
+
import type { JsonlTailResetReason } from '../watchers/jsonlTail';
|
|
15
16
|
/** Stable runtime list for consumers that must enumerate session providers. */
|
|
16
17
|
export { SESSION_PROVIDER_IDS } from '../providerIds';
|
|
17
18
|
export type ProviderId = (typeof SESSION_PROVIDER_IDS)[number];
|
|
@@ -163,6 +164,12 @@ export interface ProviderRuntimeStatus {
|
|
|
163
164
|
kind: 'available' | 'home_unavailable' | 'config_unreadable' | 'db_missing' | 'sqlite_missing' | 'sqlite_blocked' | 'query_failed';
|
|
164
165
|
message?: string;
|
|
165
166
|
}
|
|
167
|
+
/** Why a {@link SessionReader} started over from offset 0 (see `SessionReader.lastReset`). */
|
|
168
|
+
export interface SessionReaderReset {
|
|
169
|
+
reason: JsonlTailResetReason;
|
|
170
|
+
/** The committed position (`getPosition()`) before the restart. */
|
|
171
|
+
previousPosition: number;
|
|
172
|
+
}
|
|
166
173
|
/**
|
|
167
174
|
* Incremental reader for session data.
|
|
168
175
|
*
|
|
@@ -184,8 +191,36 @@ export interface SessionReader {
|
|
|
184
191
|
getPosition(): number;
|
|
185
192
|
/** Seek to a specific position (byte offset for JSONL, timestamp for DB-backed). */
|
|
186
193
|
seekTo(position: number): void;
|
|
187
|
-
/**
|
|
194
|
+
/**
|
|
195
|
+
* Whether the last `readNew()` restarted from offset 0 because the file was
|
|
196
|
+
* truncated, rewritten, or replaced. Its events are then the session from
|
|
197
|
+
* the beginning: discard anything built from earlier reads before
|
|
198
|
+
* processing them. (Before 0.30.0 the built-in readers reported only a
|
|
199
|
+
* file that shrank below the read position.)
|
|
200
|
+
*/
|
|
188
201
|
wasTruncated(): boolean;
|
|
202
|
+
/**
|
|
203
|
+
* Why the last `readNew()` restarted from offset 0, and the committed
|
|
204
|
+
* position it left: `truncated` (the file shrank below the read
|
|
205
|
+
* position), `rewritten` (its first bytes, or the byte before the resume
|
|
206
|
+
* point, changed), or `replaced` (another file now has the path). `null`
|
|
207
|
+
* when it continued where it was. Optional; the JSONL readers implement it.
|
|
208
|
+
*/
|
|
209
|
+
lastReset?(): SessionReaderReset | null;
|
|
210
|
+
/**
|
|
211
|
+
* Parse state the reader needs, beyond `getPosition()`, to continue
|
|
212
|
+
* correctly after a restart: JSON-serializable data to persist together
|
|
213
|
+
* with that position (for example which Claude responses were already
|
|
214
|
+
* counted). `undefined` when the reader keeps none. Optional.
|
|
215
|
+
*/
|
|
216
|
+
getResumeState?(): unknown;
|
|
217
|
+
/**
|
|
218
|
+
* Restores state from `getResumeState()`. Call it right after `seekTo()`
|
|
219
|
+
* the position that state was taken at. Returns false, changing nothing,
|
|
220
|
+
* when `state` is missing or not valid for this reader; the caller should
|
|
221
|
+
* then replay from the start instead. Optional.
|
|
222
|
+
*/
|
|
223
|
+
restoreResumeState?(state: unknown): boolean;
|
|
189
224
|
}
|
|
190
225
|
/**
|
|
191
226
|
* Expanded session provider interface (no VS Code dependencies).
|
package/dist/schemas/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export { ACCOUNT_PROVIDER_IDS, QUOTA_PROVIDER_IDS, RUNTIME_QUOTA_PROVIDER_IDS, SESSION_PROVIDER_IDS, } from '../providerIds';
|
|
14
14
|
export { messageUsageSchema, sessionMessageSchema, sessionEventSchema, permissionModeSchema, extractSessionEvents, userSessionEventSchema, assistantSessionEventSchema, summarySessionEventSchema, systemSessionEventSchema, toolUseSessionEventSchema, toolResultSessionEventSchema, } from './sessionEvent';
|
|
15
|
+
export type { ExtractSessionEventsOptions } from './sessionEvent';
|
|
15
16
|
export type { MessageUsage, SessionMessage, SessionEvent, PermissionMode, } from '../types/sessionEvent';
|
|
16
17
|
export { normalizedUsageSchema, normalizedUsageCostSchema } from './usageNormalization';
|
|
17
18
|
export type { NormalizedUsage, NormalizedUsageCost } from '../usageNormalization';
|