copilot-usage-studio 0.1.0 → 0.2.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 +29 -4
- package/README.md +206 -150
- package/data/github-copilot-pricing.json +34 -6
- package/dist/copilot-usage-studio/browser/chunk-2SAR2Q6M.js +1 -0
- package/dist/copilot-usage-studio/browser/chunk-5JYQJM5G.js +1 -0
- package/dist/copilot-usage-studio/browser/chunk-7FKLWMFH.js +1 -0
- package/dist/copilot-usage-studio/browser/chunk-EWN3YOHT.js +1 -0
- package/dist/copilot-usage-studio/browser/chunk-KS5W2HMH.js +1 -0
- package/dist/copilot-usage-studio/browser/chunk-OS2XPCVW.js +2 -0
- package/dist/copilot-usage-studio/browser/chunk-U4H425LY.js +1 -0
- package/dist/copilot-usage-studio/browser/chunk-VEKXIM47.js +1 -0
- package/dist/copilot-usage-studio/browser/chunk-WOIYAEKB.js +4 -0
- package/dist/copilot-usage-studio/browser/chunk-XHID4XVE.js +1 -0
- package/dist/copilot-usage-studio/browser/index.html +11 -11
- package/dist/copilot-usage-studio/browser/main-OZIMQRXC.js +1 -0
- package/dist/copilot-usage-studio/browser/styles-7RDDQXQV.css +1 -0
- package/docs/copilot-memory.md +91 -0
- package/docs/local-deployment.md +119 -13
- package/docs/pricing.md +3 -3
- package/docs/scanner-api.md +32 -3
- package/lib/cli.mjs +15 -1
- package/lib/local-runtime.mjs +530 -19
- package/lib/scanner-worker.mjs +25 -0
- package/package.json +95 -78
- package/scripts/pricing-utils.mjs +5 -0
- package/scripts/scan-vscode-sessions.mjs +418 -1204
- package/scripts/scanner-customization-evidence.mjs +576 -0
- package/scripts/scanner-customization-inventory.mjs +1021 -0
- package/scripts/scanner-memory.mjs +229 -0
- package/scripts/scanner-session-parser.mjs +1237 -0
- package/scripts/scanner-traversal.mjs +203 -0
- package/scripts/scanner-workspace.mjs +209 -0
- package/dist/copilot-usage-studio/browser/chunk-C6VWIY5S.js +0 -1
- package/dist/copilot-usage-studio/browser/chunk-DLWQO3VR.js +0 -1
- package/dist/copilot-usage-studio/browser/chunk-F6TIG2GE.js +0 -4
- package/dist/copilot-usage-studio/browser/chunk-JIP7ONRZ.js +0 -1
- package/dist/copilot-usage-studio/browser/chunk-RNKEPBEU.js +0 -1
- package/dist/copilot-usage-studio/browser/chunk-Z3XIAKMM.js +0 -1
- package/dist/copilot-usage-studio/browser/main-C6XOJRSH.js +0 -2
- package/dist/copilot-usage-studio/browser/styles-GZOQ5QIH.css +0 -1
|
@@ -1,14 +1,34 @@
|
|
|
1
|
-
import { existsSync, mkdirSync,
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { homedir, platform } from 'node:os';
|
|
3
|
-
import { basename, dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { basename, dirname, isAbsolute, join, resolve } from 'node:path';
|
|
4
4
|
import { pathToFileURL } from 'node:url';
|
|
5
5
|
import {
|
|
6
|
-
costBreakdownUsdForTokens,
|
|
7
|
-
costUsdForTokens,
|
|
8
|
-
modelKey,
|
|
9
6
|
normalizeModel,
|
|
10
|
-
pricingModelForModel,
|
|
11
7
|
} from './pricing-utils.mjs';
|
|
8
|
+
import {
|
|
9
|
+
attachMemoryRecalls as attachMemoryRecallsCore,
|
|
10
|
+
createMemoryScanner,
|
|
11
|
+
} from './scanner-memory.mjs';
|
|
12
|
+
import {
|
|
13
|
+
customizationEvidenceFromDebugLogs as customizationEvidenceFromDebugLogsCore,
|
|
14
|
+
mergeCustomizationRecords,
|
|
15
|
+
statusRank,
|
|
16
|
+
} from './scanner-customization-evidence.mjs';
|
|
17
|
+
import { createCustomizationInventoryScanner } from './scanner-customization-inventory.mjs';
|
|
18
|
+
import { createSessionParser } from './scanner-session-parser.mjs';
|
|
19
|
+
import {
|
|
20
|
+
defaultCodeUserDirs,
|
|
21
|
+
listDebugLogFiles as traverseDebugLogFiles,
|
|
22
|
+
listDirs as traverseDirs,
|
|
23
|
+
listFiles as traverseFiles,
|
|
24
|
+
listFilesRecursive as traverseFilesRecursive,
|
|
25
|
+
uniqueResolvedRoots,
|
|
26
|
+
userDirForRoot,
|
|
27
|
+
workspaceDirsForRoot,
|
|
28
|
+
} from './scanner-traversal.mjs';
|
|
29
|
+
import { parseWorkspace as parseWorkspaceEntry } from './scanner-workspace.mjs';
|
|
30
|
+
|
|
31
|
+
export { defaultCodeUserDirs } from './scanner-traversal.mjs';
|
|
12
32
|
|
|
13
33
|
const sessionDataSchemaVersion = 1;
|
|
14
34
|
const pricingData = JSON.parse(
|
|
@@ -18,6 +38,8 @@ const pricingVersion = pricingData.version;
|
|
|
18
38
|
const pricingSourceUrl = pricingData.sourceUrl;
|
|
19
39
|
const fallbackPricingModel = pricingData.fallbackModel;
|
|
20
40
|
const traceEventLimit = 1000;
|
|
41
|
+
const memoryFileLimit = 5000;
|
|
42
|
+
const memoryFileSizeLimit = 1024 * 1024;
|
|
21
43
|
|
|
22
44
|
const pricing = pricingData.models;
|
|
23
45
|
|
|
@@ -36,6 +58,23 @@ function createDiagnostics() {
|
|
|
36
58
|
importedChatSnapshotSessions: 0,
|
|
37
59
|
debugLogSessionsWithTranscripts: 0,
|
|
38
60
|
transcriptEventsAvailable: 0,
|
|
61
|
+
scannedMemoryRoots: 0,
|
|
62
|
+
importedMemories: 0,
|
|
63
|
+
importedPlans: 0,
|
|
64
|
+
scannedCustomizationRoots: 0,
|
|
65
|
+
scannedCustomizationLocations: [],
|
|
66
|
+
customizationEvidenceScannedSessions: 0,
|
|
67
|
+
customizationEvidenceModelCalls: 0,
|
|
68
|
+
customizationEvidenceTextParts: 0,
|
|
69
|
+
customizationEvidenceMatchedCustomizations: 0,
|
|
70
|
+
customizationEvidenceCapReason: '',
|
|
71
|
+
importedCustomizations: 0,
|
|
72
|
+
workspaceScans: [],
|
|
73
|
+
skippedSystemCustomizations: 0,
|
|
74
|
+
skippedOversizedMemories: 0,
|
|
75
|
+
skippedUnreadableMemories: 0,
|
|
76
|
+
skippedOversizedCustomizations: 0,
|
|
77
|
+
skippedUnreadableCustomizations: 0,
|
|
39
78
|
skippedEmptyDebugLogs: 0,
|
|
40
79
|
skippedChatSnapshotsWithoutRequests: 0,
|
|
41
80
|
skippedDuplicateChatSnapshots: 0,
|
|
@@ -43,24 +82,6 @@ function createDiagnostics() {
|
|
|
43
82
|
};
|
|
44
83
|
}
|
|
45
84
|
|
|
46
|
-
export function defaultCodeUserDirs() {
|
|
47
|
-
const home = homedir();
|
|
48
|
-
|
|
49
|
-
if (platform() === 'win32') {
|
|
50
|
-
const appData = process.env.APPDATA ?? join(home, 'AppData', 'Roaming');
|
|
51
|
-
return [join(appData, 'Code', 'User'), join(appData, 'Code - Insiders', 'User')];
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (platform() === 'darwin') {
|
|
55
|
-
return [
|
|
56
|
-
join(home, 'Library', 'Application Support', 'Code', 'User'),
|
|
57
|
-
join(home, 'Library', 'Application Support', 'Code - Insiders', 'User'),
|
|
58
|
-
];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return [join(home, '.config', 'Code', 'User'), join(home, '.config', 'Code - Insiders', 'User')];
|
|
62
|
-
}
|
|
63
|
-
|
|
64
85
|
function safeJson(text) {
|
|
65
86
|
try {
|
|
66
87
|
return JSON.parse(text);
|
|
@@ -241,821 +262,185 @@ function readJsonl(file) {
|
|
|
241
262
|
.filter(Boolean);
|
|
242
263
|
}
|
|
243
264
|
|
|
244
|
-
function
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
.map((entry) => join(dir, entry))
|
|
251
|
-
.filter((path) => statSync(path).isDirectory());
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function listFiles(dir, suffix) {
|
|
255
|
-
if (!existsSync(dir)) {
|
|
256
|
-
return [];
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return readdirSync(dir)
|
|
260
|
-
.map((entry) => join(dir, entry))
|
|
261
|
-
.filter((path) => statSync(path).isFile() && path.endsWith(suffix));
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function costUsd(model, tokens) {
|
|
265
|
-
return costUsdForTokens(model, tokens, pricing, fallbackPricingModel);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function costBreakdownUsd(model, tokens) {
|
|
269
|
-
return costBreakdownUsdForTokens(model, tokens, pricing, fallbackPricingModel);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function transcriptAvailability(workspaceDir, sessionId) {
|
|
273
|
-
const sourcePath = join(workspaceDir, 'GitHub.copilot-chat', 'transcripts', `${sessionId}.jsonl`);
|
|
265
|
+
function stripJsonComments(text) {
|
|
266
|
+
let output = '';
|
|
267
|
+
let inString = false;
|
|
268
|
+
let escaped = false;
|
|
269
|
+
let inLineComment = false;
|
|
270
|
+
let inBlockComment = false;
|
|
274
271
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
sourcePath: '',
|
|
279
|
-
eventCount: 0,
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const eventCount = readJsonl(sourcePath).length;
|
|
284
|
-
|
|
285
|
-
return {
|
|
286
|
-
available: true,
|
|
287
|
-
sourcePath,
|
|
288
|
-
eventCount,
|
|
289
|
-
};
|
|
290
|
-
}
|
|
272
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
273
|
+
const character = text[index];
|
|
274
|
+
const next = text[index + 1];
|
|
291
275
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
276
|
+
if (inLineComment) {
|
|
277
|
+
if (character === '\n' || character === '\r') {
|
|
278
|
+
inLineComment = false;
|
|
279
|
+
output += character;
|
|
280
|
+
}
|
|
281
|
+
continue;
|
|
297
282
|
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
return 0;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
export function llmTokenFields(event) {
|
|
304
|
-
const inputTokens = Number(event.attrs?.inputTokens ?? 0);
|
|
305
|
-
const outputTokens = Number(event.attrs?.outputTokens ?? 0);
|
|
306
|
-
const rawCachedInputTokens = numericAttr(event.attrs, [
|
|
307
|
-
'cachedTokens',
|
|
308
|
-
'cachedInputTokens',
|
|
309
|
-
'cacheReadTokens',
|
|
310
|
-
]);
|
|
311
|
-
const cachedInputTokens = Math.min(inputTokens, rawCachedInputTokens);
|
|
312
|
-
const cacheWriteTokens = numericAttr(event.attrs, ['cacheWriteTokens', 'cachedWriteTokens']);
|
|
313
|
-
const billableInputTokens = Math.max(0, inputTokens - cachedInputTokens);
|
|
314
|
-
|
|
315
|
-
return {
|
|
316
|
-
inputTokens,
|
|
317
|
-
billableInputTokens,
|
|
318
|
-
rawCachedInputTokens,
|
|
319
|
-
cachedInputTokens,
|
|
320
|
-
cacheWriteTokens,
|
|
321
|
-
outputTokens,
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
283
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
audit.modelCalls += 1;
|
|
331
|
-
audit.rawInputTokens += tokenFields.inputTokens;
|
|
332
|
-
audit.normalInputTokens += tokenFields.billableInputTokens;
|
|
333
|
-
audit.cachedInputTokens += tokenFields.cachedInputTokens;
|
|
334
|
-
audit.cacheWriteTokens += tokenFields.cacheWriteTokens;
|
|
335
|
-
audit.outputTokens += tokenFields.outputTokens;
|
|
336
|
-
|
|
337
|
-
if (tokenFields.rawCachedInputTokens > 0) {
|
|
338
|
-
audit.callsWithCachedTokens += 1;
|
|
284
|
+
if (inBlockComment) {
|
|
285
|
+
if (character === '*' && next === '/') {
|
|
286
|
+
inBlockComment = false;
|
|
287
|
+
index += 1;
|
|
339
288
|
}
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
340
291
|
|
|
341
|
-
|
|
342
|
-
|
|
292
|
+
if (inString) {
|
|
293
|
+
output += character;
|
|
294
|
+
if (escaped) {
|
|
295
|
+
escaped = false;
|
|
296
|
+
} else if (character === '\\') {
|
|
297
|
+
escaped = true;
|
|
298
|
+
} else if (character === '"') {
|
|
299
|
+
inString = false;
|
|
343
300
|
}
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
344
303
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
},
|
|
351
|
-
{
|
|
352
|
-
modelCalls: 0,
|
|
353
|
-
callsWithCachedTokens: 0,
|
|
354
|
-
invalidCachedTokenSplits: 0,
|
|
355
|
-
rawInputTokens: 0,
|
|
356
|
-
normalInputTokens: 0,
|
|
357
|
-
cachedInputTokens: 0,
|
|
358
|
-
cacheWriteTokens: 0,
|
|
359
|
-
outputTokens: 0,
|
|
360
|
-
maxCachedInputShare: 0,
|
|
361
|
-
},
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
export function mergeCacheTokenAudits(audits) {
|
|
366
|
-
return audits.reduce(
|
|
367
|
-
(total, audit) => ({
|
|
368
|
-
modelCalls: total.modelCalls + audit.modelCalls,
|
|
369
|
-
callsWithCachedTokens: total.callsWithCachedTokens + audit.callsWithCachedTokens,
|
|
370
|
-
invalidCachedTokenSplits: total.invalidCachedTokenSplits + audit.invalidCachedTokenSplits,
|
|
371
|
-
rawInputTokens: total.rawInputTokens + audit.rawInputTokens,
|
|
372
|
-
normalInputTokens: total.normalInputTokens + audit.normalInputTokens,
|
|
373
|
-
cachedInputTokens: total.cachedInputTokens + audit.cachedInputTokens,
|
|
374
|
-
cacheWriteTokens: total.cacheWriteTokens + audit.cacheWriteTokens,
|
|
375
|
-
outputTokens: total.outputTokens + audit.outputTokens,
|
|
376
|
-
maxCachedInputShare: Math.max(total.maxCachedInputShare, audit.maxCachedInputShare),
|
|
377
|
-
}),
|
|
378
|
-
{
|
|
379
|
-
modelCalls: 0,
|
|
380
|
-
callsWithCachedTokens: 0,
|
|
381
|
-
invalidCachedTokenSplits: 0,
|
|
382
|
-
rawInputTokens: 0,
|
|
383
|
-
normalInputTokens: 0,
|
|
384
|
-
cachedInputTokens: 0,
|
|
385
|
-
cacheWriteTokens: 0,
|
|
386
|
-
outputTokens: 0,
|
|
387
|
-
maxCachedInputShare: 0,
|
|
388
|
-
},
|
|
389
|
-
);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
export function eventModelCostFields(rawModel, tokenFields) {
|
|
393
|
-
const normalizedModel = normalizeModel(rawModel, pricing);
|
|
394
|
-
const pricingModel = pricingModelForModel(normalizedModel, pricing, fallbackPricingModel);
|
|
395
|
-
const tokens = {
|
|
396
|
-
input: tokenFields.billableInputTokens,
|
|
397
|
-
cachedInput: tokenFields.cachedInputTokens,
|
|
398
|
-
cacheWrite: tokenFields.cacheWriteTokens,
|
|
399
|
-
output: tokenFields.outputTokens,
|
|
400
|
-
};
|
|
401
|
-
const costBreakdown = costBreakdownUsd(pricingModel, tokens);
|
|
402
|
-
|
|
403
|
-
return {
|
|
404
|
-
model: normalizedModel,
|
|
405
|
-
rawModel:
|
|
406
|
-
String(rawModel ?? '')
|
|
407
|
-
.replace(/^copilot\//i, '')
|
|
408
|
-
.trim() || 'unknown',
|
|
409
|
-
pricingModel,
|
|
410
|
-
pricingTier: costBreakdown.tier,
|
|
411
|
-
totalTokens: tokenFields.inputTokens + tokenFields.outputTokens + tokenFields.cacheWriteTokens,
|
|
412
|
-
estimatedCost: { usd: costBreakdown.total, eur: costBreakdown.total * usdToEur },
|
|
413
|
-
};
|
|
414
|
-
}
|
|
304
|
+
if (character === '"') {
|
|
305
|
+
inString = true;
|
|
306
|
+
output += character;
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
415
309
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
310
|
+
if (character === '/' && next === '/') {
|
|
311
|
+
inLineComment = true;
|
|
312
|
+
index += 1;
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
420
315
|
|
|
421
|
-
|
|
422
|
-
|
|
316
|
+
if (character === '/' && next === '*') {
|
|
317
|
+
inBlockComment = true;
|
|
318
|
+
index += 1;
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
423
321
|
|
|
424
|
-
|
|
425
|
-
if (value === undefined || value === null) {
|
|
426
|
-
return 0;
|
|
322
|
+
output += character;
|
|
427
323
|
}
|
|
428
324
|
|
|
429
|
-
return
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
function parseContentFile(file) {
|
|
433
|
-
const envelope = safeJson(readFileSync(file, 'utf8'));
|
|
434
|
-
const content = parseMaybeJson(envelope?.content ?? envelope);
|
|
435
|
-
|
|
436
|
-
return {
|
|
437
|
-
file,
|
|
438
|
-
content,
|
|
439
|
-
chars: charLength(content),
|
|
440
|
-
};
|
|
325
|
+
return output;
|
|
441
326
|
}
|
|
442
327
|
|
|
443
|
-
function
|
|
444
|
-
const file = join(sessionDir, 'models.json');
|
|
328
|
+
function readJsoncFile(file) {
|
|
445
329
|
if (!existsSync(file)) {
|
|
446
|
-
return
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const parsed = safeJson(readFileSync(file, 'utf8'));
|
|
450
|
-
const models = Array.isArray(parsed) ? parsed : [];
|
|
451
|
-
const index = new Map();
|
|
452
|
-
|
|
453
|
-
for (const model of models) {
|
|
454
|
-
const keys = [model?.id, model?.name, model?.version, model?.capabilities?.family]
|
|
455
|
-
.filter(Boolean)
|
|
456
|
-
.map(modelKey);
|
|
457
|
-
|
|
458
|
-
for (const key of keys) {
|
|
459
|
-
if (key) {
|
|
460
|
-
index.set(key, model);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
return index;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
function modelCapabilityFor(rawModel, capabilityIndex) {
|
|
469
|
-
const key = modelKey(rawModel);
|
|
470
|
-
|
|
471
|
-
return (
|
|
472
|
-
capabilityIndex.get(key) ??
|
|
473
|
-
[...capabilityIndex.entries()].find(([candidate]) => key.includes(candidate))?.[1] ??
|
|
474
|
-
null
|
|
475
|
-
);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
function modelLimitSummaries(sessionDir, llmRequests) {
|
|
479
|
-
const capabilityIndex = modelCapabilityIndex(sessionDir);
|
|
480
|
-
if (!capabilityIndex.size || !llmRequests.length) {
|
|
481
|
-
return [];
|
|
330
|
+
return {};
|
|
482
331
|
}
|
|
483
332
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
const displayModel = normalizeModel(rawModel, pricing);
|
|
491
|
-
const capability = modelCapabilityFor(rawModel || displayModel, capabilityIndex);
|
|
492
|
-
const limits = capability?.capabilities?.limits ?? {};
|
|
493
|
-
const supports = capability?.capabilities?.supports ?? {};
|
|
494
|
-
const current = byModel.get(displayModel) ?? {
|
|
495
|
-
model: displayModel,
|
|
496
|
-
rawModels: new Set(),
|
|
497
|
-
modelId: capability?.id ?? rawModel,
|
|
498
|
-
vendor: capability?.vendor ?? '',
|
|
499
|
-
tokenizer: capability?.capabilities?.tokenizer ?? '',
|
|
500
|
-
contextWindowTokens: Number(limits.max_context_window_tokens ?? 0) || 0,
|
|
501
|
-
promptLimitTokens: Number(limits.max_prompt_tokens ?? 0) || 0,
|
|
502
|
-
outputLimitTokens: Number(limits.max_output_tokens ?? 0) || 0,
|
|
503
|
-
supportedReasoningEfforts: Array.isArray(supports.reasoning_effort)
|
|
504
|
-
? supports.reasoning_effort
|
|
505
|
-
: [],
|
|
506
|
-
supportedEndpoints: Array.isArray(capability?.supported_endpoints)
|
|
507
|
-
? capability.supported_endpoints
|
|
508
|
-
: [],
|
|
509
|
-
modelPickerEnabled: Boolean(capability?.model_picker_enabled),
|
|
510
|
-
isChatDefault: Boolean(capability?.is_chat_default),
|
|
511
|
-
isChatFallback: Boolean(capability?.is_chat_fallback),
|
|
512
|
-
modelCalls: 0,
|
|
513
|
-
largestRawInputTokens: 0,
|
|
514
|
-
totalRawInputTokens: 0,
|
|
515
|
-
largestOutputTokens: 0,
|
|
516
|
-
};
|
|
517
|
-
|
|
518
|
-
current.rawModels.add(rawModel || 'unknown');
|
|
519
|
-
current.modelCalls += 1;
|
|
520
|
-
current.largestRawInputTokens = Math.max(
|
|
521
|
-
current.largestRawInputTokens,
|
|
522
|
-
Number(event.attrs?.inputTokens ?? 0),
|
|
523
|
-
);
|
|
524
|
-
current.totalRawInputTokens += Number(event.attrs?.inputTokens ?? 0);
|
|
525
|
-
current.largestOutputTokens = Math.max(
|
|
526
|
-
current.largestOutputTokens,
|
|
527
|
-
Number(event.attrs?.outputTokens ?? 0),
|
|
528
|
-
);
|
|
529
|
-
byModel.set(displayModel, current);
|
|
333
|
+
try {
|
|
334
|
+
const json = stripJsonComments(readFileSync(file, 'utf8')).replace(/,\s*([}\]])/g, '$1');
|
|
335
|
+
return safeJson(json) ?? {};
|
|
336
|
+
} catch (error) {
|
|
337
|
+
diagnostics.warnings.push(`${file}: settings file skipped: ${error.message}`);
|
|
338
|
+
return {};
|
|
530
339
|
}
|
|
531
|
-
|
|
532
|
-
return [...byModel.values()].map((summary) => ({
|
|
533
|
-
...summary,
|
|
534
|
-
rawModels: [...summary.rawModels],
|
|
535
|
-
promptLimitShare:
|
|
536
|
-
summary.promptLimitTokens > 0
|
|
537
|
-
? summary.largestRawInputTokens / summary.promptLimitTokens
|
|
538
|
-
: null,
|
|
539
|
-
contextWindowShare:
|
|
540
|
-
summary.contextWindowTokens > 0
|
|
541
|
-
? summary.largestRawInputTokens / summary.contextWindowTokens
|
|
542
|
-
: null,
|
|
543
|
-
repeatedInputFactor:
|
|
544
|
-
summary.largestRawInputTokens > 0
|
|
545
|
-
? summary.totalRawInputTokens / summary.largestRawInputTokens
|
|
546
|
-
: 0,
|
|
547
|
-
}));
|
|
548
340
|
}
|
|
549
341
|
|
|
550
|
-
function
|
|
551
|
-
return
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
function toolSchemaSize(tool) {
|
|
555
|
-
const descriptionChars = charLength(tool?.function?.description ?? tool?.description);
|
|
556
|
-
const parameterChars = charLength(
|
|
557
|
-
tool?.function?.parameters ?? tool?.parameters ?? tool?.input_schema,
|
|
558
|
-
);
|
|
559
|
-
|
|
560
|
-
return {
|
|
561
|
-
name: toolName(tool),
|
|
562
|
-
descriptionChars,
|
|
563
|
-
parameterChars,
|
|
564
|
-
totalChars: charLength(tool),
|
|
565
|
-
};
|
|
342
|
+
function listDirs(dir) {
|
|
343
|
+
return traverseDirs(dir, traversalOptions());
|
|
566
344
|
}
|
|
567
345
|
|
|
568
|
-
function
|
|
569
|
-
|
|
570
|
-
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
346
|
+
function listFiles(dir, suffix) {
|
|
347
|
+
return traverseFiles(dir, suffix, traversalOptions());
|
|
571
348
|
}
|
|
572
349
|
|
|
573
|
-
function
|
|
574
|
-
return
|
|
350
|
+
function listDebugLogFiles(root) {
|
|
351
|
+
return traverseDebugLogFiles(root, traversalOptions());
|
|
575
352
|
}
|
|
576
353
|
|
|
577
|
-
function
|
|
578
|
-
return
|
|
354
|
+
function listFilesRecursive(root, predicate, limit = memoryFileLimit, options = {}) {
|
|
355
|
+
return traverseFilesRecursive(root, predicate, limit, traversalOptions(options));
|
|
579
356
|
}
|
|
580
357
|
|
|
581
|
-
function
|
|
582
|
-
const shape = parseMaybeJson(event.attrs?.requestShape);
|
|
583
|
-
|
|
584
|
-
if (!shape || typeof shape !== 'object') {
|
|
585
|
-
return null;
|
|
586
|
-
}
|
|
587
|
-
|
|
358
|
+
function traversalOptions(options = {}) {
|
|
588
359
|
return {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
function requestShapeSummary(event) {
|
|
599
|
-
const shape = requestShapeMetadata(event);
|
|
600
|
-
if (!shape) {
|
|
601
|
-
return '';
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
const parts = [
|
|
605
|
-
shape.api ? `api: ${shape.api}` : '',
|
|
606
|
-
shape.inputItemCount
|
|
607
|
-
? `${shape.inputItemCount.toLocaleString()} input item${shape.inputItemCount === 1 ? '' : 's'}`
|
|
608
|
-
: '',
|
|
609
|
-
shape.inputItemTypes.length ? `types: ${shape.inputItemTypes.join(', ')}` : '',
|
|
610
|
-
shape.hasPreviousResponseId ? 'continues previous response' : '',
|
|
611
|
-
].filter(Boolean);
|
|
612
|
-
|
|
613
|
-
return parts.join(' · ');
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
function countedValues(values) {
|
|
617
|
-
const counts = new Map();
|
|
618
|
-
|
|
619
|
-
for (const value of values.filter(Boolean)) {
|
|
620
|
-
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
return [...counts.entries()]
|
|
624
|
-
.map(([value, count]) => ({ value, count }))
|
|
625
|
-
.sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
function toolPayloadSummary(toolEvents) {
|
|
629
|
-
const byName = new Map();
|
|
630
|
-
|
|
631
|
-
for (const event of toolEvents) {
|
|
632
|
-
const name = String(
|
|
633
|
-
event.data?.toolName ?? event.attrs?.toolName ?? event.name ?? event.type ?? 'tool',
|
|
634
|
-
);
|
|
635
|
-
const current = byName.get(name) ?? { name, calls: 0, argsChars: 0, resultChars: 0 };
|
|
636
|
-
current.calls += 1;
|
|
637
|
-
current.argsChars += charLength(
|
|
638
|
-
event.attrs?.args ??
|
|
639
|
-
event.attrs?.arguments ??
|
|
640
|
-
event.attrs?.input ??
|
|
641
|
-
event.data?.args ??
|
|
642
|
-
event.data?.arguments ??
|
|
643
|
-
event.data?.input,
|
|
644
|
-
);
|
|
645
|
-
current.resultChars += charLength(
|
|
646
|
-
event.attrs?.result ??
|
|
647
|
-
event.attrs?.output ??
|
|
648
|
-
event.attrs?.stdout ??
|
|
649
|
-
event.data?.result ??
|
|
650
|
-
event.data?.output ??
|
|
651
|
-
event.data?.stdout,
|
|
652
|
-
);
|
|
653
|
-
byName.set(name, current);
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
return [...byName.values()]
|
|
657
|
-
.sort(
|
|
658
|
-
(a, b) =>
|
|
659
|
-
b.resultChars + b.argsChars - (a.resultChars + a.argsChars) || a.name.localeCompare(b.name),
|
|
660
|
-
)
|
|
661
|
-
.slice(0, 12);
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
function requestPayloadSummary(sessionDir, llmRequests, toolEvents) {
|
|
665
|
-
const systemPromptFiles = [
|
|
666
|
-
...new Set(llmRequests.map((event) => event.attrs?.systemPromptFile).filter(Boolean)),
|
|
667
|
-
];
|
|
668
|
-
const toolsFiles = [
|
|
669
|
-
...new Set(llmRequests.map((event) => event.attrs?.toolsFile).filter(Boolean)),
|
|
670
|
-
];
|
|
671
|
-
const systemPrompts = systemPromptFiles
|
|
672
|
-
.map((file) => join(sessionDir, file))
|
|
673
|
-
.filter(existsSync)
|
|
674
|
-
.map(parseContentFile);
|
|
675
|
-
const toolFileSummaries = toolsFiles
|
|
676
|
-
.map((file) => join(sessionDir, file))
|
|
677
|
-
.filter(existsSync)
|
|
678
|
-
.map(parseContentFile);
|
|
679
|
-
const tools = toolFileSummaries.flatMap((summary) =>
|
|
680
|
-
Array.isArray(summary.content) ? summary.content : [],
|
|
681
|
-
);
|
|
682
|
-
const toolSchemas = tools.map(toolSchemaSize);
|
|
683
|
-
const mcpToolNames = toolSchemas
|
|
684
|
-
.map((tool) => tool.name)
|
|
685
|
-
.filter((name) => name.startsWith('mcp_'));
|
|
686
|
-
const reasoningEfforts = countedValues(llmRequests.map(reasoningEffort)).map(
|
|
687
|
-
({ value, count }) => ({
|
|
688
|
-
effort: value,
|
|
689
|
-
count,
|
|
360
|
+
...options,
|
|
361
|
+
onWarning: (message) => diagnostics.warnings.push(message),
|
|
362
|
+
onUnreadable: options.onUnreadable ?? (() => {
|
|
363
|
+
if (options.label === 'customization') {
|
|
364
|
+
diagnostics.skippedUnreadableCustomizations += 1;
|
|
365
|
+
} else {
|
|
366
|
+
diagnostics.skippedUnreadableMemories += 1;
|
|
367
|
+
}
|
|
690
368
|
}),
|
|
691
|
-
);
|
|
692
|
-
const subagentLogCount = listFiles(sessionDir, '.jsonl').filter((file) =>
|
|
693
|
-
basename(file).startsWith('runSubagent-'),
|
|
694
|
-
).length;
|
|
695
|
-
|
|
696
|
-
return {
|
|
697
|
-
systemPromptFiles: systemPrompts.length,
|
|
698
|
-
systemPromptChars: systemPrompts.reduce((sum, summary) => sum + summary.chars, 0),
|
|
699
|
-
toolSchemaFiles: toolFileSummaries.length,
|
|
700
|
-
toolSchemaChars: toolFileSummaries.reduce((sum, summary) => sum + summary.chars, 0),
|
|
701
|
-
toolCount: toolSchemas.length,
|
|
702
|
-
mcpToolCount: mcpToolNames.length,
|
|
703
|
-
mcpToolNames: [...new Set(mcpToolNames)].sort(),
|
|
704
|
-
largestToolSchemas: toolSchemas.sort((a, b) => b.totalChars - a.totalChars).slice(0, 8),
|
|
705
|
-
modelCallsWithSystemPromptFile: llmRequests.filter((event) => event.attrs?.systemPromptFile)
|
|
706
|
-
.length,
|
|
707
|
-
modelCallsWithToolsFile: llmRequests.filter((event) => event.attrs?.toolsFile).length,
|
|
708
|
-
reasoningEfforts,
|
|
709
|
-
toolResultCharsByName: toolPayloadSummary(toolEvents),
|
|
710
|
-
subagentLogCount,
|
|
711
|
-
};
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
function contentSummaryFromCache(sessionDir, cache, file) {
|
|
715
|
-
if (!file) {
|
|
716
|
-
return null;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
if (cache.has(file)) {
|
|
720
|
-
return cache.get(file);
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
const path = join(sessionDir, file);
|
|
724
|
-
const summary = existsSync(path) ? parseContentFile(path) : null;
|
|
725
|
-
cache.set(file, summary);
|
|
726
|
-
return summary;
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
function modelCallSetupPayloadFactory(sessionDir) {
|
|
730
|
-
const cache = new Map();
|
|
731
|
-
|
|
732
|
-
return (event) => {
|
|
733
|
-
if (event.type !== 'llm_request') {
|
|
734
|
-
return null;
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
const systemPromptFile = String(event.attrs?.systemPromptFile ?? '').trim();
|
|
738
|
-
const toolsFile = String(event.attrs?.toolsFile ?? '').trim();
|
|
739
|
-
|
|
740
|
-
if (!systemPromptFile && !toolsFile) {
|
|
741
|
-
return null;
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
const systemPrompt = contentSummaryFromCache(sessionDir, cache, systemPromptFile);
|
|
745
|
-
const toolsSummary = contentSummaryFromCache(sessionDir, cache, toolsFile);
|
|
746
|
-
const tools = Array.isArray(toolsSummary?.content) ? toolsSummary.content : [];
|
|
747
|
-
const toolSchemas = tools.map(toolSchemaSize);
|
|
748
|
-
const mcpToolNames = toolSchemas
|
|
749
|
-
.map((tool) => tool.name)
|
|
750
|
-
.filter((name) => name.startsWith('mcp_'));
|
|
751
|
-
|
|
752
|
-
return {
|
|
753
|
-
systemPromptFile,
|
|
754
|
-
systemPromptChars: systemPrompt?.chars ?? 0,
|
|
755
|
-
toolsFile,
|
|
756
|
-
toolSchemaChars: toolsSummary?.chars ?? 0,
|
|
757
|
-
toolCount: toolSchemas.length,
|
|
758
|
-
mcpToolCount: mcpToolNames.length,
|
|
759
|
-
mcpToolNames: [...new Set(mcpToolNames)].sort(),
|
|
760
|
-
largestToolSchemas: toolSchemas.sort((a, b) => b.totalChars - a.totalChars).slice(0, 5),
|
|
761
|
-
};
|
|
762
|
-
};
|
|
763
|
-
}
|
|
764
|
-
|
|
765
|
-
function debugEvidence(llmRequests, agentResponses) {
|
|
766
|
-
const inputSeries = llmRequests.map((event) => Number(event.attrs?.inputTokens ?? 0));
|
|
767
|
-
const outputCaps = [
|
|
768
|
-
...new Set(
|
|
769
|
-
llmRequests.map((event) => Number(event.attrs?.maxTokens ?? 0)).filter((value) => value > 0),
|
|
770
|
-
),
|
|
771
|
-
].sort((a, b) => a - b);
|
|
772
|
-
const maxInputTokens = Math.max(0, ...inputSeries);
|
|
773
|
-
const maxRequestTokens = Math.max(0, ...outputCaps);
|
|
774
|
-
const reasoningEvents = agentResponses.filter((event) =>
|
|
775
|
-
String(event.attrs?.reasoning ?? '').trim(),
|
|
776
|
-
).length;
|
|
777
|
-
const efforts = countedValues(llmRequests.map(reasoningEffort));
|
|
778
|
-
const primaryEffort = efforts[0]?.value ?? '';
|
|
779
|
-
|
|
780
|
-
return {
|
|
781
|
-
reasoning: {
|
|
782
|
-
visible: reasoningEvents > 0 || Boolean(primaryEffort),
|
|
783
|
-
level: primaryEffort,
|
|
784
|
-
events: reasoningEvents,
|
|
785
|
-
source: primaryEffort
|
|
786
|
-
? 'llm_request.attrs.requestOptions.reasoning.effort'
|
|
787
|
-
: reasoningEvents > 0
|
|
788
|
-
? 'agent_response.attrs.reasoning'
|
|
789
|
-
: '',
|
|
790
|
-
help: primaryEffort
|
|
791
|
-
? 'VS Code Agent Debug Logs expose the request reasoning effort in llm_request.attrs.requestOptions.reasoning.effort.'
|
|
792
|
-
: reasoningEvents > 0
|
|
793
|
-
? 'VS Code debug logs include reasoning text on agent_response events, but no request reasoning effort was imported.'
|
|
794
|
-
: 'No reasoning text field was present on imported agent_response events.',
|
|
795
|
-
},
|
|
796
|
-
context: {
|
|
797
|
-
maxInputTokens,
|
|
798
|
-
maxRequestTokens,
|
|
799
|
-
outputCaps,
|
|
800
|
-
requestCapShare: maxRequestTokens > 0 ? maxInputTokens / maxRequestTokens : null,
|
|
801
|
-
source:
|
|
802
|
-
maxRequestTokens > 0
|
|
803
|
-
? 'llm_request.attrs.inputTokens and attrs.maxTokens'
|
|
804
|
-
: 'llm_request.attrs.inputTokens',
|
|
805
|
-
help:
|
|
806
|
-
maxRequestTokens > 0
|
|
807
|
-
? 'Compares the largest observed input token count with the request maxTokens field present in VS Code debug logs. This is an observed pressure signal, not a provider context-window guarantee.'
|
|
808
|
-
: 'Largest observed model input token count. The log did not include a request cap to compare against.',
|
|
809
|
-
},
|
|
810
369
|
};
|
|
811
370
|
}
|
|
812
371
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
tokens: { input: 0, cachedInput: 0, cacheWrite: 0, output: 0 },
|
|
826
|
-
cost: { usd: 0, eur: 0 },
|
|
827
|
-
costBreakdown: { inputUsd: 0, cachedInputUsd: 0, cacheWriteUsd: 0, outputUsd: 0 },
|
|
828
|
-
pricingTiers: new Set(),
|
|
829
|
-
pricingModel: pricingModelForModel(displayModel, pricing, fallbackPricingModel),
|
|
830
|
-
};
|
|
831
|
-
|
|
832
|
-
current.rawModels.add(rawModel || 'unknown');
|
|
833
|
-
current.turns += 1;
|
|
834
|
-
const tokenFields = llmTokenFields(event);
|
|
835
|
-
current.tokens.input += tokenFields.billableInputTokens;
|
|
836
|
-
current.tokens.cachedInput += tokenFields.cachedInputTokens;
|
|
837
|
-
current.tokens.cacheWrite += tokenFields.cacheWriteTokens;
|
|
838
|
-
current.tokens.output += tokenFields.outputTokens;
|
|
839
|
-
const callCost = costBreakdownUsd(current.pricingModel, {
|
|
840
|
-
input: tokenFields.billableInputTokens,
|
|
841
|
-
cachedInput: tokenFields.cachedInputTokens,
|
|
842
|
-
cacheWrite: tokenFields.cacheWriteTokens,
|
|
843
|
-
output: tokenFields.outputTokens,
|
|
844
|
-
});
|
|
845
|
-
current.costBreakdown.inputUsd += callCost.input;
|
|
846
|
-
current.costBreakdown.cachedInputUsd += callCost.cachedInput;
|
|
847
|
-
current.costBreakdown.cacheWriteUsd += callCost.cacheWrite;
|
|
848
|
-
current.costBreakdown.outputUsd += callCost.output;
|
|
849
|
-
current.pricingTiers.add(callCost.tier);
|
|
850
|
-
byModel.set(displayModel, current);
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
return [...byModel.values()].map((entry) => {
|
|
854
|
-
const usd =
|
|
855
|
-
entry.costBreakdown.inputUsd +
|
|
856
|
-
entry.costBreakdown.cachedInputUsd +
|
|
857
|
-
entry.costBreakdown.cacheWriteUsd +
|
|
858
|
-
entry.costBreakdown.outputUsd;
|
|
859
|
-
return {
|
|
860
|
-
...entry,
|
|
861
|
-
rawModels: [...entry.rawModels],
|
|
862
|
-
pricingTiers: [...entry.pricingTiers],
|
|
863
|
-
cost: { usd, eur: usd * usdToEur },
|
|
864
|
-
};
|
|
372
|
+
function memoryScanner() {
|
|
373
|
+
return createMemoryScanner({
|
|
374
|
+
diagnostics: () => diagnostics,
|
|
375
|
+
listDebugLogFiles,
|
|
376
|
+
listFilesRecursive,
|
|
377
|
+
llmTokenFields,
|
|
378
|
+
memoryFileLimit,
|
|
379
|
+
memoryFileSizeLimit,
|
|
380
|
+
normalizeModel: (model) => normalizeModel(model, pricing),
|
|
381
|
+
readJsonl,
|
|
382
|
+
safeJson,
|
|
383
|
+
timestampForEvent,
|
|
865
384
|
});
|
|
866
385
|
}
|
|
867
386
|
|
|
868
|
-
function
|
|
869
|
-
|
|
870
|
-
return modelBreakdown[0].model;
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
if (modelBreakdown.length > 1) {
|
|
874
|
-
return `Mixed (${modelBreakdown.map((entry) => entry.model).join(', ')})`;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
return normalizeModel(fallbackModel, pricing);
|
|
387
|
+
function memoriesFromRoot(root, source, workspace = '') {
|
|
388
|
+
return memoryScanner().memoriesFromRoot(root, source, workspace);
|
|
878
389
|
}
|
|
879
390
|
|
|
880
|
-
function
|
|
881
|
-
|
|
882
|
-
if (!compact) {
|
|
883
|
-
return 0;
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
return Math.max(1, Math.round(Math.max(compact.split(/\s+/).length * 1.35, compact.length / 4)));
|
|
391
|
+
export function memoryRecallsFromDebugLog(sessionDir, workspace = '') {
|
|
392
|
+
return memoryScanner().memoryRecallsFromDebugLog(sessionDir, workspace);
|
|
887
393
|
}
|
|
888
394
|
|
|
889
|
-
function
|
|
890
|
-
return
|
|
395
|
+
export function attachMemoryRecalls(memories, sessions) {
|
|
396
|
+
return attachMemoryRecallsCore(memories, sessions);
|
|
891
397
|
}
|
|
892
398
|
|
|
893
|
-
function
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
if (event.type === 'agent_response') {
|
|
909
|
-
return parseAssistantResponse(event.attrs?.response).slice(0, 140);
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
return String(event.attrs?.details ?? event.name ?? event.type ?? '').slice(0, 140);
|
|
399
|
+
function sessionParser() {
|
|
400
|
+
return createSessionParser({
|
|
401
|
+
diagnostics: () => diagnostics,
|
|
402
|
+
fallbackPricingModel,
|
|
403
|
+
listFiles,
|
|
404
|
+
memoryRecallsFromDebugLog,
|
|
405
|
+
pricing,
|
|
406
|
+
readJsonl,
|
|
407
|
+
safeJson,
|
|
408
|
+
traceEventLimit,
|
|
409
|
+
usdToEur: () => usdToEur,
|
|
410
|
+
workspaceName,
|
|
411
|
+
});
|
|
913
412
|
}
|
|
914
413
|
|
|
915
|
-
function
|
|
916
|
-
|
|
917
|
-
.replace(/\s+/g, ' ')
|
|
918
|
-
.trim();
|
|
919
|
-
return compact.length > maxLength ? `${compact.slice(0, maxLength - 1)}...` : compact;
|
|
414
|
+
export function llmTokenFields(event) {
|
|
415
|
+
return sessionParser().llmTokenFields(event);
|
|
920
416
|
}
|
|
921
417
|
|
|
922
|
-
function
|
|
923
|
-
|
|
924
|
-
return '';
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
if (typeof value === 'string') {
|
|
928
|
-
return boundedText(value);
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
932
|
-
return String(value);
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
return boundedText(JSON.stringify(compactObject(value)));
|
|
418
|
+
export function cacheTokenAuditFromLlmRequests(llmRequests) {
|
|
419
|
+
return sessionParser().cacheTokenAuditFromLlmRequests(llmRequests);
|
|
936
420
|
}
|
|
937
421
|
|
|
938
|
-
function
|
|
939
|
-
return
|
|
422
|
+
export function mergeCacheTokenAudits(audits) {
|
|
423
|
+
return sessionParser().mergeCacheTokenAudits(audits);
|
|
940
424
|
}
|
|
941
425
|
|
|
942
|
-
function
|
|
943
|
-
|
|
944
|
-
if (!Number.isFinite(nanoAiu) || nanoAiu <= 0) {
|
|
945
|
-
return null;
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
const credits = nanoAiu / 1_000_000_000;
|
|
949
|
-
|
|
950
|
-
return {
|
|
951
|
-
nanoAiu,
|
|
952
|
-
credits,
|
|
953
|
-
usd: credits * 0.01,
|
|
954
|
-
modelCalls: 1,
|
|
955
|
-
};
|
|
426
|
+
export function eventModelCostFields(rawModel, tokenFields) {
|
|
427
|
+
return sessionParser().eventModelCostFields(rawModel, tokenFields);
|
|
956
428
|
}
|
|
957
429
|
|
|
958
|
-
function
|
|
959
|
-
|
|
960
|
-
const nanoAiu = usages.reduce((sum, usage) => sum + usage.nanoAiu, 0);
|
|
961
|
-
const credits = nanoAiu / 1_000_000_000;
|
|
962
|
-
|
|
963
|
-
return {
|
|
964
|
-
nanoAiu,
|
|
965
|
-
credits,
|
|
966
|
-
usd: credits * 0.01,
|
|
967
|
-
modelCalls: usages.length,
|
|
968
|
-
};
|
|
430
|
+
export function modelBreakdownFromLlmRequests(llmRequests) {
|
|
431
|
+
return sessionParser().modelBreakdownFromLlmRequests(llmRequests);
|
|
969
432
|
}
|
|
970
433
|
|
|
971
|
-
function
|
|
972
|
-
|
|
973
|
-
return value;
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
const result = {};
|
|
977
|
-
const skip = new Set(['content', 'response', 'result', 'output', 'stdout', 'stderr']);
|
|
978
|
-
|
|
979
|
-
for (const [key, nestedValue] of Object.entries(value)) {
|
|
980
|
-
if (skip.has(key)) {
|
|
981
|
-
continue;
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
if (nestedValue === undefined || nestedValue === null) {
|
|
985
|
-
continue;
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
result[key] =
|
|
989
|
-
typeof nestedValue === 'object'
|
|
990
|
-
? boundedText(JSON.stringify(nestedValue), 120)
|
|
991
|
-
: boundedText(nestedValue, 120);
|
|
992
|
-
|
|
993
|
-
if (Object.keys(result).length >= 6) {
|
|
994
|
-
break;
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
return result;
|
|
434
|
+
function timestampForEvent(event) {
|
|
435
|
+
return sessionParser().timestampForEvent(event);
|
|
999
436
|
}
|
|
1000
437
|
|
|
1001
|
-
function
|
|
1002
|
-
|
|
1003
|
-
const data = event.data ?? {};
|
|
1004
|
-
const candidates = [
|
|
1005
|
-
['category', attrs.category],
|
|
1006
|
-
['source', attrs.source],
|
|
1007
|
-
['model', attrs.model],
|
|
1008
|
-
['debugName', attrs.debugName],
|
|
1009
|
-
['inputTokens', attrs.inputTokens],
|
|
1010
|
-
['cachedTokens', attrs.cachedTokens ?? attrs.cachedInputTokens ?? attrs.cacheReadTokens],
|
|
1011
|
-
['cacheWriteTokens', attrs.cacheWriteTokens ?? attrs.cachedWriteTokens],
|
|
1012
|
-
['outputTokens', attrs.outputTokens],
|
|
1013
|
-
['sourceEstimatedCost', attrs.estimatedCost],
|
|
1014
|
-
['copilotUsageNanoAiu', attrs.copilotUsageNanoAiu],
|
|
1015
|
-
['reasoningEffort', event.type === 'llm_request' ? reasoningEffort(event) : undefined],
|
|
1016
|
-
['textVerbosity', event.type === 'llm_request' ? textVerbosity(event) : undefined],
|
|
1017
|
-
['requestShape', event.type === 'llm_request' ? requestShapeSummary(event) : undefined],
|
|
1018
|
-
['maxTokens', attrs.maxTokens],
|
|
1019
|
-
['ttft', attrs.ttft],
|
|
1020
|
-
['systemPromptFile', attrs.systemPromptFile],
|
|
1021
|
-
['toolsFile', attrs.toolsFile],
|
|
1022
|
-
['vscodeVersion', attrs.vscodeVersion],
|
|
1023
|
-
['copilotVersion', attrs.copilotVersion],
|
|
1024
|
-
['responseId', attrs.responseId],
|
|
1025
|
-
['logVersion', event.v],
|
|
1026
|
-
['toolName', data.toolName ?? attrs.toolName],
|
|
1027
|
-
['details', attrs.details],
|
|
1028
|
-
['content', attrs.content],
|
|
1029
|
-
[
|
|
1030
|
-
'response',
|
|
1031
|
-
event.type === 'agent_response' ? parseAssistantResponse(attrs.response) : undefined,
|
|
1032
|
-
],
|
|
1033
|
-
['data', data && Object.keys(data).length ? data : undefined],
|
|
1034
|
-
];
|
|
1035
|
-
|
|
1036
|
-
const fields = [];
|
|
1037
|
-
const seen = new Set();
|
|
1038
|
-
|
|
1039
|
-
for (const [label, value] of candidates) {
|
|
1040
|
-
const summarized = summaryValue(value);
|
|
1041
|
-
|
|
1042
|
-
if (!summarized || seen.has(label)) {
|
|
1043
|
-
continue;
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
fields.push({ label, value: summarized });
|
|
1047
|
-
seen.add(label);
|
|
1048
|
-
|
|
1049
|
-
if (fields.length >= 8) {
|
|
1050
|
-
break;
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
return fields;
|
|
438
|
+
export function sessionFromDebugLog(sessionDir, workspaceDir) {
|
|
439
|
+
return sessionParser().sessionFromDebugLog(sessionDir, workspaceDir);
|
|
1055
440
|
}
|
|
1056
441
|
|
|
1057
|
-
function
|
|
1058
|
-
return
|
|
442
|
+
export function sessionFromChatSnapshot(file, workspaceDir) {
|
|
443
|
+
return sessionParser().sessionFromChatSnapshot(file, workspaceDir);
|
|
1059
444
|
}
|
|
1060
445
|
|
|
1061
446
|
function workspaceName(workspaceDir) {
|
|
@@ -1065,395 +450,42 @@ function workspaceName(workspaceDir) {
|
|
|
1065
450
|
return folder ? basename(folder) : basename(workspaceDir);
|
|
1066
451
|
}
|
|
1067
452
|
|
|
1068
|
-
function
|
|
1069
|
-
const
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
return parsed
|
|
1075
|
-
.flatMap((message) => message?.parts ?? [])
|
|
1076
|
-
.map((part) => {
|
|
1077
|
-
const content =
|
|
1078
|
-
typeof part?.content === 'string'
|
|
1079
|
-
? (safeJson(part.content) ?? part.content)
|
|
1080
|
-
: part?.content;
|
|
1081
|
-
return typeof content === 'object' && content?.text ? content.text : String(content ?? '');
|
|
1082
|
-
})
|
|
1083
|
-
.filter(Boolean)
|
|
1084
|
-
.join('\n');
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
export function sessionFromDebugLog(sessionDir, workspaceDir) {
|
|
1088
|
-
const main = readJsonl(join(sessionDir, 'main.jsonl'));
|
|
1089
|
-
if (!main.length) {
|
|
1090
|
-
diagnostics.skippedEmptyDebugLogs += 1;
|
|
1091
|
-
return null;
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
const sid = basename(sessionDir);
|
|
1095
|
-
const userMessages = main.filter((event) => event.type === 'user_message');
|
|
1096
|
-
const llmRequests = main.filter((event) => event.type === 'llm_request');
|
|
1097
|
-
const assistantEvents = main.filter(
|
|
1098
|
-
(event) => event.type === 'agent_response' || event.type === 'assistant.message',
|
|
1099
|
-
);
|
|
1100
|
-
const toolEvents = main.filter((event) => String(event.type ?? '').includes('tool'));
|
|
1101
|
-
const errorEvents = main.filter((event) => event.status && event.status !== 'ok');
|
|
1102
|
-
|
|
1103
|
-
if (!userMessages.length && !llmRequests.length && !assistantEvents.length) {
|
|
1104
|
-
diagnostics.skippedEmptyDebugLogs += 1;
|
|
1105
|
-
return null;
|
|
453
|
+
function workspaceFolderPath(workspaceDir) {
|
|
454
|
+
const workspaceJson = join(workspaceDir, 'workspace.json');
|
|
455
|
+
const raw = existsSync(workspaceJson) ? safeJson(readFileSync(workspaceJson, 'utf8')) : null;
|
|
456
|
+
if (!raw?.folder) {
|
|
457
|
+
return '';
|
|
1106
458
|
}
|
|
1107
459
|
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
(
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
const cachedInput = llmRequests.reduce(
|
|
1119
|
-
(sum, event) => sum + llmTokenFields(event).cachedInputTokens,
|
|
1120
|
-
0,
|
|
1121
|
-
);
|
|
1122
|
-
const cacheWrite = llmRequests.reduce(
|
|
1123
|
-
(sum, event) => sum + llmTokenFields(event).cacheWriteTokens,
|
|
1124
|
-
0,
|
|
1125
|
-
);
|
|
1126
|
-
const output = llmRequests.reduce((sum, event) => sum + llmTokenFields(event).outputTokens, 0);
|
|
1127
|
-
const tokens = { input, cachedInput, cacheWrite, output };
|
|
1128
|
-
const usd = modelBreakdown.length
|
|
1129
|
-
? modelBreakdown.reduce((sum, entry) => sum + entry.cost.usd, 0)
|
|
1130
|
-
: costUsd(model, tokens);
|
|
1131
|
-
const startEvent = main.find((event) => event.type === 'session_start') ?? main[0];
|
|
1132
|
-
const debugLogRuntime = {
|
|
1133
|
-
logVersion: Number(startEvent?.v ?? 0) || 0,
|
|
1134
|
-
vscodeVersion: String(startEvent?.attrs?.vscodeVersion ?? '').trim(),
|
|
1135
|
-
copilotVersion: String(startEvent?.attrs?.copilotVersion ?? '').trim(),
|
|
1136
|
-
};
|
|
1137
|
-
const lastEvent = main[main.length - 1];
|
|
1138
|
-
const startedAt =
|
|
1139
|
-
startEvent?.timestamp ??
|
|
1140
|
-
new Date(
|
|
1141
|
-
Number(startEvent?.ts ?? statSync(join(sessionDir, 'main.jsonl')).mtimeMs),
|
|
1142
|
-
).toISOString();
|
|
1143
|
-
const endedAt =
|
|
1144
|
-
lastEvent?.timestamp ??
|
|
1145
|
-
new Date(
|
|
1146
|
-
Number(lastEvent?.ts ?? statSync(join(sessionDir, 'main.jsonl')).mtimeMs),
|
|
1147
|
-
).toISOString();
|
|
1148
|
-
const evidence = debugEvidence(llmRequests, assistantEvents, main);
|
|
1149
|
-
const payload = requestPayloadSummary(sessionDir, llmRequests, toolEvents);
|
|
1150
|
-
const modelLimits = modelLimitSummaries(sessionDir, llmRequests);
|
|
1151
|
-
const cacheTokenAudit = cacheTokenAuditFromLlmRequests(llmRequests);
|
|
1152
|
-
const sourceUsage = sourceUsageSummary(llmRequests);
|
|
1153
|
-
const setupPayloadForEvent = modelCallSetupPayloadFactory(sessionDir);
|
|
1154
|
-
const transcript = transcriptAvailability(workspaceDir, sid);
|
|
1155
|
-
|
|
1156
|
-
if (transcript.available) {
|
|
1157
|
-
diagnostics.debugLogSessionsWithTranscripts += 1;
|
|
1158
|
-
diagnostics.transcriptEventsAvailable += transcript.eventCount;
|
|
460
|
+
try {
|
|
461
|
+
const value = String(raw.folder);
|
|
462
|
+
if (!value.startsWith('file:')) {
|
|
463
|
+
return '';
|
|
464
|
+
}
|
|
465
|
+
const folder = decodeURIComponent(value.replace(/^file:\/+/, ''));
|
|
466
|
+
const resolved = platform() === 'win32' ? folder.replace(/^\//, '') : `/${folder.replace(/^\/+/, '')}`;
|
|
467
|
+
return isAbsolute(resolved) && existsSync(resolved) ? resolved : '';
|
|
468
|
+
} catch {
|
|
469
|
+
return '';
|
|
1159
470
|
}
|
|
471
|
+
}
|
|
1160
472
|
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
);
|
|
473
|
+
function normalizedWorkspaceFolderScope(workspaceFolders) {
|
|
474
|
+
if (!Array.isArray(workspaceFolders)) {
|
|
475
|
+
return new Set();
|
|
1165
476
|
}
|
|
1166
|
-
|
|
1167
|
-
const turns = [
|
|
1168
|
-
...userMessages.map((event) => ({
|
|
1169
|
-
role: 'user',
|
|
1170
|
-
text: String(event.attrs?.content ?? ''),
|
|
1171
|
-
tokens: estimateTokens(event.attrs?.content),
|
|
1172
|
-
})),
|
|
1173
|
-
...assistantEvents.map((event) => {
|
|
1174
|
-
const text =
|
|
1175
|
-
event.type === 'assistant.message'
|
|
1176
|
-
? event.data?.content
|
|
1177
|
-
: parseAssistantResponse(event.attrs?.response);
|
|
1178
|
-
return { role: 'assistant', text: String(text ?? ''), tokens: estimateTokens(text) };
|
|
1179
|
-
}),
|
|
1180
|
-
].filter((turn) => turn.text.trim());
|
|
1181
|
-
|
|
1182
|
-
return {
|
|
1183
|
-
id: sid,
|
|
1184
|
-
sourceKind: 'vscode-copilot-debug-log',
|
|
1185
|
-
tokenSource: llmRequests.length
|
|
1186
|
-
? 'llm_request_token_totals'
|
|
1187
|
-
: 'debug-log-visible-text-estimate',
|
|
1188
|
-
sessionType: 'Local',
|
|
1189
|
-
location: 'Chat Panel',
|
|
1190
|
-
status: 'Idle',
|
|
1191
|
-
title: firstUserMessage.slice(0, 80),
|
|
1192
|
-
firstPrompt: firstUserMessage.slice(0, 240),
|
|
1193
|
-
workspace: workspaceName(workspaceDir),
|
|
1194
|
-
sourcePath: sessionDir,
|
|
1195
|
-
...(debugLogRuntime.logVersion ||
|
|
1196
|
-
debugLogRuntime.vscodeVersion ||
|
|
1197
|
-
debugLogRuntime.copilotVersion
|
|
1198
|
-
? { debugLogRuntime }
|
|
1199
|
-
: {}),
|
|
1200
|
-
model,
|
|
1201
|
-
modelBreakdown,
|
|
1202
|
-
startedAt,
|
|
1203
|
-
endedAt,
|
|
1204
|
-
tags: ['debug-log', llmRequests.length ? 'llm-request-token-totals' : 'estimated-visible-text'],
|
|
1205
|
-
toolsUsed: [
|
|
1206
|
-
...new Set(
|
|
1207
|
-
toolEvents
|
|
1208
|
-
.map((event) => event.data?.toolName ?? event.attrs?.toolName ?? event.name)
|
|
1209
|
-
.filter(Boolean),
|
|
1210
|
-
),
|
|
1211
|
-
],
|
|
1212
|
-
tokens,
|
|
1213
|
-
cost: { usd, eur: usd * usdToEur },
|
|
1214
|
-
confidence: llmRequests.length ? 'exact' : 'estimated',
|
|
1215
|
-
traceSummary: {
|
|
1216
|
-
modelTurns: llmRequests.length,
|
|
1217
|
-
toolCalls: toolEvents.length,
|
|
1218
|
-
totalTokens: input + cachedInput + cacheWrite + output,
|
|
1219
|
-
errors: errorEvents.length,
|
|
1220
|
-
totalEvents: main.length,
|
|
1221
|
-
reasoningEvents: evidence.reasoning.events,
|
|
1222
|
-
maxInputTokens: evidence.context.maxInputTokens,
|
|
1223
|
-
maxRequestTokens: evidence.context.maxRequestTokens,
|
|
1224
|
-
reasoningEfforts: payload.reasoningEfforts,
|
|
1225
|
-
},
|
|
1226
|
-
cacheTokenAudit,
|
|
1227
|
-
...(sourceUsage.modelCalls > 0 ? { sourceUsage } : {}),
|
|
1228
|
-
transcript,
|
|
1229
|
-
advancedSignals: evidence,
|
|
1230
|
-
requestPayload: payload,
|
|
1231
|
-
modelLimits,
|
|
1232
|
-
traceEvents: capTraceEvents(
|
|
1233
|
-
main.map((event, index) => {
|
|
1234
|
-
const tokenFields =
|
|
1235
|
-
event.type === 'llm_request'
|
|
1236
|
-
? llmTokenFields(event)
|
|
1237
|
-
: {
|
|
1238
|
-
inputTokens: 0,
|
|
1239
|
-
billableInputTokens: 0,
|
|
1240
|
-
cachedInputTokens: 0,
|
|
1241
|
-
cacheWriteTokens: 0,
|
|
1242
|
-
outputTokens: 0,
|
|
1243
|
-
};
|
|
1244
|
-
|
|
1245
|
-
const setupPayload = setupPayloadForEvent(event);
|
|
1246
|
-
const sourceUsage = sourceUsageFromNanoAiu(event);
|
|
1247
|
-
const requestShape = event.type === 'llm_request' ? requestShapeMetadata(event) : null;
|
|
1248
|
-
|
|
1249
|
-
return {
|
|
1250
|
-
index,
|
|
1251
|
-
timestamp: timestampForEvent(event),
|
|
1252
|
-
type: String(event.type ?? 'unknown'),
|
|
1253
|
-
name: String(event.name ?? event.type ?? 'unknown'),
|
|
1254
|
-
status: String(event.status ?? 'unknown'),
|
|
1255
|
-
detail: eventDetail(event),
|
|
1256
|
-
attributes: eventAttributeSummary(event),
|
|
1257
|
-
inputTokens: tokenFields.inputTokens,
|
|
1258
|
-
cachedInputTokens: tokenFields.cachedInputTokens,
|
|
1259
|
-
cacheWriteTokens: tokenFields.cacheWriteTokens,
|
|
1260
|
-
outputTokens: tokenFields.outputTokens,
|
|
1261
|
-
ttftMs: event.type === 'llm_request' ? Number(event.attrs?.ttft ?? 0) : 0,
|
|
1262
|
-
maxTokens: event.type === 'llm_request' ? Number(event.attrs?.maxTokens ?? 0) : 0,
|
|
1263
|
-
hasReasoning:
|
|
1264
|
-
event.type === 'agent_response' && Boolean(String(event.attrs?.reasoning ?? '').trim()),
|
|
1265
|
-
reasoningEffort: event.type === 'llm_request' ? reasoningEffort(event) : '',
|
|
1266
|
-
...(requestShape ? { requestShape } : {}),
|
|
1267
|
-
...(event.type === 'llm_request'
|
|
1268
|
-
? eventModelCostFields(event.attrs?.model, tokenFields)
|
|
1269
|
-
: {}),
|
|
1270
|
-
...(event.type === 'llm_request' && sourceEstimatedCost(event)
|
|
1271
|
-
? { sourceEstimatedCost: sourceEstimatedCost(event) }
|
|
1272
|
-
: {}),
|
|
1273
|
-
...(sourceUsage ? { sourceUsage } : {}),
|
|
1274
|
-
...(setupPayload ? { setupPayload } : {}),
|
|
1275
|
-
};
|
|
1276
|
-
}),
|
|
1277
|
-
),
|
|
1278
|
-
turns: turns.slice(0, 60),
|
|
1279
|
-
};
|
|
477
|
+
return new Set(workspaceFolders.map(normalizeWorkspacePath).filter(Boolean));
|
|
1280
478
|
}
|
|
1281
479
|
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
if (!requests.length) {
|
|
1291
|
-
diagnostics.skippedChatSnapshotsWithoutRequests += 1;
|
|
1292
|
-
return null;
|
|
480
|
+
function normalizeWorkspacePath(path) {
|
|
481
|
+
if (!path) {
|
|
482
|
+
return '';
|
|
483
|
+
}
|
|
484
|
+
try {
|
|
485
|
+
return resolve(String(path)).toLowerCase();
|
|
486
|
+
} catch {
|
|
487
|
+
return '';
|
|
1293
488
|
}
|
|
1294
|
-
|
|
1295
|
-
const firstRequest = requests[0];
|
|
1296
|
-
const firstPrompt =
|
|
1297
|
-
firstRequest?.message?.text ?? snapshot?.customTitle ?? 'Untitled Copilot session';
|
|
1298
|
-
const model = normalizeModel(
|
|
1299
|
-
firstRequest?.modelId ?? firstRequest?.inputState?.selectedModel?.identifier,
|
|
1300
|
-
pricing,
|
|
1301
|
-
);
|
|
1302
|
-
const output = requests.reduce((sum, request) => sum + Number(request.completionTokens ?? 0), 0);
|
|
1303
|
-
const input = requests.reduce(
|
|
1304
|
-
(sum, request) => sum + estimateTokens(request?.message?.text ?? ''),
|
|
1305
|
-
0,
|
|
1306
|
-
);
|
|
1307
|
-
const tokens = { input, cachedInput: 0, cacheWrite: 0, output };
|
|
1308
|
-
const usd = costUsd(model, tokens);
|
|
1309
|
-
const pricingModel = pricingModelForModel(model, pricing, fallbackPricingModel);
|
|
1310
|
-
const startedAt = new Date(Number(snapshot.creationDate ?? statSync(file).mtimeMs)).toISOString();
|
|
1311
|
-
const endedAt = statSync(file).mtime.toISOString();
|
|
1312
|
-
|
|
1313
|
-
return {
|
|
1314
|
-
id: basename(file, '.jsonl'),
|
|
1315
|
-
sourceKind: 'vscode-chat-session-snapshot',
|
|
1316
|
-
tokenSource: 'chat-snapshot-output-plus-visible-input-estimate',
|
|
1317
|
-
sessionType: 'Local',
|
|
1318
|
-
location: 'Chat Panel',
|
|
1319
|
-
status: 'Idle',
|
|
1320
|
-
title: String(snapshot.customTitle ?? firstPrompt).slice(0, 80),
|
|
1321
|
-
firstPrompt: String(firstPrompt).slice(0, 240),
|
|
1322
|
-
workspace: workspaceName(workspaceDir),
|
|
1323
|
-
sourcePath: file,
|
|
1324
|
-
model,
|
|
1325
|
-
modelBreakdown: [
|
|
1326
|
-
{
|
|
1327
|
-
model,
|
|
1328
|
-
rawModels: [
|
|
1329
|
-
String(
|
|
1330
|
-
firstRequest?.modelId ?? firstRequest?.inputState?.selectedModel?.identifier ?? model,
|
|
1331
|
-
),
|
|
1332
|
-
],
|
|
1333
|
-
turns: requests.length,
|
|
1334
|
-
tokens,
|
|
1335
|
-
cost: { usd, eur: usd * usdToEur },
|
|
1336
|
-
pricingModel,
|
|
1337
|
-
},
|
|
1338
|
-
],
|
|
1339
|
-
startedAt,
|
|
1340
|
-
endedAt,
|
|
1341
|
-
tags: ['chat-session', 'estimated-input'],
|
|
1342
|
-
toolsUsed: [],
|
|
1343
|
-
tokens,
|
|
1344
|
-
cost: { usd, eur: usd * usdToEur },
|
|
1345
|
-
confidence: 'estimated',
|
|
1346
|
-
traceSummary: {
|
|
1347
|
-
modelTurns: requests.length,
|
|
1348
|
-
toolCalls: 0,
|
|
1349
|
-
totalTokens: input + output,
|
|
1350
|
-
errors: 0,
|
|
1351
|
-
totalEvents: records.length,
|
|
1352
|
-
reasoningEvents: 0,
|
|
1353
|
-
maxInputTokens: input,
|
|
1354
|
-
maxRequestTokens: 0,
|
|
1355
|
-
},
|
|
1356
|
-
cacheTokenAudit: {
|
|
1357
|
-
modelCalls: 0,
|
|
1358
|
-
callsWithCachedTokens: 0,
|
|
1359
|
-
invalidCachedTokenSplits: 0,
|
|
1360
|
-
rawInputTokens: 0,
|
|
1361
|
-
normalInputTokens: 0,
|
|
1362
|
-
cachedInputTokens: 0,
|
|
1363
|
-
cacheWriteTokens: 0,
|
|
1364
|
-
outputTokens: 0,
|
|
1365
|
-
maxCachedInputShare: 0,
|
|
1366
|
-
},
|
|
1367
|
-
transcript: {
|
|
1368
|
-
available: false,
|
|
1369
|
-
sourcePath: '',
|
|
1370
|
-
eventCount: 0,
|
|
1371
|
-
},
|
|
1372
|
-
advancedSignals: {
|
|
1373
|
-
reasoning: {
|
|
1374
|
-
visible: false,
|
|
1375
|
-
level: '',
|
|
1376
|
-
events: 0,
|
|
1377
|
-
source: '',
|
|
1378
|
-
help: 'Chat snapshots do not expose agent_response reasoning text or a reasoning-level field.',
|
|
1379
|
-
},
|
|
1380
|
-
context: {
|
|
1381
|
-
maxInputTokens: input,
|
|
1382
|
-
maxRequestTokens: 0,
|
|
1383
|
-
outputCaps: [],
|
|
1384
|
-
requestCapShare: null,
|
|
1385
|
-
source: 'estimated visible chat text',
|
|
1386
|
-
help: 'Chat snapshots only provide visible text context here, so context pressure is not reliable for cost debugging.',
|
|
1387
|
-
},
|
|
1388
|
-
},
|
|
1389
|
-
traceEvents: capTraceEvents(
|
|
1390
|
-
requests.flatMap((request, index) => {
|
|
1391
|
-
const rawRequestModel =
|
|
1392
|
-
request?.modelId ?? request?.inputState?.selectedModel?.identifier ?? model;
|
|
1393
|
-
const userInputTokens = estimateTokens(request?.message?.text ?? '');
|
|
1394
|
-
const assistantOutputTokens = Number(request.completionTokens ?? 0);
|
|
1395
|
-
|
|
1396
|
-
return [
|
|
1397
|
-
{
|
|
1398
|
-
index: index * 2,
|
|
1399
|
-
timestamp: startedAt,
|
|
1400
|
-
type: 'user_message',
|
|
1401
|
-
name: 'user_message',
|
|
1402
|
-
status: 'ok',
|
|
1403
|
-
detail: String(request?.message?.text ?? '').slice(0, 140),
|
|
1404
|
-
inputTokens: userInputTokens,
|
|
1405
|
-
cachedInputTokens: 0,
|
|
1406
|
-
cacheWriteTokens: 0,
|
|
1407
|
-
outputTokens: 0,
|
|
1408
|
-
...eventModelCostFields(rawRequestModel, {
|
|
1409
|
-
inputTokens: userInputTokens,
|
|
1410
|
-
billableInputTokens: userInputTokens,
|
|
1411
|
-
cachedInputTokens: 0,
|
|
1412
|
-
cacheWriteTokens: 0,
|
|
1413
|
-
outputTokens: 0,
|
|
1414
|
-
}),
|
|
1415
|
-
},
|
|
1416
|
-
{
|
|
1417
|
-
index: index * 2 + 1,
|
|
1418
|
-
timestamp: endedAt,
|
|
1419
|
-
type: 'assistant_response',
|
|
1420
|
-
name: 'assistant_response',
|
|
1421
|
-
status: 'ok',
|
|
1422
|
-
detail: `${assistantOutputTokens.toLocaleString()} completion tokens`,
|
|
1423
|
-
inputTokens: 0,
|
|
1424
|
-
cachedInputTokens: 0,
|
|
1425
|
-
cacheWriteTokens: 0,
|
|
1426
|
-
outputTokens: assistantOutputTokens,
|
|
1427
|
-
...eventModelCostFields(rawRequestModel, {
|
|
1428
|
-
inputTokens: 0,
|
|
1429
|
-
billableInputTokens: 0,
|
|
1430
|
-
cachedInputTokens: 0,
|
|
1431
|
-
cacheWriteTokens: 0,
|
|
1432
|
-
outputTokens: assistantOutputTokens,
|
|
1433
|
-
}),
|
|
1434
|
-
},
|
|
1435
|
-
];
|
|
1436
|
-
}),
|
|
1437
|
-
),
|
|
1438
|
-
turns: requests
|
|
1439
|
-
.flatMap((request) => [
|
|
1440
|
-
{
|
|
1441
|
-
role: 'user',
|
|
1442
|
-
text: String(request?.message?.text ?? ''),
|
|
1443
|
-
tokens: estimateTokens(request?.message?.text),
|
|
1444
|
-
},
|
|
1445
|
-
{
|
|
1446
|
-
role: 'assistant',
|
|
1447
|
-
text: (request?.response ?? [])
|
|
1448
|
-
.map((part) => part.value ?? part.generatedTitle ?? part.kind ?? '')
|
|
1449
|
-
.filter(Boolean)
|
|
1450
|
-
.join('\n'),
|
|
1451
|
-
tokens: Number(request.completionTokens ?? 0),
|
|
1452
|
-
},
|
|
1453
|
-
])
|
|
1454
|
-
.filter((turn) => turn.text.trim())
|
|
1455
|
-
.slice(0, 60),
|
|
1456
|
-
};
|
|
1457
489
|
}
|
|
1458
490
|
|
|
1459
491
|
function enrichSessionFromWorkspaceState(session, stateBySessionId) {
|
|
@@ -1501,35 +533,61 @@ function enrichSessionFromWorkspaceState(session, stateBySessionId) {
|
|
|
1501
533
|
};
|
|
1502
534
|
}
|
|
1503
535
|
|
|
1504
|
-
function
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
if (session && debugIds.has(session.id)) {
|
|
1519
|
-
diagnostics.skippedDuplicateChatSnapshots += 1;
|
|
1520
|
-
return null;
|
|
1521
|
-
}
|
|
1522
|
-
return session ? enrichSessionFromWorkspaceState(session, stateBySessionId) : null;
|
|
1523
|
-
})
|
|
1524
|
-
.filter(Boolean);
|
|
1525
|
-
diagnostics.importedChatSnapshotSessions += chatSessions.length;
|
|
536
|
+
function customizationInventoryScanner() {
|
|
537
|
+
return createCustomizationInventoryScanner({
|
|
538
|
+
diagnostics: () => diagnostics,
|
|
539
|
+
listDirs,
|
|
540
|
+
listFilesRecursive,
|
|
541
|
+
readJsonl,
|
|
542
|
+
workspaceFolderPath,
|
|
543
|
+
workspaceName,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function customizationsFromWorkspace(workspaceDir, options = {}) {
|
|
548
|
+
return customizationInventoryScanner().customizationsFromWorkspace(workspaceDir, options);
|
|
549
|
+
}
|
|
1526
550
|
|
|
1527
|
-
|
|
551
|
+
function customizationsFromDiscoveryFolders(debugRoot, workspace, options = {}) {
|
|
552
|
+
return customizationInventoryScanner().customizationsFromDiscoveryFolders(debugRoot, workspace, options);
|
|
1528
553
|
}
|
|
1529
554
|
|
|
1530
|
-
function
|
|
1531
|
-
|
|
1532
|
-
|
|
555
|
+
function customizationsFromDebugReferences(debugRoot, bases, workspace, options = {}) {
|
|
556
|
+
return customizationInventoryScanner().customizationsFromDebugReferences(debugRoot, bases, workspace, options);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function customizationEvidenceFromDebugLogs(
|
|
560
|
+
debugRoot,
|
|
561
|
+
customizations,
|
|
562
|
+
workspace = '',
|
|
563
|
+
workspaceDir = '',
|
|
564
|
+
onProgress = () => {},
|
|
565
|
+
evidenceOptions = {},
|
|
566
|
+
) {
|
|
567
|
+
return customizationEvidenceFromDebugLogsCore(debugRoot, customizations, workspace, workspaceDir, onProgress, {
|
|
568
|
+
...evidenceOptions,
|
|
569
|
+
diagnostics,
|
|
570
|
+
listDirs,
|
|
571
|
+
readJsonl,
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function workspaceScannerDependencies() {
|
|
576
|
+
return {
|
|
577
|
+
customizationsFromDebugReferences,
|
|
578
|
+
customizationsFromDiscoveryFolders,
|
|
579
|
+
customizationsFromWorkspace,
|
|
580
|
+
customizationEvidenceFromDebugLogs,
|
|
581
|
+
diagnostics,
|
|
582
|
+
enrichSessionFromWorkspaceState,
|
|
583
|
+
listDirs,
|
|
584
|
+
listFiles,
|
|
585
|
+
memoriesFromRoot,
|
|
586
|
+
readWorkspaceState,
|
|
587
|
+
sessionFromChatSnapshot,
|
|
588
|
+
sessionFromDebugLog,
|
|
589
|
+
workspaceName,
|
|
590
|
+
};
|
|
1533
591
|
}
|
|
1534
592
|
|
|
1535
593
|
/**
|
|
@@ -1546,7 +604,7 @@ export async function scanVsCodeSessions(options = {}) {
|
|
|
1546
604
|
if (!Array.isArray(configuredRoots)) {
|
|
1547
605
|
throw new TypeError('roots must be an array of VS Code user-data or workspace-storage paths.');
|
|
1548
606
|
}
|
|
1549
|
-
const roots =
|
|
607
|
+
const roots = uniqueResolvedRoots(configuredRoots);
|
|
1550
608
|
const conversionRate = Number(options.usdToEur ?? process.env.USD_TO_EUR ?? 1);
|
|
1551
609
|
if (!Number.isFinite(conversionRate) || conversionRate <= 0) {
|
|
1552
610
|
throw new TypeError('usdToEur must be a positive number.');
|
|
@@ -1563,22 +621,109 @@ export async function scanVsCodeSessions(options = {}) {
|
|
|
1563
621
|
diagnostics = createDiagnostics();
|
|
1564
622
|
diagnostics.scannedRoots = roots;
|
|
1565
623
|
usdToEur = conversionRate;
|
|
624
|
+
const onProgress = typeof options.onProgress === 'function' ? options.onProgress : () => {};
|
|
625
|
+
const workspaceOptions = {
|
|
626
|
+
includeCustomizations: options.includeCustomizations !== false,
|
|
627
|
+
includeSystemCustomizations: options.includeSystemCustomizations === true,
|
|
628
|
+
customizationDiscovery: options.customizationDiscovery ?? null,
|
|
629
|
+
customizationEvidence: options.customizationEvidence ?? {},
|
|
630
|
+
};
|
|
1566
631
|
|
|
1567
632
|
try {
|
|
633
|
+
onProgress({
|
|
634
|
+
stage: 'roots',
|
|
635
|
+
message: `Scanning ${roots.length} VS Code root${roots.length === 1 ? '' : 's'}.`,
|
|
636
|
+
roots,
|
|
637
|
+
});
|
|
1568
638
|
DatabaseSync = options.sqlite === false ? null : await loadSqliteSupport();
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
639
|
+
let workspaceDirs = [
|
|
640
|
+
...new Set(roots.flatMap((root) => workspaceDirsForRoot(root, traversalOptions()))),
|
|
641
|
+
];
|
|
642
|
+
const workspaceFolderScope = normalizedWorkspaceFolderScope(options.workspaceFolders);
|
|
643
|
+
if (workspaceFolderScope.size) {
|
|
644
|
+
const scopedWorkspaceDirs = dedupeWorkspaceDirsByFolder(
|
|
645
|
+
workspaceDirs.filter((workspaceDir) =>
|
|
646
|
+
workspaceFolderScope.has(normalizeWorkspacePath(workspaceFolderPath(workspaceDir))),
|
|
647
|
+
),
|
|
648
|
+
);
|
|
649
|
+
onProgress({
|
|
650
|
+
stage: 'workspace-scope',
|
|
651
|
+
message: `Current workspace scope matched ${scopedWorkspaceDirs.length} VS Code storage entr${scopedWorkspaceDirs.length === 1 ? 'y' : 'ies'}.`,
|
|
652
|
+
total: scopedWorkspaceDirs.length,
|
|
653
|
+
});
|
|
654
|
+
workspaceDirs = scopedWorkspaceDirs;
|
|
655
|
+
} else if (options.requireWorkspaceFolders === true) {
|
|
656
|
+
onProgress({
|
|
657
|
+
stage: 'workspace-scope',
|
|
658
|
+
message: 'No current VS Code workspace folder was provided; skipping broad customization evidence scan.',
|
|
659
|
+
total: 0,
|
|
660
|
+
});
|
|
661
|
+
workspaceDirs = [];
|
|
662
|
+
}
|
|
663
|
+
onProgress({
|
|
664
|
+
stage: 'workspaces',
|
|
665
|
+
message: `Found ${workspaceDirs.length} VS Code storage entr${workspaceDirs.length === 1 ? 'y' : 'ies'}.`,
|
|
666
|
+
total: workspaceDirs.length,
|
|
1577
667
|
});
|
|
1578
|
-
|
|
1579
|
-
const
|
|
1580
|
-
|
|
668
|
+
const workspaceResults = [];
|
|
669
|
+
for (const [index, workspaceDir] of workspaceDirs.entries()) {
|
|
670
|
+
if (index > 0 && index % 50 === 0) {
|
|
671
|
+
onProgress({
|
|
672
|
+
stage: 'workspace-queue',
|
|
673
|
+
message: `Checked ${index}/${workspaceDirs.length} VS Code storage entries.`,
|
|
674
|
+
index,
|
|
675
|
+
total: workspaceDirs.length,
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
workspaceResults.push(parseWorkspaceEntry(
|
|
679
|
+
workspaceDir,
|
|
680
|
+
{
|
|
681
|
+
...workspaceOptions,
|
|
682
|
+
workspaceIndex: index + 1,
|
|
683
|
+
workspaceTotal: workspaceDirs.length,
|
|
684
|
+
},
|
|
685
|
+
onProgress,
|
|
686
|
+
workspaceScannerDependencies(),
|
|
687
|
+
));
|
|
688
|
+
await yieldToRuntime();
|
|
689
|
+
}
|
|
690
|
+
const sessions = workspaceResults
|
|
691
|
+
.flatMap((result) => result.sessions)
|
|
1581
692
|
.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
|
|
693
|
+
const globalMemoryRoots = [...new Set(
|
|
694
|
+
roots
|
|
695
|
+
.map(userDirForRoot)
|
|
696
|
+
.filter(Boolean)
|
|
697
|
+
.map((userDir) => join(userDir, 'globalStorage', 'github.copilot-chat', 'memory-tool', 'memories')),
|
|
698
|
+
)];
|
|
699
|
+
const memoryMap = new Map();
|
|
700
|
+
onProgress({
|
|
701
|
+
stage: 'memories',
|
|
702
|
+
message: `Indexing memories from ${globalMemoryRoots.length} global root${globalMemoryRoots.length === 1 ? '' : 's'} and VS Code storage.`,
|
|
703
|
+
total: globalMemoryRoots.length,
|
|
704
|
+
});
|
|
705
|
+
for (const memory of [
|
|
706
|
+
...workspaceResults.flatMap((result) => result.memories),
|
|
707
|
+
...globalMemoryRoots.flatMap((root) => memoriesFromRoot(root, 'global')),
|
|
708
|
+
]) {
|
|
709
|
+
memoryMap.set(memory.id, memory);
|
|
710
|
+
}
|
|
711
|
+
const memories = attachMemoryRecalls([...memoryMap.values()], sessions).sort((a, b) =>
|
|
712
|
+
b.modifiedAt.localeCompare(a.modifiedAt),
|
|
713
|
+
);
|
|
714
|
+
const customizationMap = new Map();
|
|
715
|
+
for (const customization of workspaceResults.flatMap((result) => result.customizations)) {
|
|
716
|
+
customizationMap.set(
|
|
717
|
+
customization.id,
|
|
718
|
+
mergeCustomizationRecords(customizationMap.get(customization.id), customization),
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
const customizations = [...customizationMap.values()].sort(
|
|
722
|
+
(a, b) =>
|
|
723
|
+
statusRank(b.evidenceStatus) - statusRank(a.evidenceStatus) ||
|
|
724
|
+
b.modifiedAt.localeCompare(a.modifiedAt),
|
|
725
|
+
);
|
|
726
|
+
diagnostics.importedCustomizations = customizations.length;
|
|
1582
727
|
const seenIds = new Set();
|
|
1583
728
|
for (const session of sessions) {
|
|
1584
729
|
if (seenIds.has(session.id)) {
|
|
@@ -1587,6 +732,13 @@ export async function scanVsCodeSessions(options = {}) {
|
|
|
1587
732
|
seenIds.add(session.id);
|
|
1588
733
|
}
|
|
1589
734
|
|
|
735
|
+
onProgress({
|
|
736
|
+
stage: 'complete',
|
|
737
|
+
message: `Scan complete: imported ${sessions.length} session${sessions.length === 1 ? '' : 's'}.`,
|
|
738
|
+
sessions: sessions.length,
|
|
739
|
+
workspaces: workspaceDirs.length,
|
|
740
|
+
});
|
|
741
|
+
|
|
1590
742
|
return {
|
|
1591
743
|
schemaVersion: sessionDataSchemaVersion,
|
|
1592
744
|
generatedAt,
|
|
@@ -1600,6 +752,8 @@ export async function scanVsCodeSessions(options = {}) {
|
|
|
1600
752
|
sessions.map((session) => session.cacheTokenAudit).filter(Boolean),
|
|
1601
753
|
),
|
|
1602
754
|
},
|
|
755
|
+
memories,
|
|
756
|
+
customizations,
|
|
1603
757
|
sessions,
|
|
1604
758
|
};
|
|
1605
759
|
} finally {
|
|
@@ -1610,6 +764,26 @@ export async function scanVsCodeSessions(options = {}) {
|
|
|
1610
764
|
}
|
|
1611
765
|
}
|
|
1612
766
|
|
|
767
|
+
function yieldToRuntime() {
|
|
768
|
+
return new Promise((resolveYield) => setImmediate(resolveYield));
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function dedupeWorkspaceDirsByFolder(workspaceDirs) {
|
|
772
|
+
const seen = new Set();
|
|
773
|
+
const deduped = [];
|
|
774
|
+
for (const workspaceDir of workspaceDirs) {
|
|
775
|
+
const folder = normalizeWorkspacePath(workspaceFolderPath(workspaceDir));
|
|
776
|
+
const key = folder || normalizeWorkspacePath(workspaceDir);
|
|
777
|
+
if (seen.has(key)) {
|
|
778
|
+
diagnostics.warnings.push(`Duplicate VS Code storage entry skipped for current workspace: ${workspaceDir}`);
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
seen.add(key);
|
|
782
|
+
deduped.push(workspaceDir);
|
|
783
|
+
}
|
|
784
|
+
return deduped;
|
|
785
|
+
}
|
|
786
|
+
|
|
1613
787
|
export function writeSessionData(sessionData, outputFile = 'public/data/sessions.json') {
|
|
1614
788
|
const resolvedOutputFile = resolve(outputFile);
|
|
1615
789
|
mkdirSync(dirname(resolvedOutputFile), { recursive: true });
|
|
@@ -1617,16 +791,56 @@ export function writeSessionData(sessionData, outputFile = 'public/data/sessions
|
|
|
1617
791
|
return resolvedOutputFile;
|
|
1618
792
|
}
|
|
1619
793
|
|
|
1620
|
-
export async function runScannerCli(args = process.argv.slice(2), logger = console) {
|
|
1621
|
-
const outputFile = args
|
|
1622
|
-
const
|
|
1623
|
-
const
|
|
1624
|
-
const
|
|
794
|
+
export async function runScannerCli(args = process.argv.slice(2), logger = console, dependencies = {}) {
|
|
795
|
+
const { outputFile, roots } = parseScannerCliArgs(args);
|
|
796
|
+
const scanner = dependencies.scanner ?? scanVsCodeSessions;
|
|
797
|
+
const writer = dependencies.writer ?? writeSessionData;
|
|
798
|
+
const sessionData = await scanner(roots.length ? { roots } : {});
|
|
799
|
+
const resolvedOutputFile = writer(sessionData, outputFile);
|
|
1625
800
|
|
|
1626
801
|
logger.log(`Wrote ${sessionData.sessions.length} sessions to ${resolvedOutputFile}`);
|
|
1627
802
|
return sessionData;
|
|
1628
803
|
}
|
|
1629
804
|
|
|
805
|
+
function parseScannerCliArgs(args) {
|
|
806
|
+
let outputFile = '';
|
|
807
|
+
const roots = [];
|
|
808
|
+
|
|
809
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
810
|
+
const argument = args[index];
|
|
811
|
+
const [flag, inlineValue] = argument.split('=', 2);
|
|
812
|
+
if (flag === '--output' || flag === '--root') {
|
|
813
|
+
const value = inlineValue ?? args[index + 1];
|
|
814
|
+
if (!value) {
|
|
815
|
+
throw new Error(`${flag} requires a value.`);
|
|
816
|
+
}
|
|
817
|
+
if (inlineValue === undefined) {
|
|
818
|
+
index += 1;
|
|
819
|
+
}
|
|
820
|
+
if (flag === '--output') {
|
|
821
|
+
outputFile = value;
|
|
822
|
+
} else {
|
|
823
|
+
roots.push(value);
|
|
824
|
+
}
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
if (argument.startsWith('-')) {
|
|
829
|
+
throw new Error(`Unknown option: ${argument}`);
|
|
830
|
+
}
|
|
831
|
+
if (!outputFile) {
|
|
832
|
+
outputFile = argument;
|
|
833
|
+
} else {
|
|
834
|
+
roots.push(argument);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
return {
|
|
839
|
+
outputFile: outputFile || 'public/data/sessions.json',
|
|
840
|
+
roots,
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
|
|
1630
844
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
1631
845
|
await runScannerCli();
|
|
1632
846
|
}
|