@agent-finops/core 0.7.0 → 0.7.2
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/dist/activitySnapshot.d.ts +11 -28
- package/dist/activitySnapshot.js +19 -10
- package/dist/agentEconomicsReceipt.d.ts +934 -0
- package/dist/agentEconomicsReceipt.js +1005 -0
- package/dist/contextHealth.js +3 -1
- package/dist/glance.js +16 -16
- package/dist/index.d.ts +5 -1
- package/dist/index.js +3 -1
- package/dist/localAgentFormats/registry.d.ts +8 -0
- package/dist/localAgentFormats/registry.js +219 -0
- package/dist/localAgentFormats/runtimeRegistry.d.ts +4 -0
- package/dist/localAgentFormats/runtimeRegistry.js +55 -0
- package/dist/localAgentFormats/types.d.ts +84 -0
- package/dist/localAgentFormats/types.js +2 -0
- package/dist/localAgentLogs.d.ts +19 -2
- package/dist/localAgentLogs.js +234 -172
- package/dist/sourceStatus.d.ts +2 -1
- package/dist/sourceStatus.js +7 -13
- package/package.json +1 -1
package/dist/localAgentLogs.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createReadStream } from "node:fs";
|
|
2
2
|
import { lstat, open, readdir, readFile, stat } from "node:fs/promises";
|
|
3
|
-
import { basename, isAbsolute, join, resolve, sep } from "node:path";
|
|
3
|
+
import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { createInterface } from "node:readline";
|
|
6
6
|
import { estimateTokenCostUsd } from "./modelPricing.js";
|
|
7
7
|
import { redactSecrets } from "./discovery.js";
|
|
8
|
-
import {
|
|
8
|
+
import { localAgentFormatDescriptors, localAgentFormatLabel, matchesLocalAgentFormatFile, validateLocalAgentFormatDescriptors } from "./localAgentFormats/registry.js";
|
|
9
9
|
/**
|
|
10
10
|
* Resolve the repository root most recently observed in transcript metadata.
|
|
11
11
|
*
|
|
@@ -551,9 +551,16 @@ function parseCodexRateLimitWindow(value) {
|
|
|
551
551
|
}
|
|
552
552
|
/** Scan this machine's agent logs and return aggregated UsageRecords. */
|
|
553
553
|
export async function loadLocalAgentUsage(options = {}) {
|
|
554
|
+
return loadLocalAgentUsageWithFormats(await localAgentFormatRuntimes(), options);
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Registry-driven ingestion engine. Exported from this module for registry
|
|
558
|
+
* contract tests and format modules, but intentionally omitted from the
|
|
559
|
+
* package-root API.
|
|
560
|
+
*/
|
|
561
|
+
export async function loadLocalAgentUsageWithFormats(registry, options = {}) {
|
|
562
|
+
validateLocalAgentFormatDescriptors(registry.map((entry) => entry.descriptor));
|
|
554
563
|
const home = homedir();
|
|
555
|
-
const claudeDir = options.claudeProjectsDir ?? join(home, ".claude", "projects");
|
|
556
|
-
const codexDir = options.codexSessionsDir ?? join(home, ".codex", "sessions");
|
|
557
564
|
const calls = [];
|
|
558
565
|
const codexInvocationFiles = options.collectCodexInvocationEvidence
|
|
559
566
|
? []
|
|
@@ -562,59 +569,52 @@ export async function loadLocalAgentUsage(options = {}) {
|
|
|
562
569
|
const since = options.sinceIso ? Date.parse(options.sinceIso) : undefined;
|
|
563
570
|
const sinceMs = typeof since === "number" && Number.isFinite(since) ? since : undefined;
|
|
564
571
|
const diagnostics = [];
|
|
565
|
-
const sourceScans = [
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
content
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
572
|
+
const sourceScans = [];
|
|
573
|
+
for (const runtime of registry) {
|
|
574
|
+
const { descriptor } = runtime;
|
|
575
|
+
const scan = emptySourceScan(descriptor.id);
|
|
576
|
+
sourceScans.push(scan);
|
|
577
|
+
const root = localAgentFormatRoot(descriptor, options, home);
|
|
578
|
+
for (const file of await listFormatCandidateFiles(root, descriptor, scan, diagnostics)) {
|
|
579
|
+
if (!matchesLocalAgentFormatFile(descriptor, file))
|
|
580
|
+
continue;
|
|
581
|
+
let content;
|
|
582
|
+
try {
|
|
583
|
+
content = await readFile(file, "utf8");
|
|
584
|
+
}
|
|
585
|
+
catch (error) {
|
|
586
|
+
recordUnreadableFile(descriptor.id, scan, diagnostics, error);
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
if (!content)
|
|
590
|
+
continue;
|
|
591
|
+
filesParsed += 1;
|
|
592
|
+
scan.filesParsed += 1;
|
|
593
|
+
const collectInvocationEvidence = Boolean(codexInvocationFiles) &&
|
|
594
|
+
descriptor.id === "codex" && descriptor.capabilities.invocationEvidence;
|
|
595
|
+
const parsed = runtime.parseFull({
|
|
596
|
+
content,
|
|
597
|
+
filePath: file,
|
|
598
|
+
sinceMs,
|
|
599
|
+
collectInvocationEvidence,
|
|
600
|
+
onDiagnostic: (diagnostic) => {
|
|
601
|
+
recordParseDiagnostic(descriptor.id, scan, diagnostics, diagnostic);
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
assertFormatCallOwnership(descriptor, parsed.calls);
|
|
605
|
+
assertInvocationOwnership(descriptor, parsed.invocationFile, collectInvocationEvidence);
|
|
606
|
+
calls.push(...parsed.calls);
|
|
607
|
+
if (codexInvocationFiles && collectInvocationEvidence && parsed.invocationFile) {
|
|
608
|
+
codexInvocationFiles.push(parsed.invocationFile);
|
|
609
|
+
}
|
|
598
610
|
}
|
|
599
|
-
if (!content)
|
|
600
|
-
continue;
|
|
601
|
-
filesParsed += 1;
|
|
602
|
-
codexScan.filesParsed += 1;
|
|
603
|
-
const collector = codexInvocationFiles
|
|
604
|
-
? createCodexInvocationCollector(sinceMs)
|
|
605
|
-
: undefined;
|
|
606
|
-
calls.push(...parseCodexRollout(content, collector?.consume, (diagnostic) => {
|
|
607
|
-
recordParseDiagnostic("codex", codexScan, diagnostics, diagnostic);
|
|
608
|
-
}));
|
|
609
|
-
if (collector)
|
|
610
|
-
codexInvocationFiles.push(collector.finish());
|
|
611
611
|
}
|
|
612
612
|
const normalizedCalls = dedupeCumulativeSessionCalls(calls);
|
|
613
613
|
const filtered = typeof sinceMs === "number"
|
|
614
614
|
? normalizedCalls.filter((call) => Date.parse(call.timestamp) >= sinceMs)
|
|
615
615
|
: normalizedCalls;
|
|
616
616
|
return {
|
|
617
|
-
records:
|
|
617
|
+
records: aggregateCallsForFormats(filtered, registry.map((entry) => entry.descriptor)),
|
|
618
618
|
calls: filtered,
|
|
619
619
|
filesParsed,
|
|
620
620
|
agentsDetected: [...new Set(filtered.map((call) => call.agent))],
|
|
@@ -634,129 +634,183 @@ export async function loadLocalAgentUsage(options = {}) {
|
|
|
634
634
|
* tool-workdir inference remains exclusive to the full qualitative loader.
|
|
635
635
|
*/
|
|
636
636
|
export async function loadLocalAgentFinancialUsage(options = {}) {
|
|
637
|
+
return loadLocalAgentFinancialUsageWithFormats(await localAgentFormatRuntimes(), options);
|
|
638
|
+
}
|
|
639
|
+
/** Registry-driven financial-only engine; package-root exports stay unchanged. */
|
|
640
|
+
export async function loadLocalAgentFinancialUsageWithFormats(registry, options = {}) {
|
|
641
|
+
validateLocalAgentFormatDescriptors(registry.map((entry) => entry.descriptor));
|
|
637
642
|
const home = homedir();
|
|
638
|
-
const claudeDir = options.claudeProjectsDir ?? join(home, ".claude", "projects");
|
|
639
|
-
const codexDir = options.codexSessionsDir ?? join(home, ".codex", "sessions");
|
|
640
|
-
const claudeCalls = [];
|
|
641
|
-
const codexCalls = [];
|
|
642
643
|
const since = options.sinceIso ? Date.parse(options.sinceIso) : undefined;
|
|
643
644
|
const sinceMs = typeof since === "number" && Number.isFinite(since) ? since : undefined;
|
|
644
|
-
const
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
{
|
|
653
|
-
...emptySourceScan("codex"),
|
|
654
|
-
filesSkippedBeforeWindow: 0,
|
|
655
|
-
filesReadFinancially: 0,
|
|
656
|
-
bytesSkippedAsNonFinancialHistory: 0,
|
|
657
|
-
nonFinancialLinesPrefiltered: 0,
|
|
658
|
-
nonFinancialBytesPrefiltered: 0,
|
|
659
|
-
jsonlValidationCoverage: "complete"
|
|
660
|
-
}
|
|
661
|
-
];
|
|
662
|
-
const claudeScan = sourceScans[0];
|
|
663
|
-
const codexScan = sourceScans[1];
|
|
664
|
-
const scanClaude = async () => {
|
|
665
|
-
for (const file of await listJsonlFiles(claudeDir, claudeScan, claudeDiagnostics)) {
|
|
666
|
-
if (!await shouldStreamFile(file, sinceMs, "claude-code", claudeScan, claudeDiagnostics)) {
|
|
645
|
+
const scanned = await Promise.all(registry.map(async (runtime) => {
|
|
646
|
+
const { descriptor } = runtime;
|
|
647
|
+
const diagnostics = [];
|
|
648
|
+
const scan = financialSourceScan(descriptor);
|
|
649
|
+
const calls = [];
|
|
650
|
+
const root = localAgentFormatRoot(descriptor, options, home);
|
|
651
|
+
for (const file of await listFormatCandidateFiles(root, descriptor, scan, diagnostics)) {
|
|
652
|
+
if (!matchesLocalAgentFormatFile(descriptor, file))
|
|
667
653
|
continue;
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
try {
|
|
674
|
-
streamed = await streamJsonlRecords(file, (entry) => {
|
|
675
|
-
const call = parseClaudeFinancialEntry(entry, file, sinceMs, seen, (diagnostic) => fileDiagnostics.push(diagnostic));
|
|
676
|
-
if (call)
|
|
677
|
-
fileCalls.push(call);
|
|
678
|
-
});
|
|
679
|
-
}
|
|
680
|
-
catch (error) {
|
|
681
|
-
recordUnreadableFile("claude-code", claudeScan, claudeDiagnostics, error);
|
|
682
|
-
continue;
|
|
683
|
-
}
|
|
684
|
-
if (!streamed.hadContent)
|
|
685
|
-
continue;
|
|
686
|
-
claudeScan.filesParsed += 1;
|
|
687
|
-
claudeCalls.push(...fileCalls);
|
|
688
|
-
for (const diagnostic of fileDiagnostics) {
|
|
689
|
-
recordParseDiagnostic("claude-code", claudeScan, claudeDiagnostics, diagnostic);
|
|
690
|
-
}
|
|
691
|
-
if (streamed.malformedLines > 0) {
|
|
692
|
-
recordParseDiagnostic("claude-code", claudeScan, claudeDiagnostics, {
|
|
693
|
-
code: "malformed_jsonl",
|
|
694
|
-
count: streamed.malformedLines
|
|
695
|
-
});
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
};
|
|
699
|
-
const scanCodex = async () => {
|
|
700
|
-
for (const file of await listJsonlFiles(codexDir, codexScan, codexDiagnostics)) {
|
|
701
|
-
if (!basename(file).startsWith("rollout-"))
|
|
702
|
-
continue;
|
|
703
|
-
if (!await shouldStreamFile(file, sinceMs, "codex", codexScan, codexDiagnostics)) {
|
|
704
|
-
continue;
|
|
705
|
-
}
|
|
706
|
-
let financialFile;
|
|
707
|
-
try {
|
|
708
|
-
financialFile = await readCodexFinancialFile(file);
|
|
709
|
-
}
|
|
710
|
-
catch (error) {
|
|
711
|
-
recordUnreadableFile("codex", codexScan, codexDiagnostics, error);
|
|
712
|
-
continue;
|
|
713
|
-
}
|
|
714
|
-
if (!financialFile.hadContent)
|
|
715
|
-
continue;
|
|
716
|
-
codexScan.filesParsed += 1;
|
|
717
|
-
codexScan.filesReadFinancially = (codexScan.filesReadFinancially ?? 0) + 1;
|
|
718
|
-
codexScan.bytesSkippedAsNonFinancialHistory =
|
|
719
|
-
(codexScan.bytesSkippedAsNonFinancialHistory ?? 0) + financialFile.bytesSkipped;
|
|
720
|
-
codexScan.nonFinancialLinesPrefiltered =
|
|
721
|
-
(codexScan.nonFinancialLinesPrefiltered ?? 0) + financialFile.prefilteredLines;
|
|
722
|
-
codexScan.nonFinancialBytesPrefiltered =
|
|
723
|
-
(codexScan.nonFinancialBytesPrefiltered ?? 0) + financialFile.prefilteredBytes;
|
|
724
|
-
if (financialFile.bytesSkipped > 0 || financialFile.prefilteredLines > 0) {
|
|
725
|
-
codexScan.jsonlValidationCoverage = "financial_events_only";
|
|
726
|
-
}
|
|
727
|
-
if (financialFile.malformedLines > 0) {
|
|
728
|
-
recordParseDiagnostic("codex", codexScan, codexDiagnostics, {
|
|
729
|
-
code: "malformed_jsonl",
|
|
730
|
-
count: financialFile.malformedLines
|
|
731
|
-
});
|
|
732
|
-
}
|
|
733
|
-
const state = createCodexFinancialStreamState();
|
|
734
|
-
for (const entry of financialFile.entries) {
|
|
735
|
-
consumeCodexFinancialEntry(state, entry);
|
|
736
|
-
}
|
|
737
|
-
const call = finishCodexFinancialStream(state, (diagnostic) => {
|
|
738
|
-
recordParseDiagnostic("codex", codexScan, codexDiagnostics, diagnostic);
|
|
654
|
+
const parsedCalls = await runtime.parseFinancialFile({
|
|
655
|
+
filePath: file,
|
|
656
|
+
sinceMs,
|
|
657
|
+
scan,
|
|
658
|
+
diagnostics
|
|
739
659
|
});
|
|
740
|
-
|
|
741
|
-
|
|
660
|
+
assertFormatCallOwnership(descriptor, parsedCalls);
|
|
661
|
+
assertFinancialSourceOwnership(descriptor, scan, diagnostics);
|
|
662
|
+
calls.push(...parsedCalls);
|
|
742
663
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
//
|
|
746
|
-
|
|
747
|
-
const calls =
|
|
664
|
+
return { calls, scan, diagnostics };
|
|
665
|
+
}));
|
|
666
|
+
// Sources scan concurrently, but flatten in registry order to preserve the
|
|
667
|
+
// long-standing Claude-then-Codex output and diagnostic contract.
|
|
668
|
+
const calls = scanned.flatMap((entry) => entry.calls);
|
|
748
669
|
const normalizedCalls = dedupeCumulativeSessionCalls(calls);
|
|
749
670
|
const filtered = typeof sinceMs === "number"
|
|
750
671
|
? normalizedCalls.filter((call) => Date.parse(call.timestamp) >= sinceMs)
|
|
751
672
|
: normalizedCalls;
|
|
752
673
|
return {
|
|
753
|
-
records:
|
|
674
|
+
records: aggregateCallsForFormats(filtered, registry.map((entry) => entry.descriptor)),
|
|
754
675
|
calls: filtered,
|
|
755
|
-
filesParsed:
|
|
676
|
+
filesParsed: scanned.reduce((total, entry) => total + entry.scan.filesParsed, 0),
|
|
756
677
|
agentsDetected: [...new Set(filtered.map((call) => call.agent))],
|
|
757
|
-
sourceScans,
|
|
758
|
-
diagnostics:
|
|
678
|
+
sourceScans: scanned.map((entry) => entry.scan),
|
|
679
|
+
diagnostics: scanned.flatMap((entry) => entry.diagnostics)
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
/** @internal Runtime hook owned by the Claude Code registry entry. */
|
|
683
|
+
export async function readClaudeCodeFinancialFileForRegistry(context) {
|
|
684
|
+
const { filePath, sinceMs, scan, diagnostics } = context;
|
|
685
|
+
if (!await shouldStreamFile(filePath, sinceMs, "claude-code", scan, diagnostics)) {
|
|
686
|
+
return [];
|
|
687
|
+
}
|
|
688
|
+
const calls = [];
|
|
689
|
+
const fileDiagnostics = [];
|
|
690
|
+
const seen = new Set();
|
|
691
|
+
let streamed;
|
|
692
|
+
try {
|
|
693
|
+
streamed = await streamJsonlRecords(filePath, (entry) => {
|
|
694
|
+
const call = parseClaudeFinancialEntry(entry, filePath, sinceMs, seen, (diagnostic) => fileDiagnostics.push(diagnostic));
|
|
695
|
+
if (call)
|
|
696
|
+
calls.push(call);
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
catch (error) {
|
|
700
|
+
recordUnreadableFile("claude-code", scan, diagnostics, error);
|
|
701
|
+
return [];
|
|
702
|
+
}
|
|
703
|
+
if (!streamed.hadContent)
|
|
704
|
+
return [];
|
|
705
|
+
scan.filesParsed += 1;
|
|
706
|
+
for (const diagnostic of fileDiagnostics) {
|
|
707
|
+
recordParseDiagnostic("claude-code", scan, diagnostics, diagnostic);
|
|
708
|
+
}
|
|
709
|
+
if (streamed.malformedLines > 0) {
|
|
710
|
+
recordParseDiagnostic("claude-code", scan, diagnostics, {
|
|
711
|
+
code: "malformed_jsonl",
|
|
712
|
+
count: streamed.malformedLines
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
return calls;
|
|
716
|
+
}
|
|
717
|
+
/** @internal Runtime hook owned by the Codex registry entry. */
|
|
718
|
+
export async function readCodexFinancialFileForRegistry(context) {
|
|
719
|
+
const { filePath, sinceMs, scan, diagnostics } = context;
|
|
720
|
+
if (!await shouldStreamFile(filePath, sinceMs, "codex", scan, diagnostics)) {
|
|
721
|
+
return [];
|
|
722
|
+
}
|
|
723
|
+
let financialFile;
|
|
724
|
+
try {
|
|
725
|
+
financialFile = await readCodexFinancialFile(filePath);
|
|
726
|
+
}
|
|
727
|
+
catch (error) {
|
|
728
|
+
recordUnreadableFile("codex", scan, diagnostics, error);
|
|
729
|
+
return [];
|
|
730
|
+
}
|
|
731
|
+
if (!financialFile.hadContent)
|
|
732
|
+
return [];
|
|
733
|
+
scan.filesParsed += 1;
|
|
734
|
+
scan.filesReadFinancially = (scan.filesReadFinancially ?? 0) + 1;
|
|
735
|
+
scan.bytesSkippedAsNonFinancialHistory =
|
|
736
|
+
(scan.bytesSkippedAsNonFinancialHistory ?? 0) + financialFile.bytesSkipped;
|
|
737
|
+
scan.nonFinancialLinesPrefiltered =
|
|
738
|
+
(scan.nonFinancialLinesPrefiltered ?? 0) + financialFile.prefilteredLines;
|
|
739
|
+
scan.nonFinancialBytesPrefiltered =
|
|
740
|
+
(scan.nonFinancialBytesPrefiltered ?? 0) + financialFile.prefilteredBytes;
|
|
741
|
+
if (financialFile.bytesSkipped > 0 || financialFile.prefilteredLines > 0) {
|
|
742
|
+
scan.jsonlValidationCoverage = "financial_events_only";
|
|
743
|
+
}
|
|
744
|
+
if (financialFile.malformedLines > 0) {
|
|
745
|
+
recordParseDiagnostic("codex", scan, diagnostics, {
|
|
746
|
+
code: "malformed_jsonl",
|
|
747
|
+
count: financialFile.malformedLines
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
const state = createCodexFinancialStreamState();
|
|
751
|
+
for (const entry of financialFile.entries) {
|
|
752
|
+
consumeCodexFinancialEntry(state, entry);
|
|
753
|
+
}
|
|
754
|
+
const call = finishCodexFinancialStream(state, (diagnostic) => {
|
|
755
|
+
recordParseDiagnostic("codex", scan, diagnostics, diagnostic);
|
|
756
|
+
});
|
|
757
|
+
return call ? [call] : [];
|
|
758
|
+
}
|
|
759
|
+
async function localAgentFormatRuntimes() {
|
|
760
|
+
const module = await import("./localAgentFormats/runtimeRegistry.js");
|
|
761
|
+
return module.localAgentFormatRuntimeRegistry;
|
|
762
|
+
}
|
|
763
|
+
function localAgentFormatRoot(descriptor, options, home) {
|
|
764
|
+
const registryOverride = options.sourceDirectories?.[descriptor.id];
|
|
765
|
+
if (registryOverride)
|
|
766
|
+
return registryOverride;
|
|
767
|
+
if (descriptor.legacyDirectoryOption === "claudeProjectsDir" && options.claudeProjectsDir) {
|
|
768
|
+
return options.claudeProjectsDir;
|
|
769
|
+
}
|
|
770
|
+
if (descriptor.legacyDirectoryOption === "codexSessionsDir" && options.codexSessionsDir) {
|
|
771
|
+
return options.codexSessionsDir;
|
|
772
|
+
}
|
|
773
|
+
const canonicalHome = resolve(home);
|
|
774
|
+
const root = resolve(canonicalHome, ...descriptor.defaultHomeRelative);
|
|
775
|
+
const fromHome = relative(canonicalHome, root);
|
|
776
|
+
if (fromHome === ".." || fromHome.startsWith(`..${sep}`) || isAbsolute(fromHome)) {
|
|
777
|
+
throw new Error(`Local-agent format ${descriptor.id} resolved outside the home boundary.`);
|
|
778
|
+
}
|
|
779
|
+
return root;
|
|
780
|
+
}
|
|
781
|
+
function assertFormatCallOwnership(descriptor, calls) {
|
|
782
|
+
if (calls.some((call) => call.agent !== descriptor.id)) {
|
|
783
|
+
throw new Error(`Local-agent format ${descriptor.id} emitted a call for a different source.`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
function assertFinancialSourceOwnership(descriptor, scan, diagnostics) {
|
|
787
|
+
if (scan.agent !== descriptor.id || diagnostics.some((entry) => entry.agent !== descriptor.id)) {
|
|
788
|
+
throw new Error(`Local-agent format ${descriptor.id} emitted financial metadata for a different source.`);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
function assertInvocationOwnership(descriptor, invocationFile, collectionAllowed) {
|
|
792
|
+
if (!invocationFile)
|
|
793
|
+
return;
|
|
794
|
+
if (!collectionAllowed || descriptor.id !== "codex" ||
|
|
795
|
+
invocationFile.contextSignal.agent !== descriptor.id) {
|
|
796
|
+
throw new Error(`Local-agent format ${descriptor.id} emitted invocation evidence for a different source.`);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
function financialSourceScan(descriptor) {
|
|
800
|
+
const scan = {
|
|
801
|
+
...emptySourceScan(descriptor.id),
|
|
802
|
+
filesSkippedBeforeWindow: 0
|
|
759
803
|
};
|
|
804
|
+
if (descriptor.financialRead === "bounded_event_jsonl") {
|
|
805
|
+
scan.filesReadFinancially = 0;
|
|
806
|
+
scan.bytesSkippedAsNonFinancialHistory = 0;
|
|
807
|
+
scan.nonFinancialLinesPrefiltered = 0;
|
|
808
|
+
scan.nonFinancialBytesPrefiltered = 0;
|
|
809
|
+
}
|
|
810
|
+
// Keep this after format-specific metrics: persisted/debug JSON has used
|
|
811
|
+
// this exact insertion order since the optimized financial reader shipped.
|
|
812
|
+
scan.jsonlValidationCoverage = "complete";
|
|
813
|
+
return scan;
|
|
760
814
|
}
|
|
761
815
|
async function streamJsonlRecords(file, onRecord) {
|
|
762
816
|
const input = createReadStream(file, { encoding: "utf8" });
|
|
@@ -1267,6 +1321,11 @@ function finishCodexFinancialStream(state, onDiagnostic) {
|
|
|
1267
1321
|
}
|
|
1268
1322
|
/** Aggregate per-call usage into one UsageRecord per day+agent+model+project. */
|
|
1269
1323
|
export function aggregateCalls(calls) {
|
|
1324
|
+
return aggregateCallsForFormats(calls, localAgentFormatDescriptors);
|
|
1325
|
+
}
|
|
1326
|
+
/** @internal Registry-aware aggregation used by the extensible ingestion engine. */
|
|
1327
|
+
export function aggregateCallsForFormats(calls, descriptors) {
|
|
1328
|
+
const formats = new Map(descriptors.map((descriptor) => [descriptor.id, descriptor]));
|
|
1270
1329
|
const groups = new Map();
|
|
1271
1330
|
for (const call of dedupeCumulativeSessionCalls(calls)) {
|
|
1272
1331
|
const day = call.timestamp.slice(0, 10);
|
|
@@ -1276,6 +1335,7 @@ export function aggregateCalls(calls) {
|
|
|
1276
1335
|
const records = [];
|
|
1277
1336
|
for (const [key, groupCalls] of groups) {
|
|
1278
1337
|
const [day, agent, model, project] = key.split("|");
|
|
1338
|
+
const format = formats.get(agent);
|
|
1279
1339
|
const usage = {
|
|
1280
1340
|
inputTokens: sum(groupCalls, (c) => c.usage.inputTokens),
|
|
1281
1341
|
outputTokens: sum(groupCalls, (c) => c.usage.outputTokens),
|
|
@@ -1284,37 +1344,39 @@ export function aggregateCalls(calls) {
|
|
|
1284
1344
|
cacheWrite1hTokens: sum(groupCalls, (c) => c.usage.cacheWrite1hTokens ?? 0)
|
|
1285
1345
|
};
|
|
1286
1346
|
const usageSupported = groupCalls.every((call) => call.usageSupport !== "unsupported_token_shape");
|
|
1287
|
-
const amountUsd = usageSupported ? estimateTokenCostUsd(model, usage) : undefined;
|
|
1347
|
+
const amountUsd = usageSupported && format ? estimateTokenCostUsd(model, usage) : undefined;
|
|
1288
1348
|
const priced = usageSupported && typeof amountUsd === "number";
|
|
1289
1349
|
records.push({
|
|
1290
1350
|
id: slug(["local", agent, day, model, project].join("-")),
|
|
1291
1351
|
timestamp: new Date(`${day}T00:00:00Z`).toISOString(),
|
|
1292
1352
|
source: {
|
|
1293
|
-
id: "local-agent-logs",
|
|
1294
|
-
name: "Local agent session logs",
|
|
1295
|
-
provider:
|
|
1296
|
-
confidence: "estimated",
|
|
1297
|
-
observedFrom:
|
|
1353
|
+
id: format?.sourceRecord.id ?? "local-agent-logs",
|
|
1354
|
+
name: format?.sourceRecord.name ?? "Local agent session logs",
|
|
1355
|
+
provider: format?.provider ?? "unknown",
|
|
1356
|
+
confidence: format?.confidenceDefaults.sourceConfidence ?? "estimated",
|
|
1357
|
+
observedFrom: format?.sourceRecord.observedFrom ?? "unregistered local transcript (this machine)"
|
|
1298
1358
|
},
|
|
1299
1359
|
model,
|
|
1300
1360
|
inputTokens: usage.inputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWrite5mTokens ?? 0) + (usage.cacheWrite1hTokens ?? 0),
|
|
1301
1361
|
outputTokens: usage.outputTokens,
|
|
1302
1362
|
amountUsd: priced ? amountUsd : null,
|
|
1303
|
-
costConfidence: priced
|
|
1363
|
+
costConfidence: priced
|
|
1364
|
+
? format?.confidenceDefaults.pricedFinancialEvidence ?? "estimated"
|
|
1365
|
+
: format?.confidenceDefaults.unpricedFinancialEvidence ?? "missing",
|
|
1304
1366
|
// `(home)` is an attribution fallback, not a real project. Keep it on
|
|
1305
1367
|
// LocalAgentCall for Glance/session context, but do not promote it to a
|
|
1306
1368
|
// high-confidence project id in receipts or the attribution engine.
|
|
1307
1369
|
projectId: project === "unattributed" || project === "(home)" ? undefined : project,
|
|
1308
1370
|
agentId: agent,
|
|
1309
|
-
providerCostType: "local_agent_logs",
|
|
1310
|
-
usageGranularity: "daily_aggregate",
|
|
1371
|
+
providerCostType: format?.sourceRecord.providerCostType ?? "local_agent_logs",
|
|
1372
|
+
usageGranularity: format?.sourceRecord.usageGranularity ?? "daily_aggregate",
|
|
1311
1373
|
quantity: groupCalls.length,
|
|
1312
|
-
operation: `${agent} sessions`
|
|
1374
|
+
operation: format?.sourceRecord.operation ?? `${agent} sessions`
|
|
1313
1375
|
});
|
|
1314
1376
|
}
|
|
1315
1377
|
return records.sort((left, right) => left.id.localeCompare(right.id));
|
|
1316
1378
|
}
|
|
1317
|
-
async function
|
|
1379
|
+
async function listFormatCandidateFiles(root, descriptor, scan, diagnostics) {
|
|
1318
1380
|
let rootStat;
|
|
1319
1381
|
try {
|
|
1320
1382
|
rootStat = await stat(root);
|
|
@@ -1377,7 +1439,7 @@ async function listJsonlFiles(root, scan, diagnostics) {
|
|
|
1377
1439
|
const path = join(dir, entry.name);
|
|
1378
1440
|
if (entry.isDirectory())
|
|
1379
1441
|
queue.push(path);
|
|
1380
|
-
else if (entry.isFile() && entry.name.endsWith(
|
|
1442
|
+
else if (entry.isFile() && (!descriptor.discovery.extension || entry.name.endsWith(descriptor.discovery.extension))) {
|
|
1381
1443
|
out.push(path);
|
|
1382
1444
|
scan.filesDiscovered += 1;
|
|
1383
1445
|
}
|
|
@@ -1428,7 +1490,7 @@ function recordParseDiagnostic(agent, scan, diagnostics, diagnostic) {
|
|
|
1428
1490
|
});
|
|
1429
1491
|
}
|
|
1430
1492
|
function agentLabel(agent) {
|
|
1431
|
-
return agent
|
|
1493
|
+
return localAgentFormatLabel(agent);
|
|
1432
1494
|
}
|
|
1433
1495
|
function errorCodeSuffix(error) {
|
|
1434
1496
|
const code = error instanceof Error
|
package/dist/sourceStatus.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CostConfidence, UsageRecord } from "./schema.js";
|
|
2
|
+
import type { LocalAgentFormatId } from "./localAgentFormats/types.js";
|
|
2
3
|
/**
|
|
3
4
|
* How thoroughly an ingestion path itself has been exercised.
|
|
4
5
|
*
|
|
@@ -11,7 +12,7 @@ export type SourceValidationCoverage = typeof sourceValidationCoverageValues[num
|
|
|
11
12
|
export type FinancialEvidenceStatus = CostConfidence;
|
|
12
13
|
export declare const sourceFreshnessStatusValues: readonly ["fresh", "stale", "not_checked"];
|
|
13
14
|
export type SourceFreshnessStatus = typeof sourceFreshnessStatusValues[number];
|
|
14
|
-
export type SourceStatusId =
|
|
15
|
+
export type SourceStatusId = LocalAgentFormatId | "openai" | "anthropic" | "cursor" | "github-copilot";
|
|
15
16
|
export type SourceStatusDefinition = {
|
|
16
17
|
id: SourceStatusId;
|
|
17
18
|
label: string;
|
package/dist/sourceStatus.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { localAgentFormatDescriptors } from "./localAgentFormats/registry.js";
|
|
1
2
|
/**
|
|
2
3
|
* How thoroughly an ingestion path itself has been exercised.
|
|
3
4
|
*
|
|
@@ -17,20 +18,13 @@ export const sourceFreshnessStatusValues = ["fresh", "stale", "not_checked"];
|
|
|
17
18
|
* reconciliation.
|
|
18
19
|
*/
|
|
19
20
|
export const sourceStatusDefinitions = [
|
|
20
|
-
{
|
|
21
|
-
id:
|
|
22
|
-
label:
|
|
23
|
-
validationCoverage:
|
|
24
|
-
validationNote:
|
|
21
|
+
...localAgentFormatDescriptors.map((descriptor) => ({
|
|
22
|
+
id: descriptor.id,
|
|
23
|
+
label: `${descriptor.label} local logs`,
|
|
24
|
+
validationCoverage: descriptor.confidenceDefaults.validationCoverage,
|
|
25
|
+
validationNote: descriptor.validationNote,
|
|
25
26
|
staleAfterHours: 72
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
id: "codex",
|
|
29
|
-
label: "Codex local logs",
|
|
30
|
-
validationCoverage: "live_verified",
|
|
31
|
-
validationNote: "Local parsing was replayed against live logs; total-only token shapes and unknown aliases remain missing rather than becoming estimated $0.",
|
|
32
|
-
staleAfterHours: 72
|
|
33
|
-
},
|
|
27
|
+
})),
|
|
34
28
|
{
|
|
35
29
|
id: "openai",
|
|
36
30
|
label: "OpenAI Costs and Usage API",
|