@agent-finops/core 0.7.1 → 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.
@@ -1,10 +1,11 @@
1
1
  import { z } from "zod";
2
2
  import { type LocalAgentCall, type LocalAgentSourceScan } from "./localAgentLogs.js";
3
+ import type { LocalAgentFormatId } from "./localAgentFormats/types.js";
3
4
  import type { DetectedPlan } from "./planDetection.js";
4
5
  import { type UsageRecord } from "./schema.js";
5
6
  import { type SourceValidationCoverage } from "./sourceStatus.js";
6
- export declare const activitySnapshotAgentValues: readonly ["claude-code", "codex"];
7
- export type ActivitySnapshotAgent = typeof activitySnapshotAgentValues[number];
7
+ export declare const activitySnapshotAgentValues: readonly LocalAgentFormatId[];
8
+ export type ActivitySnapshotAgent = LocalAgentFormatId;
8
9
  export declare const activitySnapshotPlanIdValues: readonly ["claude-pro", "claude-max-5x", "claude-max-20x", "chatgpt-plus", "chatgpt-pro"];
9
10
  export type ActivitySnapshotPlanId = typeof activitySnapshotPlanIdValues[number];
10
11
  export declare const activitySnapshotProviderValues: readonly ["openai", "anthropic", "cursor", "github-copilot", "other"];
@@ -177,10 +178,7 @@ export declare const activitySnapshotLimitSchema: z.ZodObject<{
177
178
  }, z.core.$strict>;
178
179
  export type ActivitySnapshotLimit = z.infer<typeof activitySnapshotLimitSchema>;
179
180
  export declare const activitySnapshotSubscriptionAgentSchema: z.ZodObject<{
180
- agent: z.ZodEnum<{
181
- "claude-code": "claude-code";
182
- codex: "codex";
183
- }>;
181
+ agent: z.ZodString & z.ZodType<LocalAgentFormatId, string, z.core.$ZodTypeInternals<LocalAgentFormatId, string>>;
184
182
  billing: z.ZodLiteral<"subscription">;
185
183
  planId: z.ZodNullable<z.ZodEnum<{
186
184
  "claude-max-20x": "claude-max-20x";
@@ -260,10 +258,7 @@ export declare const activitySnapshotOverageSchema: z.ZodObject<{
260
258
  export type ActivitySnapshotOverage = z.infer<typeof activitySnapshotOverageSchema>;
261
259
  export declare const activitySnapshotCoverageSchema: z.ZodObject<{
262
260
  agents: z.ZodArray<z.ZodObject<{
263
- agent: z.ZodEnum<{
264
- "claude-code": "claude-code";
265
- codex: "codex";
266
- }>;
261
+ agent: z.ZodString & z.ZodType<LocalAgentFormatId, string, z.core.$ZodTypeInternals<LocalAgentFormatId, string>>;
267
262
  directoryStatus: z.ZodEnum<{
268
263
  missing: "missing";
269
264
  readable: "readable";
@@ -314,8 +309,8 @@ export declare const activitySnapshotCoverageSchema: z.ZodObject<{
314
309
  recordsPriced: z.ZodNumber;
315
310
  recordsUnpriced: z.ZodNumber;
316
311
  validationStatus: z.ZodEnum<{
317
- complete: "complete";
318
312
  failed: "failed";
313
+ complete: "complete";
319
314
  not_checked: "not_checked";
320
315
  partial: "partial";
321
316
  }>;
@@ -354,10 +349,7 @@ export declare const activitySnapshotSchema: z.ZodObject<{
354
349
  }>;
355
350
  subscription: z.ZodNullable<z.ZodObject<{
356
351
  agents: z.ZodArray<z.ZodObject<{
357
- agent: z.ZodEnum<{
358
- "claude-code": "claude-code";
359
- codex: "codex";
360
- }>;
352
+ agent: z.ZodString & z.ZodType<LocalAgentFormatId, string, z.core.$ZodTypeInternals<LocalAgentFormatId, string>>;
361
353
  billing: z.ZodLiteral<"subscription">;
362
354
  planId: z.ZodNullable<z.ZodEnum<{
363
355
  "claude-max-20x": "claude-max-20x";
@@ -428,10 +420,7 @@ export declare const activitySnapshotSchema: z.ZodObject<{
428
420
  }, z.core.$strict>>;
429
421
  metered: z.ZodNullable<z.ZodObject<{
430
422
  agents: z.ZodArray<z.ZodObject<{
431
- agent: z.ZodEnum<{
432
- "claude-code": "claude-code";
433
- codex: "codex";
434
- }>;
423
+ agent: z.ZodString & z.ZodType<LocalAgentFormatId, string, z.core.$ZodTypeInternals<LocalAgentFormatId, string>>;
435
424
  billing: z.ZodLiteral<"api_key">;
436
425
  planId: z.ZodNullable<z.ZodEnum<{
437
426
  "claude-max-20x": "claude-max-20x";
@@ -532,10 +521,7 @@ export declare const activitySnapshotSchema: z.ZodObject<{
532
521
  }, z.core.$strict>>;
533
522
  unresolved: z.ZodNullable<z.ZodObject<{
534
523
  agents: z.ZodArray<z.ZodObject<{
535
- agent: z.ZodEnum<{
536
- "claude-code": "claude-code";
537
- codex: "codex";
538
- }>;
524
+ agent: z.ZodString & z.ZodType<LocalAgentFormatId, string, z.core.$ZodTypeInternals<LocalAgentFormatId, string>>;
539
525
  billing: z.ZodLiteral<"unknown">;
540
526
  planId: z.ZodNullable<z.ZodEnum<{
541
527
  "claude-max-20x": "claude-max-20x";
@@ -600,10 +586,7 @@ export declare const activitySnapshotSchema: z.ZodObject<{
600
586
  }, z.core.$strict>>;
601
587
  coverage: z.ZodObject<{
602
588
  agents: z.ZodArray<z.ZodObject<{
603
- agent: z.ZodEnum<{
604
- "claude-code": "claude-code";
605
- codex: "codex";
606
- }>;
589
+ agent: z.ZodString & z.ZodType<LocalAgentFormatId, string, z.core.$ZodTypeInternals<LocalAgentFormatId, string>>;
607
590
  directoryStatus: z.ZodEnum<{
608
591
  missing: "missing";
609
592
  readable: "readable";
@@ -654,8 +637,8 @@ export declare const activitySnapshotSchema: z.ZodObject<{
654
637
  recordsPriced: z.ZodNumber;
655
638
  recordsUnpriced: z.ZodNumber;
656
639
  validationStatus: z.ZodEnum<{
657
- complete: "complete";
658
640
  failed: "failed";
641
+ complete: "complete";
659
642
  not_checked: "not_checked";
660
643
  partial: "partial";
661
644
  }>;
@@ -1,10 +1,13 @@
1
1
  import { z } from "zod";
2
2
  import { aggregateCalls, dedupeCumulativeSessionCalls } from "./localAgentLogs.js";
3
+ import { localAgentFormatDescriptors } from "./localAgentFormats/registry.js";
3
4
  import { estimateTokenCostUsd, PRICING_TABLE_AS_OF } from "./modelPricing.js";
4
5
  import { isBundledSampleUsage } from "./schema.js";
5
6
  import { sourceValidationCoverageValues } from "./sourceStatus.js";
6
7
  const DAY_MS = 24 * 60 * 60 * 1_000;
7
- export const activitySnapshotAgentValues = ["claude-code", "codex"];
8
+ export const activitySnapshotAgentValues = Object.freeze(localAgentFormatDescriptors.map((descriptor) => descriptor.id));
9
+ const activitySnapshotAgentValueSet = new Set(activitySnapshotAgentValues);
10
+ const activitySnapshotAgentLimit = activitySnapshotAgentValues.length;
8
11
  export const activitySnapshotPlanIdValues = [
9
12
  "claude-pro",
10
13
  "claude-max-5x",
@@ -45,7 +48,9 @@ const isoTimestampSchema = z.string().datetime({ offset: true });
45
48
  const usdSchema = z.number().finite().nonnegative();
46
49
  const countSchema = z.number().int().nonnegative();
47
50
  const windowCoverageSchema = z.enum(["complete", "partial", "missing"]);
48
- const agentSchema = z.enum(activitySnapshotAgentValues);
51
+ const agentSchema = z.string().refine(isSnapshotAgent, {
52
+ message: "Agent must be registered as a local-agent format."
53
+ });
49
54
  const planIdSchema = z.enum(activitySnapshotPlanIdValues).nullable();
50
55
  export const activitySnapshotApiEquivalentWindowSchema = z.object({
51
56
  amountUsd: usdSchema.nullable(),
@@ -169,14 +174,14 @@ export const activitySnapshotSubscriptionAgentSchema = z.object({
169
174
  }
170
175
  });
171
176
  const activitySnapshotSubscriptionSchema = z.object({
172
- agents: z.array(activitySnapshotSubscriptionAgentSchema).min(1).max(2)
177
+ agents: z.array(activitySnapshotSubscriptionAgentSchema).min(1).max(activitySnapshotAgentLimit)
173
178
  }).strict().superRefine(uniqueAgentEntries);
174
179
  const activitySnapshotMeteredSchema = z.object({
175
180
  agents: z.array(z.object({
176
181
  agent: agentSchema,
177
182
  billing: z.literal("api_key"),
178
183
  planId: planIdSchema
179
- }).strict()).max(2),
184
+ }).strict()).max(activitySnapshotAgentLimit),
180
185
  apiEquivalent: activitySnapshotApiEquivalentWindowsSchema,
181
186
  providerBilled: activitySnapshotBilledWindowsSchema
182
187
  }).strict().superRefine((value, context) => {
@@ -196,7 +201,7 @@ const activitySnapshotUnresolvedSchema = z.object({
196
201
  agent: agentSchema,
197
202
  billing: z.literal("unknown"),
198
203
  planId: planIdSchema
199
- }).strict()).max(2),
204
+ }).strict()).max(activitySnapshotAgentLimit),
200
205
  apiEquivalent: activitySnapshotApiEquivalentWindowsSchema
201
206
  }).strict().superRefine((value, context) => {
202
207
  uniqueAgentEntries(value, context);
@@ -290,7 +295,7 @@ const activitySnapshotProviderCoverageSchema = z.object({
290
295
  }
291
296
  });
292
297
  export const activitySnapshotCoverageSchema = z.object({
293
- agents: z.array(activitySnapshotAgentCoverageSchema).max(2),
298
+ agents: z.array(activitySnapshotAgentCoverageSchema).max(activitySnapshotAgentLimit),
294
299
  providers: z.array(activitySnapshotProviderCoverageSchema).max(5),
295
300
  recordsParsed: countSchema,
296
301
  recordsPriced: countSchema,
@@ -483,8 +488,10 @@ export function buildActivitySnapshot(input) {
483
488
  if (isSnapshotAgent(entry.record.agentId))
484
489
  activeAgents.add(entry.record.agentId);
485
490
  }
486
- for (const call of calls)
487
- activeAgents.add(call.agent);
491
+ for (const call of calls) {
492
+ if (isSnapshotAgent(call.agent))
493
+ activeAgents.add(call.agent);
494
+ }
488
495
  const meteredAgents = [...activeAgents]
489
496
  .filter((agent) => plans.get(agent)?.billing === "api_key")
490
497
  .sort()
@@ -529,7 +536,7 @@ export function buildActivitySnapshot(input) {
529
536
  const subscription = hasSubscriptionActivity ? { agents: subscriptionAgents } : null;
530
537
  const metered = hasMeteredActivity ? {
531
538
  agents: meteredAgents,
532
- apiEquivalent: buildApiWindows(meteredApiRecords, allCalls.filter((call) => plans.get(call.agent)?.billing === "api_key"), trustedIds, asOfMs, localCoverageForRecords(meteredApiRecords, scans, [...plans.entries()]
539
+ apiEquivalent: buildApiWindows(meteredApiRecords, allCalls.filter((call) => (isSnapshotAgent(call.agent) && plans.get(call.agent)?.billing === "api_key")), trustedIds, asOfMs, localCoverageForRecords(meteredApiRecords, scans, [...plans.entries()]
533
540
  .filter(([, plan]) => plan.billing === "api_key")
534
541
  .map(([agent]) => agent))),
535
542
  providerBilled: buildBilledWindows(meteredBilledRecords, asOfMs, providers)
@@ -537,6 +544,8 @@ export function buildActivitySnapshot(input) {
537
544
  const unresolved = hasUnresolvedActivity ? {
538
545
  agents: unresolvedAgents,
539
546
  apiEquivalent: buildApiWindows(unresolvedRecords, allCalls.filter((call) => {
547
+ if (!isSnapshotAgent(call.agent))
548
+ return false;
540
549
  const billing = plans.get(call.agent)?.billing;
541
550
  return billing !== "subscription" && billing !== "api_key";
542
551
  }), trustedIds, asOfMs, unresolvedCoverageForRecords(unresolvedRecords, scans, providers))
@@ -1200,7 +1209,7 @@ function inRollingWindow(timestamp, asOfMs, days) {
1200
1209
  return Number.isFinite(value) && value >= asOfMs - days * DAY_MS && value <= asOfMs;
1201
1210
  }
1202
1211
  function isSnapshotAgent(value) {
1203
- return typeof value === "string" && activitySnapshotAgentValues.includes(value);
1212
+ return typeof value === "string" && activitySnapshotAgentValueSet.has(value);
1204
1213
  }
1205
1214
  function isKnownPlanId(value) {
1206
1215
  return typeof value === "string" && activitySnapshotPlanIdValues.includes(value);
@@ -1,5 +1,6 @@
1
1
  import { loadAgentInventory } from "./agentInventory.js";
2
2
  import { computeDeadContext } from "./deadContext.js";
3
+ import { localAgentFormatSupports } from "./localAgentFormats/registry.js";
3
4
  import { loadToolInvocations } from "./toolInvocations.js";
4
5
  const DAY_MS = 24 * 60 * 60 * 1_000;
5
6
  const DEFAULT_WINDOW_DAYS = 30;
@@ -30,7 +31,8 @@ export async function loadContextHealth(calls, options = {}) {
30
31
  /** Pure Context Health contract builder for deterministic tests and adapters. */
31
32
  export function buildContextHealth(input = {}) {
32
33
  const now = input.now ?? new Date();
33
- const calls = input.calls ?? [];
34
+ const calls = (input.calls ?? [])
35
+ .filter((call) => localAgentFormatSupports(call.agent, "contextHealth"));
34
36
  const items = input.inventory?.items ?? [];
35
37
  const invocations = input.invocations ?? emptyInvocations();
36
38
  const windowDays = input.windowDays ?? input.deadContext?.windowDays ?? DEFAULT_WINDOW_DAYS;
package/dist/glance.js CHANGED
@@ -2,6 +2,7 @@ import { dedupeCumulativeSessionCalls, sanitizeLocalActivityText } from "./local
2
2
  import { estimateTokenCostUsd, PRICING_TABLE_AS_OF } from "./modelPricing.js";
3
3
  import { subscriptionPlans } from "./planMath.js";
4
4
  import { buildContextHealth } from "./contextHealth.js";
5
+ import { localAgentFormatDescriptors, localAgentFormatSupports } from "./localAgentFormats/registry.js";
5
6
  const HOUR_MS = 60 * 60 * 1_000;
6
7
  const DAY_MS = 24 * HOUR_MS;
7
8
  /**
@@ -16,7 +17,9 @@ export function buildUsageGlance(calls, options = {}) {
16
17
  // defense-in-depth to every string-bearing transcript/context field before
17
18
  // any calculation so secrets cannot survive in a nested session-health or
18
19
  // provenance field even if an upstream parser missed them.
19
- const safeCalls = dedupeCumulativeSessionCalls(sanitizeStringMetadata(calls));
20
+ const supportedFormats = localAgentFormatDescriptors.filter((descriptor) => (descriptor.capabilities.glance));
21
+ const safeCalls = dedupeCumulativeSessionCalls(sanitizeStringMetadata(calls))
22
+ .filter((call) => localAgentFormatSupports(call.agent, "glance"));
20
23
  const suppliedContextHealth = options.contextHealth
21
24
  ? sanitizeStringMetadata(options.contextHealth)
22
25
  : undefined;
@@ -40,7 +43,8 @@ export function buildUsageGlance(calls, options = {}) {
40
43
  const plan = currentSession
41
44
  ? toGlancePlan(currentSession.agent, safeDetectedPlans)
42
45
  : null;
43
- const limitCalls = sanitizeStringMetadata(options.limitCalls ?? safeCalls);
46
+ const limitCalls = sanitizeStringMetadata(options.limitCalls ?? safeCalls)
47
+ .filter((call) => localAgentFormatSupports(call.agent, "rateLimits"));
44
48
  const limits = latestLimits(limitCalls, now).map(({ agent, window, observedAt }) => toGlanceLimit(agent, window, observedAt));
45
49
  const windowStart = now.getTime() - focusWindowDays * DAY_MS;
46
50
  const windowCalls = safeCalls.filter((call) => Date.parse(call.timestamp) >= windowStart);
@@ -62,7 +66,8 @@ export function buildUsageGlance(calls, options = {}) {
62
66
  generatedAt: now.toISOString(),
63
67
  filesParsed: options.filesParsed ?? 0
64
68
  });
65
- const detectedAgents = options.detectedAgents ?? uniqueAgents(safeCalls);
69
+ const detectedAgents = (options.detectedAgents ?? uniqueAgents(safeCalls))
70
+ .filter((agent) => localAgentFormatSupports(agent, "glance"));
66
71
  const agentsWithLimits = new Set(limits.map((limit) => limit.agent));
67
72
  const limitAgents = uniqueAgents(limitCalls.filter((call) => call.rateLimits));
68
73
  const reportedWindows = (agent) => ([...new Set(limits
@@ -82,20 +87,15 @@ export function buildUsageGlance(calls, options = {}) {
82
87
  generatedAt: now.toISOString(),
83
88
  coverage: {
84
89
  filesParsed: options.filesParsed ?? 0,
85
- supportedTranscriptAgents: ["claude-code", "codex"],
90
+ supportedTranscriptAgents: supportedFormats.map((descriptor) => descriptor.id),
86
91
  detectedAgents,
87
- rateLimitMetadata: [
88
- {
89
- agent: "claude-code",
90
- status: "not_reported_by_transcript",
91
- windowsReported: reportedWindows("claude-code")
92
- },
93
- {
94
- agent: "codex",
95
- status: agentsWithLimits.has("codex") ? "reported" : "not_seen",
96
- windowsReported: reportedWindows("codex")
97
- }
98
- ],
92
+ rateLimitMetadata: supportedFormats.map((descriptor) => ({
93
+ agent: descriptor.id,
94
+ status: descriptor.capabilities.rateLimits
95
+ ? agentsWithLimits.has(descriptor.id) ? "reported" : "not_seen"
96
+ : "not_reported_by_transcript",
97
+ windowsReported: reportedWindows(descriptor.id)
98
+ })),
99
99
  providerConnectionRequired: ["cursor", "github-copilot"]
100
100
  },
101
101
  provenance: {
package/dist/index.d.ts CHANGED
@@ -12,7 +12,10 @@ export * from "./discovery.js";
12
12
  export * from "./glance.js";
13
13
  export * from "./toolInvocations.js";
14
14
  export * from "./insights.js";
15
- export * from "./localAgentLogs.js";
15
+ export { aggregateCalls, dedupeCumulativeSessionCalls, latestObservedWorkingDirectory, loadLocalAgentFinancialUsage, loadLocalAgentUsage, parseClaudeCodeTranscript, parseCodexRollout, sanitizeLocalActivityText } from "./localAgentLogs.js";
16
+ export type { LocalAgentActivity, LocalAgentCall, LocalAgentFinancialLogOptions, LocalAgentLogDiagnostic, LocalAgentLogDiagnosticCode, LocalAgentLogOptions, LocalAgentLogResult, LocalAgentRateLimitSnapshot, LocalAgentRateLimitWindow, LocalAgentSourceScan, LocalAgentTurnUsage } from "./localAgentLogs.js";
17
+ export * from "./localAgentFormats/registry.js";
18
+ export type * from "./localAgentFormats/types.js";
16
19
  export * from "./modelPricing.js";
17
20
  export * from "./planDetection.js";
18
21
  export * from "./planMath.js";
package/dist/index.js CHANGED
@@ -12,7 +12,8 @@ export * from "./discovery.js";
12
12
  export * from "./glance.js";
13
13
  export * from "./toolInvocations.js";
14
14
  export * from "./insights.js";
15
- export * from "./localAgentLogs.js";
15
+ export { aggregateCalls, dedupeCumulativeSessionCalls, latestObservedWorkingDirectory, loadLocalAgentFinancialUsage, loadLocalAgentUsage, parseClaudeCodeTranscript, parseCodexRollout, sanitizeLocalActivityText } from "./localAgentLogs.js";
16
+ export * from "./localAgentFormats/registry.js";
16
17
  export * from "./modelPricing.js";
17
18
  export * from "./planDetection.js";
18
19
  export * from "./planMath.js";
@@ -0,0 +1,8 @@
1
+ import type { LocalAgentFormatDescriptor, LocalAgentFormatId } from "./types.js";
2
+ export declare const localAgentFormatDescriptors: readonly LocalAgentFormatDescriptor[];
3
+ export declare function localAgentFormatDescriptor(id: LocalAgentFormatId): LocalAgentFormatDescriptor | undefined;
4
+ export declare function localAgentFormatLabel(id: LocalAgentFormatId): string;
5
+ export declare function localAgentFormatSupports(id: LocalAgentFormatId, capability: keyof LocalAgentFormatDescriptor["capabilities"]): boolean;
6
+ export declare function matchesLocalAgentFormatFile(descriptor: LocalAgentFormatDescriptor, filePath: string): boolean;
7
+ export declare function validateLocalAgentFormatDescriptors(registry?: readonly LocalAgentFormatDescriptor[]): void;
8
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1,219 @@
1
+ import { basename } from "node:path";
2
+ const descriptors = [
3
+ {
4
+ schemaVersion: 1,
5
+ id: "claude-code",
6
+ order: 10,
7
+ label: "Claude Code",
8
+ provider: "anthropic",
9
+ defaultHomeRelative: [".claude", "projects"],
10
+ legacyDirectoryOption: "claudeProjectsDir",
11
+ discovery: { extension: ".jsonl" },
12
+ confidenceDefaults: {
13
+ validationCoverage: "live_verified",
14
+ pricedFinancialEvidence: "estimated",
15
+ unpricedFinancialEvidence: "missing",
16
+ sourceConfidence: "estimated"
17
+ },
18
+ sourceRecord: {
19
+ id: "local-agent-logs",
20
+ name: "Local agent session logs",
21
+ observedFrom: "claude-code transcript JSONL (this machine)",
22
+ providerCostType: "local_agent_logs",
23
+ usageGranularity: "daily_aggregate",
24
+ operation: "claude-code sessions"
25
+ },
26
+ capabilities: {
27
+ activity: true,
28
+ contextHealth: true,
29
+ financialFastPath: true,
30
+ glance: true,
31
+ invocationEvidence: false,
32
+ planContext: true,
33
+ rateLimits: false
34
+ },
35
+ financialRead: "full_jsonl",
36
+ validationNote: "Local transcript parsing is exercised against live logs; dollar values remain API-rate estimates.",
37
+ docs: {
38
+ format: "JSON Lines under ~/.claude/projects/**/*.jsonl",
39
+ howRead: [
40
+ "Read one JSON object per line and keep assistant records with message.usage.",
41
+ "Ignore <synthetic> placeholder models and deduplicate streaming/retry rewrites by message and request identity.",
42
+ "Treat each retained assistant usage record as turn-scoped evidence."
43
+ ],
44
+ fieldsRead: [
45
+ "timestamp, model, and token-usage components",
46
+ "session and working-directory metadata for local deduplication and attribution",
47
+ "human-prompt and tool metadata for privacy-reduced local activity summaries"
48
+ ],
49
+ verified: [
50
+ "The reader and its failure paths have been exercised against live local Claude Code logs.",
51
+ "Synthetic recorded fixtures lock the supported JSONL shapes and normalized output."
52
+ ],
53
+ estimated: [
54
+ "Supported token components are priced at published API rates as API-equivalent value."
55
+ ],
56
+ notVerified: [
57
+ "API-equivalent value is not billed spend, subscription cost, savings, or ROI.",
58
+ "Claude Code transcripts do not provide account-plan headroom; missing limits are not inferred."
59
+ ],
60
+ privacy: [
61
+ "Parsing and aggregation run locally; raw prompts and responses are not returned by the parser registry.",
62
+ "aibill never sits in the inference path and never stores, prints, or proxies provider credentials."
63
+ ],
64
+ limitations: [
65
+ "Malformed lines are skipped and reported.",
66
+ "Incomplete token shapes remain unpriced with missing financial evidence instead of becoming $0."
67
+ ]
68
+ },
69
+ fixtures: ["claude-code-v1"]
70
+ },
71
+ {
72
+ schemaVersion: 1,
73
+ id: "codex",
74
+ order: 20,
75
+ label: "Codex",
76
+ provider: "openai",
77
+ defaultHomeRelative: [".codex", "sessions"],
78
+ legacyDirectoryOption: "codexSessionsDir",
79
+ discovery: { extension: ".jsonl", basenamePrefix: "rollout-" },
80
+ confidenceDefaults: {
81
+ validationCoverage: "live_verified",
82
+ pricedFinancialEvidence: "estimated",
83
+ unpricedFinancialEvidence: "missing",
84
+ sourceConfidence: "estimated"
85
+ },
86
+ sourceRecord: {
87
+ id: "local-agent-logs",
88
+ name: "Local agent session logs",
89
+ observedFrom: "codex transcript JSONL (this machine)",
90
+ providerCostType: "local_agent_logs",
91
+ usageGranularity: "daily_aggregate",
92
+ operation: "codex sessions"
93
+ },
94
+ capabilities: {
95
+ activity: true,
96
+ contextHealth: true,
97
+ financialFastPath: true,
98
+ glance: true,
99
+ invocationEvidence: true,
100
+ planContext: true,
101
+ rateLimits: true
102
+ },
103
+ financialRead: "bounded_event_jsonl",
104
+ validationNote: "Local parsing was replayed against live logs; total-only token shapes and unknown aliases remain missing rather than becoming estimated $0.",
105
+ docs: {
106
+ format: "JSON Lines under ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl",
107
+ howRead: [
108
+ "Read the Codex event stream and retain the root session identity, model, and latest cumulative event_msg/token_count evidence.",
109
+ "Never sum earlier running token counters; forked sessions subtract a supported inherited cumulative baseline.",
110
+ "Use a bounded proof-based financial reader for init/cache while the full reader retains privacy-reduced activity and optional invocation evidence."
111
+ ],
112
+ fieldsRead: [
113
+ "session metadata, timestamps, model, and cumulative/last-turn token usage",
114
+ "transcript-reported rate-limit windows when present",
115
+ "tool-call metadata for local attribution and optional privacy-safe invocation counts"
116
+ ],
117
+ verified: [
118
+ "The full and optimized financial readers have been replayed against live local Codex logs.",
119
+ "Synthetic recorded fixtures lock cumulative, last-turn, and rate-limit normalization."
120
+ ],
121
+ estimated: [
122
+ "Supported token components are priced at published API rates as API-equivalent value."
123
+ ],
124
+ notVerified: [
125
+ "API-equivalent value is not billed spend, subscription cost, savings, or ROI.",
126
+ "A transcript-reported limit is plan-capacity evidence, not a provider invoice."
127
+ ],
128
+ privacy: [
129
+ "Parsing and aggregation run locally; raw prompts and responses are not returned by the parser registry.",
130
+ "aibill never sits in the inference path and never stores, prints, or proxies provider credentials."
131
+ ],
132
+ limitations: [
133
+ "Only rollout-*.jsonl files are parsed as Codex sessions.",
134
+ "Incomplete, regressing, or total-only token shapes remain unpriced with missing financial evidence."
135
+ ]
136
+ },
137
+ fixtures: ["codex-v1"]
138
+ }
139
+ ];
140
+ export const localAgentFormatDescriptors = Object.freeze([...descriptors]
141
+ .sort((left, right) => left.order - right.order)
142
+ .map(freezeDescriptor));
143
+ export function localAgentFormatDescriptor(id) {
144
+ return localAgentFormatDescriptors.find((descriptor) => descriptor.id === id);
145
+ }
146
+ export function localAgentFormatLabel(id) {
147
+ return localAgentFormatDescriptor(id)?.label ?? id;
148
+ }
149
+ export function localAgentFormatSupports(id, capability) {
150
+ return localAgentFormatDescriptor(id)?.capabilities[capability] === true;
151
+ }
152
+ export function matchesLocalAgentFormatFile(descriptor, filePath) {
153
+ const name = basename(filePath);
154
+ if (descriptor.discovery.extension && !name.endsWith(descriptor.discovery.extension))
155
+ return false;
156
+ if (descriptor.discovery.basename && name !== descriptor.discovery.basename)
157
+ return false;
158
+ if (descriptor.discovery.basenamePrefix && !name.startsWith(descriptor.discovery.basenamePrefix))
159
+ return false;
160
+ return true;
161
+ }
162
+ export function validateLocalAgentFormatDescriptors(registry = localAgentFormatDescriptors) {
163
+ const ids = new Set();
164
+ const orders = new Set();
165
+ for (const descriptor of registry) {
166
+ if (descriptor.schemaVersion !== 1)
167
+ throw new Error(`Unsupported local-agent format schema for ${descriptor.id}.`);
168
+ if (!descriptor.id || !descriptor.label || !descriptor.provider)
169
+ throw new Error("Local-agent format identity is incomplete.");
170
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(descriptor.id) ||
171
+ !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(descriptor.provider)) {
172
+ throw new Error(`Unsafe local-agent format identity for ${descriptor.id}.`);
173
+ }
174
+ if (ids.has(descriptor.id))
175
+ throw new Error(`Duplicate local-agent format id: ${descriptor.id}.`);
176
+ if (orders.has(descriptor.order))
177
+ throw new Error(`Duplicate local-agent format order: ${descriptor.order}.`);
178
+ if (descriptor.defaultHomeRelative.length === 0 || descriptor.defaultHomeRelative.some((part) => (!part || part === "." || part === ".." || /[\\/\u0000]/.test(part)))) {
179
+ throw new Error(`Unsafe default local-agent root for ${descriptor.id}.`);
180
+ }
181
+ const discovery = descriptor.discovery;
182
+ if (!discovery.extension && !discovery.basename && !discovery.basenamePrefix) {
183
+ throw new Error(`Local-agent format ${descriptor.id} must declare a bounded file rule.`);
184
+ }
185
+ if (discovery.extension && !/^\.[A-Za-z0-9]+$/.test(discovery.extension)) {
186
+ throw new Error(`Unsafe discovery extension for ${descriptor.id}.`);
187
+ }
188
+ for (const value of [discovery.basename, discovery.basenamePrefix]) {
189
+ if (value && (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(value) || /[\\/\u0000]/.test(value))) {
190
+ throw new Error(`Unsafe discovery filename rule for ${descriptor.id}.`);
191
+ }
192
+ }
193
+ if (descriptor.confidenceDefaults.pricedFinancialEvidence !== "estimated" ||
194
+ descriptor.confidenceDefaults.unpricedFinancialEvidence !== "missing" ||
195
+ descriptor.confidenceDefaults.sourceConfidence !== "estimated") {
196
+ throw new Error(`Local transcript format ${descriptor.id} must default to estimated/missing financial evidence.`);
197
+ }
198
+ if (descriptor.fixtures.length === 0 || descriptor.fixtures.some((fixture) => (!/^[a-z0-9]+(?:-[a-z0-9]+)*-v[1-9][0-9]*$/.test(fixture)))) {
199
+ throw new Error(`Local-agent format ${descriptor.id} has an unsafe or missing recorded fixture.`);
200
+ }
201
+ ids.add(descriptor.id);
202
+ orders.add(descriptor.order);
203
+ }
204
+ }
205
+ function freezeDescriptor(descriptor) {
206
+ Object.freeze(descriptor.defaultHomeRelative);
207
+ Object.freeze(descriptor.discovery);
208
+ Object.freeze(descriptor.confidenceDefaults);
209
+ Object.freeze(descriptor.sourceRecord);
210
+ Object.freeze(descriptor.capabilities);
211
+ for (const values of Object.values(descriptor.docs)) {
212
+ if (Array.isArray(values))
213
+ Object.freeze(values);
214
+ }
215
+ Object.freeze(descriptor.docs);
216
+ Object.freeze(descriptor.fixtures);
217
+ return Object.freeze(descriptor);
218
+ }
219
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1,4 @@
1
+ import type { LocalAgentFormatRuntime } from "./types.js";
2
+ export declare const localAgentFormatRuntimeRegistry: readonly LocalAgentFormatRuntime[];
3
+ export declare function validateLocalAgentFormatRuntimeRegistry(registry?: readonly LocalAgentFormatRuntime[], descriptors?: readonly import("./types.js").LocalAgentFormatDescriptor[]): void;
4
+ //# sourceMappingURL=runtimeRegistry.d.ts.map
@@ -0,0 +1,55 @@
1
+ import { parseClaudeCodeTranscript, parseCodexRollout, readClaudeCodeFinancialFileForRegistry, readCodexFinancialFileForRegistry } from "../localAgentLogs.js";
2
+ import { createCodexInvocationCollector } from "../toolInvocations.js";
3
+ import { localAgentFormatDescriptors, validateLocalAgentFormatDescriptors } from "./registry.js";
4
+ const byId = new Map(localAgentFormatDescriptors.map((descriptor) => [descriptor.id, descriptor]));
5
+ const claudeCode = byId.get("claude-code");
6
+ const codex = byId.get("codex");
7
+ if (!claudeCode || !codex) {
8
+ throw new Error("Built-in local-agent format descriptors are incomplete.");
9
+ }
10
+ validateLocalAgentFormatDescriptors();
11
+ const runtimes = [
12
+ {
13
+ descriptor: claudeCode,
14
+ parseFull: ({ content, filePath, sinceMs, onDiagnostic }) => ({
15
+ calls: parseClaudeCodeTranscript(content, filePath, sinceMs, onDiagnostic)
16
+ }),
17
+ parseFinancialFile: readClaudeCodeFinancialFileForRegistry
18
+ },
19
+ {
20
+ descriptor: codex,
21
+ parseFull: ({ content, sinceMs, collectInvocationEvidence, onDiagnostic }) => {
22
+ const collector = collectInvocationEvidence
23
+ ? createCodexInvocationCollector(sinceMs)
24
+ : undefined;
25
+ return {
26
+ calls: parseCodexRollout(content, collector?.consume, onDiagnostic),
27
+ ...(collector ? { invocationFile: collector.finish() } : {})
28
+ };
29
+ },
30
+ parseFinancialFile: readCodexFinancialFileForRegistry
31
+ }
32
+ ];
33
+ export const localAgentFormatRuntimeRegistry = Object.freeze(runtimes.map((runtime) => Object.freeze(runtime)));
34
+ export function validateLocalAgentFormatRuntimeRegistry(registry = localAgentFormatRuntimeRegistry, descriptors = localAgentFormatDescriptors) {
35
+ validateLocalAgentFormatDescriptors(registry.map((entry) => entry.descriptor));
36
+ const expected = [...registry].sort((left, right) => (left.descriptor.order - right.descriptor.order));
37
+ if (expected.some((entry, index) => entry !== registry[index])) {
38
+ throw new Error("Local-agent runtime registry must be ordered by descriptor order.");
39
+ }
40
+ for (const entry of registry) {
41
+ if (typeof entry.parseFull !== "function" || typeof entry.parseFinancialFile !== "function") {
42
+ throw new Error(`Local-agent format ${entry.descriptor.id} is missing a runtime parser.`);
43
+ }
44
+ }
45
+ const runtimeIds = registry.map((entry) => entry.descriptor.id);
46
+ const descriptorIds = descriptors.map((descriptor) => descriptor.id);
47
+ if (JSON.stringify(runtimeIds) !== JSON.stringify(descriptorIds)) {
48
+ throw new Error(`Local-agent runtime registry must exactly match descriptor order: ${descriptorIds.join(", ")}.`);
49
+ }
50
+ if (registry.some((entry, index) => entry.descriptor !== descriptors[index])) {
51
+ throw new Error("Local-agent runtime entries must use the canonical descriptor objects.");
52
+ }
53
+ }
54
+ validateLocalAgentFormatRuntimeRegistry();
55
+ //# sourceMappingURL=runtimeRegistry.js.map