letmecode 0.1.10 → 0.1.11
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/ink-app/dist/providers/claude.js +133 -5
- package/package.json +1 -1
|
@@ -78,13 +78,14 @@ export class ClaudeUsageProvider extends UsageProviderBase {
|
|
|
78
78
|
const parsedSessionFiles = await loadParsedClaudeSessionFiles(sessionsRoot, this.usageCommandKind, options.traceLogger);
|
|
79
79
|
traceClaude(options.traceLogger, this.usageCommandKind, `Loaded ${parsedSessionFiles.length} parsed session file(s) from ${sessionsRoot}.`);
|
|
80
80
|
for (const file of parsedSessionFiles) {
|
|
81
|
-
const matchingEvents = file.events.filter((event) => this.entrypoints.
|
|
81
|
+
const matchingEvents = file.events.filter((event) => matchesClaudeProviderEvent(event, file, this.entrypoints, this.usageCommandKind));
|
|
82
82
|
traceClaude(options.traceLogger, this.usageCommandKind, [
|
|
83
83
|
`Session file ${describeSessionFilePath(sessionsRoot, file.filePath)}:`,
|
|
84
84
|
`lines=${file.linesRead}`,
|
|
85
85
|
`malformed=${file.malformedLines}`,
|
|
86
86
|
`assistantUsageEvents=${file.events.length}`,
|
|
87
87
|
`matchingEvents=${matchingEvents.length}`,
|
|
88
|
+
`source=${file.sourceKind}`,
|
|
88
89
|
`entrypoints=${summarizeEventCounts(file.events.map((event) => event.entrypoint || "<empty>"))}`,
|
|
89
90
|
`models=${summarizeDistinctValues(file.events.map((event) => event.modelId || "unknown"))}`
|
|
90
91
|
].join(" "));
|
|
@@ -390,20 +391,26 @@ async function loadParsedClaudeSessionFiles(sessionsRoot, usageCommandKind, trac
|
|
|
390
391
|
const files = [];
|
|
391
392
|
traceClaude(traceLogger, usageCommandKind, `Scanning session files under ${sessionsRoot}.`);
|
|
392
393
|
for await (const filePath of walkSessionFiles(sessionsRoot)) {
|
|
393
|
-
files.push(await parseSessionFile(filePath));
|
|
394
|
+
files.push(await parseSessionFile(filePath, sessionsRoot));
|
|
394
395
|
}
|
|
396
|
+
inferClaudeSessionFileSources(files);
|
|
395
397
|
traceClaude(traceLogger, usageCommandKind, `Completed session file scan under ${sessionsRoot}: ${files.length} file(s) parsed.`);
|
|
396
398
|
return files;
|
|
397
399
|
})();
|
|
398
400
|
parsedClaudeSessionFilesCache.set(cacheKey, pending);
|
|
399
401
|
return pending;
|
|
400
402
|
}
|
|
401
|
-
async function parseSessionFile(filePath) {
|
|
403
|
+
async function parseSessionFile(filePath, sessionsRoot) {
|
|
402
404
|
const stream = fs.createReadStream(filePath, { encoding: "utf8" });
|
|
403
405
|
const lineReader = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
404
406
|
let linesRead = 0;
|
|
405
407
|
let malformedLines = 0;
|
|
406
408
|
const events = [];
|
|
409
|
+
const assistantEntryPoints = new Set();
|
|
410
|
+
let hasIdeOpenedFileAttachment = false;
|
|
411
|
+
let hasIdeOpenedFileMarker = false;
|
|
412
|
+
let hasIdeTooling = false;
|
|
413
|
+
let hasQueueOperations = false;
|
|
407
414
|
for await (const line of lineReader) {
|
|
408
415
|
linesRead += 1;
|
|
409
416
|
if (!line.trim()) {
|
|
@@ -417,6 +424,19 @@ async function parseSessionFile(filePath) {
|
|
|
417
424
|
malformedLines += 1;
|
|
418
425
|
continue;
|
|
419
426
|
}
|
|
427
|
+
if (payloadObject.type === "queue-operation") {
|
|
428
|
+
hasQueueOperations = true;
|
|
429
|
+
}
|
|
430
|
+
if (messageContainsIdeOpenedFileMarker(asRecord(payloadObject.message))) {
|
|
431
|
+
hasIdeOpenedFileMarker = true;
|
|
432
|
+
}
|
|
433
|
+
const attachment = asRecord(payloadObject.attachment);
|
|
434
|
+
if (attachment?.type === "opened_file_in_ide") {
|
|
435
|
+
hasIdeOpenedFileAttachment = true;
|
|
436
|
+
}
|
|
437
|
+
if (attachmentHasIdeTooling(attachment)) {
|
|
438
|
+
hasIdeTooling = true;
|
|
439
|
+
}
|
|
420
440
|
if (payloadObject.type !== "assistant") {
|
|
421
441
|
continue;
|
|
422
442
|
}
|
|
@@ -427,12 +447,14 @@ async function parseSessionFile(filePath) {
|
|
|
427
447
|
}
|
|
428
448
|
const modelId = String(message?.model ?? "unknown");
|
|
429
449
|
const eventTimeMs = Date.parse(String(payloadObject.timestamp ?? ""));
|
|
450
|
+
const entrypoint = typeof payloadObject.entrypoint === "string" ? payloadObject.entrypoint : "";
|
|
430
451
|
const rateLimits = extractRateLimits(payloadObject, message);
|
|
431
452
|
const normalizedUsage = normalizeUsage(usage);
|
|
432
453
|
const usageKey = buildUsageEventKey(payloadObject, message);
|
|
433
454
|
const usageSignature = buildUsageSignature(payloadObject, modelId, normalizedUsage);
|
|
455
|
+
assistantEntryPoints.add(entrypoint);
|
|
434
456
|
events.push({
|
|
435
|
-
entrypoint
|
|
457
|
+
entrypoint,
|
|
436
458
|
usageKey,
|
|
437
459
|
usageSignature,
|
|
438
460
|
timestampMs: eventTimeMs,
|
|
@@ -441,7 +463,101 @@ async function parseSessionFile(filePath) {
|
|
|
441
463
|
rateLimits
|
|
442
464
|
});
|
|
443
465
|
}
|
|
444
|
-
return {
|
|
466
|
+
return {
|
|
467
|
+
filePath,
|
|
468
|
+
sessionGroupKey: buildClaudeSessionGroupKey(sessionsRoot, filePath),
|
|
469
|
+
linesRead,
|
|
470
|
+
malformedLines,
|
|
471
|
+
sourceKind: "unknown",
|
|
472
|
+
sourceReason: "unclassified",
|
|
473
|
+
signals: {
|
|
474
|
+
assistantEntryPoints: [...assistantEntryPoints].sort(),
|
|
475
|
+
hasIdeOpenedFileAttachment,
|
|
476
|
+
hasIdeOpenedFileMarker,
|
|
477
|
+
hasIdeTooling,
|
|
478
|
+
hasQueueOperations
|
|
479
|
+
},
|
|
480
|
+
events
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
function buildClaudeSessionGroupKey(sessionsRoot, filePath) {
|
|
484
|
+
const relativePath = path.relative(sessionsRoot, filePath);
|
|
485
|
+
if (!relativePath || relativePath.startsWith("..")) {
|
|
486
|
+
return filePath;
|
|
487
|
+
}
|
|
488
|
+
const normalizedRelativePath = relativePath.split(path.sep).join("/");
|
|
489
|
+
const subagentMatch = normalizedRelativePath.match(/^(.*\/[^/]+)\/subagents\/[^/]+\.jsonl$/);
|
|
490
|
+
if (subagentMatch?.[1]) {
|
|
491
|
+
return subagentMatch[1];
|
|
492
|
+
}
|
|
493
|
+
return normalizedRelativePath.replace(/\.jsonl$/i, "");
|
|
494
|
+
}
|
|
495
|
+
function inferClaudeSessionFileSources(files) {
|
|
496
|
+
const groups = new Map();
|
|
497
|
+
for (const file of files) {
|
|
498
|
+
const group = groups.get(file.sessionGroupKey) ?? {
|
|
499
|
+
assistantEntryPoints: new Set(),
|
|
500
|
+
hasIdeHints: false
|
|
501
|
+
};
|
|
502
|
+
for (const entrypoint of file.signals.assistantEntryPoints) {
|
|
503
|
+
group.assistantEntryPoints.add(entrypoint);
|
|
504
|
+
}
|
|
505
|
+
group.hasIdeHints =
|
|
506
|
+
group.hasIdeHints ||
|
|
507
|
+
file.signals.hasIdeOpenedFileAttachment ||
|
|
508
|
+
file.signals.hasIdeOpenedFileMarker ||
|
|
509
|
+
file.signals.hasIdeTooling ||
|
|
510
|
+
file.signals.hasQueueOperations;
|
|
511
|
+
groups.set(file.sessionGroupKey, group);
|
|
512
|
+
}
|
|
513
|
+
for (const file of files) {
|
|
514
|
+
const group = groups.get(file.sessionGroupKey);
|
|
515
|
+
const { kind, reason } = classifyClaudeSessionGroup(group);
|
|
516
|
+
file.sourceKind = kind;
|
|
517
|
+
file.sourceReason = reason;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
function classifyClaudeSessionGroup(group) {
|
|
521
|
+
if (!group) {
|
|
522
|
+
return { kind: "unknown", reason: "missing session group signals" };
|
|
523
|
+
}
|
|
524
|
+
if (group.assistantEntryPoints.has("claude-vscode")) {
|
|
525
|
+
return { kind: "vscode", reason: "explicit claude-vscode entrypoint" };
|
|
526
|
+
}
|
|
527
|
+
if (group.assistantEntryPoints.has("sdk-cli") || group.assistantEntryPoints.has("claude")) {
|
|
528
|
+
return { kind: "cli", reason: "explicit sdk-cli/claude entrypoint" };
|
|
529
|
+
}
|
|
530
|
+
if (group.assistantEntryPoints.has("cli")) {
|
|
531
|
+
return group.hasIdeHints
|
|
532
|
+
? { kind: "vscode", reason: "generic cli entrypoint with IDE session hints" }
|
|
533
|
+
: { kind: "cli", reason: "generic cli entrypoint without IDE session hints" };
|
|
534
|
+
}
|
|
535
|
+
return { kind: "unknown", reason: "no assistant entrypoints" };
|
|
536
|
+
}
|
|
537
|
+
function attachmentHasIdeTooling(attachment) {
|
|
538
|
+
if (attachment?.type !== "deferred_tools_delta") {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
return extractStringArray(attachment.addedNames).some((name) => name.startsWith("mcp__ide__"));
|
|
542
|
+
}
|
|
543
|
+
function messageContainsIdeOpenedFileMarker(message) {
|
|
544
|
+
const content = message?.content;
|
|
545
|
+
if (typeof content === "string") {
|
|
546
|
+
return content.includes("<ide_opened_file>");
|
|
547
|
+
}
|
|
548
|
+
if (!Array.isArray(content)) {
|
|
549
|
+
return false;
|
|
550
|
+
}
|
|
551
|
+
return content.some((item) => {
|
|
552
|
+
const contentItem = asRecord(item);
|
|
553
|
+
return typeof contentItem?.text === "string" && contentItem.text.includes("<ide_opened_file>");
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
function extractStringArray(value) {
|
|
557
|
+
if (!Array.isArray(value)) {
|
|
558
|
+
return [];
|
|
559
|
+
}
|
|
560
|
+
return value.filter((item) => typeof item === "string");
|
|
445
561
|
}
|
|
446
562
|
function buildUsageEventKey(payloadObject, message) {
|
|
447
563
|
const sessionId = String(payloadObject.sessionId ?? "");
|
|
@@ -509,6 +625,18 @@ function shouldReplaceUsageEvent(previous, next) {
|
|
|
509
625
|
}
|
|
510
626
|
return false;
|
|
511
627
|
}
|
|
628
|
+
function matchesClaudeProviderEvent(event, file, entrypoints, usageCommandKind) {
|
|
629
|
+
if (entrypoints.has(event.entrypoint)) {
|
|
630
|
+
return true;
|
|
631
|
+
}
|
|
632
|
+
if (event.entrypoint !== "cli") {
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
if (usageCommandKind === "vscode") {
|
|
636
|
+
return file.sourceKind === "vscode";
|
|
637
|
+
}
|
|
638
|
+
return file.sourceKind === "cli";
|
|
639
|
+
}
|
|
512
640
|
function normalizeTimestamp(value) {
|
|
513
641
|
return Number.isFinite(value) ? value : Number.NEGATIVE_INFINITY;
|
|
514
642
|
}
|