@agent-finops/core 0.9.7 → 0.9.8
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/localAgentLogs.d.ts +5 -2
- package/dist/localAgentLogs.js +112 -12
- package/dist/modelPricing.js +3 -0
- package/package.json +1 -1
package/dist/localAgentLogs.d.ts
CHANGED
|
@@ -416,7 +416,10 @@ export declare const SAFE_QUALITATIVE_SCAN_POLICY: Readonly<LocalAgentQualitativ
|
|
|
416
416
|
* intentionally unavailable: this path reads only the evidence needed for a
|
|
417
417
|
* financial snapshot and transcript-reported plan limits.
|
|
418
418
|
*/
|
|
419
|
-
export type LocalAgentFinancialLogOptions = Omit<LocalAgentLogOptions, "collectCodexInvocationEvidence" | "qualitativeScan" | "qualitativeIndex" | "ownershipIndex" | "coverageProjectRef"
|
|
419
|
+
export type LocalAgentFinancialLogOptions = Omit<LocalAgentLogOptions, "collectCodexInvocationEvidence" | "qualitativeScan" | "qualitativeIndex" | "ownershipIndex" | "coverageProjectRef"> & {
|
|
420
|
+
/** Explicit Workspace lane: Codex counter deltas at each UTC event, never lifetime totals on the last day. */
|
|
421
|
+
workspaceDailyFacts?: boolean;
|
|
422
|
+
};
|
|
420
423
|
export type LocalAgentLogDiagnosticCode = "directory_missing" | "directory_unreadable" | "file_unreadable" | "malformed_jsonl" | "malformed_session_file" | "unsupported_token_shape" | "qualitative_scan_incomplete" | "qualitative_index_error";
|
|
421
424
|
export type LocalAgentLogDiagnostic = {
|
|
422
425
|
agent: LocalAgentCall["agent"];
|
|
@@ -582,7 +585,7 @@ export declare function loadLocalAgentFinancialUsage(options?: LocalAgentFinanci
|
|
|
582
585
|
/** Registry-driven financial-only engine; package-root exports stay unchanged. */
|
|
583
586
|
export declare function loadLocalAgentFinancialUsageWithFormats(registry: readonly LocalAgentFormatRuntime[], options?: LocalAgentFinancialLogOptions): Promise<LocalAgentLogResult>;
|
|
584
587
|
/** @internal Runtime hook owned by the Claude Code registry entry. */
|
|
585
|
-
export declare function readClaudeCodeFinancialFileForRegistry(context: LocalAgentFormatFinancialFileContext): Promise<LocalAgentCall[]>;
|
|
588
|
+
export declare function readClaudeCodeFinancialFileForRegistry(context: LocalAgentFormatFinancialFileContext, workspaceDailyFacts?: boolean): Promise<LocalAgentCall[]>;
|
|
586
589
|
/** @internal Runtime hook owned by the Codex registry entry. */
|
|
587
590
|
export declare function readCodexFinancialFileForRegistry(context: LocalAgentFormatFinancialFileContext): Promise<LocalAgentCall[]>;
|
|
588
591
|
/** @internal Runtime hook owned by the Gemini CLI registry entry. */
|
package/dist/localAgentLogs.js
CHANGED
|
@@ -228,6 +228,20 @@ function parseClaudeFinancialUsage(value, onDiagnostic) {
|
|
|
228
228
|
const write1hField = cacheCreation
|
|
229
229
|
? optionalTokenComponent(cacheCreation, "ephemeral_1h_input_tokens")
|
|
230
230
|
: { present: false };
|
|
231
|
+
const writeTotal = writeTotalField.value;
|
|
232
|
+
let write5m = write5mField.value;
|
|
233
|
+
let write1h = write1hField.value;
|
|
234
|
+
const writeSplitConsistent = writeTotal === undefined || ((write5m === undefined || write5m <= writeTotal) &&
|
|
235
|
+
(write1h === undefined || write1h <= writeTotal) &&
|
|
236
|
+
(write5m === undefined || write1h === undefined || write5m + write1h === writeTotal));
|
|
237
|
+
// A total plus one disjoint duration determines the other duration. Never
|
|
238
|
+
// add the entire total to the supplied duration or invent a negative split.
|
|
239
|
+
if (writeTotal !== undefined && writeSplitConsistent) {
|
|
240
|
+
if (write5m === undefined && write1h !== undefined)
|
|
241
|
+
write5m = writeTotal - write1h;
|
|
242
|
+
else if (write1h === undefined && write5m !== undefined)
|
|
243
|
+
write1h = writeTotal - write5m;
|
|
244
|
+
}
|
|
231
245
|
const componentsSupported = inputTokens !== undefined &&
|
|
232
246
|
outputTokens !== undefined &&
|
|
233
247
|
(!cacheReadField.present || cacheReadField.value !== undefined) &&
|
|
@@ -235,16 +249,18 @@ function parseClaudeFinancialUsage(value, onDiagnostic) {
|
|
|
235
249
|
(!reportedTotalField.present || reportedTotalField.value !== undefined) &&
|
|
236
250
|
(!cacheCreationPresent || Boolean(cacheCreation)) &&
|
|
237
251
|
(!write5mField.present || write5mField.value !== undefined) &&
|
|
238
|
-
(!write1hField.present || write1hField.value !== undefined)
|
|
252
|
+
(!write1hField.present || write1hField.value !== undefined) &&
|
|
253
|
+
writeSplitConsistent;
|
|
239
254
|
const usage = {
|
|
240
255
|
// Retain every valid component for partial evidence, but never let a
|
|
241
256
|
// missing/invalid required field become a priceable zero-dollar call.
|
|
242
257
|
inputTokens: inputTokens ?? 0,
|
|
243
258
|
outputTokens: outputTokens ?? 0,
|
|
244
259
|
cacheReadTokens: cacheReadField.value ?? 0,
|
|
245
|
-
//
|
|
246
|
-
|
|
247
|
-
|
|
260
|
+
// With no duration split at all, keep the existing cheaper-bound estimate.
|
|
261
|
+
// Partial evidence without a total remains partial, never a complete sum.
|
|
262
|
+
cacheWrite5mTokens: write5m ?? (write1h === undefined ? writeTotal : undefined) ?? 0,
|
|
263
|
+
cacheWrite1hTokens: write1h ?? 0
|
|
248
264
|
};
|
|
249
265
|
if (componentsSupported) {
|
|
250
266
|
const cacheWriteEvidence = writeTotalField.value !== undefined ||
|
|
@@ -1327,6 +1343,11 @@ export async function loadLocalAgentFinancialUsage(options = {}) {
|
|
|
1327
1343
|
/** Registry-driven financial-only engine; package-root exports stay unchanged. */
|
|
1328
1344
|
export async function loadLocalAgentFinancialUsageWithFormats(registry, options = {}) {
|
|
1329
1345
|
validateLocalAgentFormatDescriptors(registry.map((entry) => entry.descriptor));
|
|
1346
|
+
// Cumulative financial caches cannot supply event-day deltas. Keep their semantics unchanged.
|
|
1347
|
+
if (options.workspaceDailyFacts) {
|
|
1348
|
+
options = { ...options, financialIndex: undefined };
|
|
1349
|
+
registry = registry.filter(runtime => runtime.descriptor.id === "claude-code" || runtime.descriptor.id === "codex");
|
|
1350
|
+
}
|
|
1330
1351
|
const home = homedir();
|
|
1331
1352
|
const since = options.sinceIso ? Date.parse(options.sinceIso) : undefined;
|
|
1332
1353
|
const sinceMs = typeof since === "number" && Number.isFinite(since) ? since : undefined;
|
|
@@ -1401,12 +1422,12 @@ export async function loadLocalAgentFinancialUsageWithFormats(registry, options
|
|
|
1401
1422
|
const diagnosticsBefore = diagnostics.length;
|
|
1402
1423
|
const unreadableBefore = scan.unreadableFiles;
|
|
1403
1424
|
const filesParsedBefore = scan.filesParsed;
|
|
1404
|
-
const
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1425
|
+
const context = { filePath: file, sinceMs, scan, diagnostics };
|
|
1426
|
+
const parsedCalls = options.workspaceDailyFacts && descriptor.id === "codex"
|
|
1427
|
+
? await readCodexDailyFinancialFile(context)
|
|
1428
|
+
: options.workspaceDailyFacts && descriptor.id === "claude-code"
|
|
1429
|
+
? await readClaudeCodeFinancialFileForRegistry(context, true)
|
|
1430
|
+
: await runtime.parseFinancialFile(context);
|
|
1410
1431
|
assertFormatCallOwnership(descriptor, parsedCalls);
|
|
1411
1432
|
assertFinancialSourceOwnership(descriptor, scan, diagnostics);
|
|
1412
1433
|
calls.push(...parsedCalls);
|
|
@@ -1588,7 +1609,7 @@ function codexHeaderAttribution(header) {
|
|
|
1588
1609
|
};
|
|
1589
1610
|
}
|
|
1590
1611
|
/** @internal Runtime hook owned by the Claude Code registry entry. */
|
|
1591
|
-
export async function readClaudeCodeFinancialFileForRegistry(context) {
|
|
1612
|
+
export async function readClaudeCodeFinancialFileForRegistry(context, workspaceDailyFacts = false) {
|
|
1592
1613
|
const { filePath, sinceMs, scan, diagnostics } = context;
|
|
1593
1614
|
if (!await shouldStreamFile(filePath, sinceMs, "claude-code", scan, diagnostics)) {
|
|
1594
1615
|
return [];
|
|
@@ -1604,8 +1625,16 @@ export async function readClaudeCodeFinancialFileForRegistry(context) {
|
|
|
1604
1625
|
// file so a narrow-window run can never truncate a wider one. The
|
|
1605
1626
|
// loader's final timestamp filter performs all narrowing.
|
|
1606
1627
|
undefined, seen, (diagnostic) => fileDiagnostics.push(diagnostic));
|
|
1607
|
-
if (call)
|
|
1628
|
+
if (call) {
|
|
1629
|
+
if (workspaceDailyFacts) {
|
|
1630
|
+
const nativeAgent = stringOf(entry.agentId);
|
|
1631
|
+
const pathAgent = filePath.split(sep).includes("subagents")
|
|
1632
|
+
? basename(filePath).match(/^agent-([A-Za-z0-9_-]+)\.jsonl$/)?.[1] : undefined;
|
|
1633
|
+
if (nativeAgent || pathAgent)
|
|
1634
|
+
call.subagentId = nativeAgent ?? pathAgent;
|
|
1635
|
+
}
|
|
1608
1636
|
calls.push(call);
|
|
1637
|
+
}
|
|
1609
1638
|
});
|
|
1610
1639
|
}
|
|
1611
1640
|
catch (error) {
|
|
@@ -1668,6 +1697,77 @@ export async function readCodexFinancialFileForRegistry(context) {
|
|
|
1668
1697
|
});
|
|
1669
1698
|
return call ? [call] : [];
|
|
1670
1699
|
}
|
|
1700
|
+
/** Workspace-only event deltas. The existing snapshot reader remains cumulative. */
|
|
1701
|
+
async function readCodexDailyFinancialFile(context) {
|
|
1702
|
+
const { filePath, sinceMs, scan, diagnostics } = context;
|
|
1703
|
+
if (!await shouldStreamFile(filePath, sinceMs, "codex", scan, diagnostics))
|
|
1704
|
+
return [];
|
|
1705
|
+
const state = createCodexFinancialStreamState(), calls = [];
|
|
1706
|
+
const report = () => recordParseDiagnostic("codex", scan, diagnostics, { code: "unsupported_token_shape", count: 1 });
|
|
1707
|
+
let priorEventAt;
|
|
1708
|
+
try {
|
|
1709
|
+
const streamed = await streamJsonlRecords(filePath, entry => {
|
|
1710
|
+
const payload = isRecord(entry.payload) ? entry.payload : undefined;
|
|
1711
|
+
const previousTotal = state.lastTotal ?? state.inheritedUsageBaseline;
|
|
1712
|
+
consumeCodexFinancialEntry(state, entry);
|
|
1713
|
+
if (entry.type !== "event_msg" || payload?.type !== "token_count"
|
|
1714
|
+
|| state.hasInheritedHistory && !state.rootTaskStarted)
|
|
1715
|
+
return;
|
|
1716
|
+
const info = isRecord(payload.info) ? payload.info : undefined;
|
|
1717
|
+
const total = info && isRecord(info.total_token_usage) ? info.total_token_usage : undefined;
|
|
1718
|
+
if (!total) {
|
|
1719
|
+
// A usage-bearing event without its cumulative endpoint cannot be
|
|
1720
|
+
// placed safely: a later counter may span this event's UTC day.
|
|
1721
|
+
if (info && isRecord(info.last_token_usage))
|
|
1722
|
+
report();
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
const timestamp = toIso(stringOf(entry.timestamp));
|
|
1726
|
+
if (!timestamp || !state.sessionId) {
|
|
1727
|
+
report();
|
|
1728
|
+
return;
|
|
1729
|
+
}
|
|
1730
|
+
const parsed = parseCodexCumulativeUsage(total, previousTotal);
|
|
1731
|
+
// Missing initial history cannot be assigned to a later day. A changed or
|
|
1732
|
+
// decreasing counter remains unknown at that event; the next exact pair
|
|
1733
|
+
// can establish a delta without pretending the reset was a zero.
|
|
1734
|
+
const firstDayKnown = !!previousTotal || !!state.startedAt && state.startedAt.slice(0, 10) === timestamp.slice(0, 10);
|
|
1735
|
+
const chronological = !priorEventAt || timestamp >= priorEventAt;
|
|
1736
|
+
const cachedShapeStable = !previousTotal ||
|
|
1737
|
+
Object.hasOwn(total, "cached_input_tokens") === Object.hasOwn(previousTotal, "cached_input_tokens");
|
|
1738
|
+
const supported = parsed.supported && firstDayKnown && chronological && cachedShapeStable;
|
|
1739
|
+
priorEventAt = timestamp;
|
|
1740
|
+
if (supported && previousTotal && parsed.usage.inputTokens === 0 && parsed.usage.outputTokens === 0
|
|
1741
|
+
&& (parsed.usage.cacheReadTokens ?? 0) === 0 && (parsed.reportedTotalTokens ?? 0) === 0)
|
|
1742
|
+
return;
|
|
1743
|
+
if (!supported)
|
|
1744
|
+
report();
|
|
1745
|
+
const workingDirectory = absoluteWorkingDirectory(state.rootCwd);
|
|
1746
|
+
// The cumulative endpoint fingerprint is stable across copies of a
|
|
1747
|
+
// rollout. Different deltas for that same endpoint become a conflict in
|
|
1748
|
+
// dedupeCumulativeSessionCalls rather than two financial contributions.
|
|
1749
|
+
const callId = `callref_${createHash("sha256").update("codex-workspace-counter-event-v1\0")
|
|
1750
|
+
.update(JSON.stringify([state.sessionId, timestamp, total.input_tokens ?? null,
|
|
1751
|
+
total.output_tokens ?? null, total.cached_input_tokens ?? null, total.total_tokens ?? null])).digest("hex")}`;
|
|
1752
|
+
calls.push({ agent: "codex", sessionId: state.sessionId, callId,
|
|
1753
|
+
model: state.model ?? "codex", timestamp, startedAt: timestamp,
|
|
1754
|
+
project: projectFromCwd(workingDirectory), workingDirectory,
|
|
1755
|
+
usageScope: "turn", usageSupport: supported ? "complete" : "unsupported_token_shape",
|
|
1756
|
+
usage: parsed.usage,
|
|
1757
|
+
...(supported && parsed.tokenComponentEvidence ? { tokenComponentEvidence: parsed.tokenComponentEvidence } : {}),
|
|
1758
|
+
...(parsed.reportedTotalTokens !== undefined ? { reportedTotalTokens: parsed.reportedTotalTokens } : {}) });
|
|
1759
|
+
});
|
|
1760
|
+
if (streamed.hadContent)
|
|
1761
|
+
scan.filesParsed++;
|
|
1762
|
+
if (streamed.malformedLines)
|
|
1763
|
+
recordParseDiagnostic("codex", scan, diagnostics, { code: "malformed_jsonl", count: streamed.malformedLines });
|
|
1764
|
+
return calls;
|
|
1765
|
+
}
|
|
1766
|
+
catch (error) {
|
|
1767
|
+
recordUnreadableFile("codex", scan, diagnostics, error);
|
|
1768
|
+
return [];
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1671
1771
|
/** @internal Runtime hook owned by the Gemini CLI registry entry. */
|
|
1672
1772
|
export async function readGeminiFinancialFileForRegistry(context) {
|
|
1673
1773
|
const { filePath, sinceMs, scan, diagnostics } = context;
|
package/dist/modelPricing.js
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
export const PRICING_TABLE_AS_OF = "2026-08-25";
|
|
11
11
|
const pricingRules = [
|
|
12
12
|
// Anthropic
|
|
13
|
+
// Reviewed 2026-09-20: platform.claude.com/docs/en/about-claude/pricing.
|
|
14
|
+
// Fable/Mythos 5.1 keep base/write rates; cache reads are $0.25 per MTok.
|
|
15
|
+
{ match: /^claude-(?:fable|mythos)-5-1$/i, inputPerM: 10, outputPerM: 50, cacheReadPerM: 0.25 },
|
|
13
16
|
{ match: /^claude-fable-5/i, inputPerM: 10, outputPerM: 50 },
|
|
14
17
|
{ match: /^claude-mythos-5/i, inputPerM: 10, outputPerM: 50 },
|
|
15
18
|
{ match: /^claude-opus-5/i, inputPerM: 5, outputPerM: 25 },
|