@agent-finops/core 0.6.0 → 0.7.0

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.
@@ -0,0 +1,1220 @@
1
+ import { z } from "zod";
2
+ import { aggregateCalls, dedupeCumulativeSessionCalls } from "./localAgentLogs.js";
3
+ import { estimateTokenCostUsd, PRICING_TABLE_AS_OF } from "./modelPricing.js";
4
+ import { isBundledSampleUsage } from "./schema.js";
5
+ import { sourceValidationCoverageValues } from "./sourceStatus.js";
6
+ const DAY_MS = 24 * 60 * 60 * 1_000;
7
+ export const activitySnapshotAgentValues = ["claude-code", "codex"];
8
+ export const activitySnapshotPlanIdValues = [
9
+ "claude-pro",
10
+ "claude-max-5x",
11
+ "claude-max-20x",
12
+ "chatgpt-plus",
13
+ "chatgpt-pro"
14
+ ];
15
+ export const activitySnapshotProviderValues = [
16
+ "openai",
17
+ "anthropic",
18
+ "cursor",
19
+ "github-copilot",
20
+ "other"
21
+ ];
22
+ export const activitySnapshotModeValues = [
23
+ "metered",
24
+ "subscription",
25
+ "mixed",
26
+ "unresolved",
27
+ "empty",
28
+ "error"
29
+ ];
30
+ export const activitySnapshotRefreshErrorCodeValues = [
31
+ "scan_failed",
32
+ "source_unreadable",
33
+ "invalid_evidence",
34
+ "timeout",
35
+ "cache_write_failed",
36
+ "unknown"
37
+ ];
38
+ export const activitySnapshotProviderCoverageStatusValues = [
39
+ "complete",
40
+ "partial",
41
+ "unavailable",
42
+ "error"
43
+ ];
44
+ const isoTimestampSchema = z.string().datetime({ offset: true });
45
+ const usdSchema = z.number().finite().nonnegative();
46
+ const countSchema = z.number().int().nonnegative();
47
+ const windowCoverageSchema = z.enum(["complete", "partial", "missing"]);
48
+ const agentSchema = z.enum(activitySnapshotAgentValues);
49
+ const planIdSchema = z.enum(activitySnapshotPlanIdValues).nullable();
50
+ export const activitySnapshotApiEquivalentWindowSchema = z.object({
51
+ amountUsd: usdSchema.nullable(),
52
+ recordCount: countSchema,
53
+ basis: z.literal("api_equivalent"),
54
+ financialEvidence: z.enum(["estimated", "missing"]),
55
+ coverage: windowCoverageSchema
56
+ }).strict().superRefine((window, context) => {
57
+ if ((window.amountUsd === null) !== (window.financialEvidence === "missing")) {
58
+ context.addIssue({
59
+ code: "custom",
60
+ path: ["financialEvidence"],
61
+ message: "API-equivalent amount and financial evidence must agree."
62
+ });
63
+ }
64
+ if (window.coverage === "missing" && window.amountUsd !== null) {
65
+ context.addIssue({
66
+ code: "custom",
67
+ path: ["amountUsd"],
68
+ message: "Missing API-equivalent coverage cannot carry an amount."
69
+ });
70
+ }
71
+ if (window.recordCount === 0 && window.amountUsd !== null &&
72
+ (window.amountUsd !== 0 || window.coverage !== "complete")) {
73
+ context.addIssue({
74
+ code: "custom",
75
+ path: ["amountUsd"],
76
+ message: "An empty API-equivalent window may carry only a coverage-proved zero."
77
+ });
78
+ }
79
+ });
80
+ export const activitySnapshotBilledWindowSchema = z.object({
81
+ amountUsd: usdSchema.nullable(),
82
+ recordCount: countSchema,
83
+ basis: z.literal("provider_billed"),
84
+ financialEvidence: z.enum(["verified", "missing"]),
85
+ coverage: windowCoverageSchema
86
+ }).strict().superRefine((window, context) => {
87
+ if ((window.amountUsd === null) !== (window.financialEvidence === "missing")) {
88
+ context.addIssue({
89
+ code: "custom",
90
+ path: ["financialEvidence"],
91
+ message: "Provider-billed amount and financial evidence must agree."
92
+ });
93
+ }
94
+ if (window.coverage === "missing" && window.amountUsd !== null) {
95
+ context.addIssue({
96
+ code: "custom",
97
+ path: ["amountUsd"],
98
+ message: "Missing provider-billed coverage cannot carry an amount."
99
+ });
100
+ }
101
+ if (window.recordCount === 0 && window.amountUsd !== null &&
102
+ (window.amountUsd !== 0 || window.coverage !== "complete")) {
103
+ context.addIssue({
104
+ code: "custom",
105
+ path: ["amountUsd"],
106
+ message: "An empty provider-billed window may carry only a receipt-proved zero."
107
+ });
108
+ }
109
+ });
110
+ function rollingWindowsSchema(windowSchema) {
111
+ return z.object({
112
+ oneDay: windowSchema,
113
+ sevenDays: windowSchema,
114
+ thirtyDays: windowSchema
115
+ }).strict();
116
+ }
117
+ export const activitySnapshotApiEquivalentWindowsSchema = rollingWindowsSchema(activitySnapshotApiEquivalentWindowSchema);
118
+ export const activitySnapshotBilledWindowsSchema = rollingWindowsSchema(activitySnapshotBilledWindowSchema);
119
+ function rollingWindowsHaveEvidence(windows) {
120
+ return [windows.oneDay, windows.sevenDays, windows.thirtyDays]
121
+ .some((window) => window.recordCount > 0 || window.amountUsd !== null);
122
+ }
123
+ function uniqueAgentEntries(value, context) {
124
+ if (new Set(value.agents.map((agent) => agent.agent)).size !== value.agents.length) {
125
+ context.addIssue({
126
+ code: "custom",
127
+ path: ["agents"],
128
+ message: "A cohort may contain each agent only once."
129
+ });
130
+ }
131
+ }
132
+ export const activitySnapshotLimitSchema = z.object({
133
+ kind: z.enum(["five-hour", "weekly"]),
134
+ usedPercent: z.number().finite().min(0).max(100),
135
+ remainingPercent: z.number().finite().min(0).max(100),
136
+ observedAt: isoTimestampSchema,
137
+ resetsAt: isoTimestampSchema,
138
+ source: z.literal("transcript_reported")
139
+ }).strict().superRefine((limit, context) => {
140
+ if (Math.abs(limit.usedPercent + limit.remainingPercent - 100) > 0.11) {
141
+ context.addIssue({
142
+ code: "custom",
143
+ path: ["remainingPercent"],
144
+ message: "Reported used and remaining percentages must sum to 100."
145
+ });
146
+ }
147
+ if (Date.parse(limit.observedAt) >= Date.parse(limit.resetsAt)) {
148
+ context.addIssue({
149
+ code: "custom",
150
+ path: ["resetsAt"],
151
+ message: "A reported limit reset must follow its observation."
152
+ });
153
+ }
154
+ });
155
+ export const activitySnapshotSubscriptionAgentSchema = z.object({
156
+ agent: agentSchema,
157
+ billing: z.literal("subscription"),
158
+ planId: planIdSchema,
159
+ apiEquivalent: activitySnapshotApiEquivalentWindowsSchema,
160
+ limits: z.array(activitySnapshotLimitSchema).max(2),
161
+ pressure: z.enum(["extra_usage_credits_exhausted"]).nullable()
162
+ }).strict().superRefine((value, context) => {
163
+ if (new Set(value.limits.map((limit) => limit.kind)).size !== value.limits.length) {
164
+ context.addIssue({
165
+ code: "custom",
166
+ path: ["limits"],
167
+ message: "An agent may contain at most one limit of each kind."
168
+ });
169
+ }
170
+ });
171
+ const activitySnapshotSubscriptionSchema = z.object({
172
+ agents: z.array(activitySnapshotSubscriptionAgentSchema).min(1).max(2)
173
+ }).strict().superRefine(uniqueAgentEntries);
174
+ const activitySnapshotMeteredSchema = z.object({
175
+ agents: z.array(z.object({
176
+ agent: agentSchema,
177
+ billing: z.literal("api_key"),
178
+ planId: planIdSchema
179
+ }).strict()).max(2),
180
+ apiEquivalent: activitySnapshotApiEquivalentWindowsSchema,
181
+ providerBilled: activitySnapshotBilledWindowsSchema
182
+ }).strict().superRefine((value, context) => {
183
+ uniqueAgentEntries(value, context);
184
+ const hasFinancialEvidence = rollingWindowsHaveEvidence(value.apiEquivalent) ||
185
+ rollingWindowsHaveEvidence(value.providerBilled);
186
+ if (value.agents.length === 0 && !hasFinancialEvidence) {
187
+ context.addIssue({
188
+ code: "custom",
189
+ path: ["agents"],
190
+ message: "A metered cohort requires an agent or bounded financial evidence."
191
+ });
192
+ }
193
+ });
194
+ const activitySnapshotUnresolvedSchema = z.object({
195
+ agents: z.array(z.object({
196
+ agent: agentSchema,
197
+ billing: z.literal("unknown"),
198
+ planId: planIdSchema
199
+ }).strict()).max(2),
200
+ apiEquivalent: activitySnapshotApiEquivalentWindowsSchema
201
+ }).strict().superRefine((value, context) => {
202
+ uniqueAgentEntries(value, context);
203
+ if (value.agents.length === 0 && !rollingWindowsHaveEvidence(value.apiEquivalent)) {
204
+ context.addIssue({
205
+ code: "custom",
206
+ path: ["agents"],
207
+ message: "An unresolved cohort requires an agent or bounded financial evidence."
208
+ });
209
+ }
210
+ });
211
+ export const activitySnapshotOverageSchema = z.object({
212
+ amountUsd: z.number().finite().positive(),
213
+ currency: z.literal("USD"),
214
+ basis: z.literal("provider_billed"),
215
+ financialEvidence: z.literal("verified"),
216
+ alertEligible: z.literal(true),
217
+ recordCount: z.number().int().positive()
218
+ }).strict();
219
+ const activitySnapshotAgentCoverageSchema = z.object({
220
+ agent: agentSchema,
221
+ directoryStatus: z.enum(["readable", "missing", "unreadable"]),
222
+ filesDiscovered: countSchema,
223
+ filesParsed: countSchema,
224
+ malformedLines: countSchema,
225
+ unreadableFiles: countSchema,
226
+ unsupportedUsageSnapshots: countSchema,
227
+ filesSkippedBeforeWindow: countSchema,
228
+ filesReadFinancially: countSchema,
229
+ bytesSkippedAsNonFinancialHistory: countSchema,
230
+ nonFinancialLinesPrefiltered: countSchema,
231
+ nonFinancialBytesPrefiltered: countSchema,
232
+ jsonlValidationCoverage: z.enum(["complete", "financial_events_only", "not_reported"])
233
+ }).strict();
234
+ const activitySnapshotProviderCoverageSchema = z.object({
235
+ provider: z.enum(activitySnapshotProviderValues),
236
+ status: z.enum(activitySnapshotProviderCoverageStatusValues),
237
+ validationCoverage: z.enum(sourceValidationCoverageValues),
238
+ checkedAt: isoTimestampSchema.nullable(),
239
+ latestEvidenceAt: isoTimestampSchema.nullable(),
240
+ coverageStart: isoTimestampSchema.nullable(),
241
+ coverageEnd: isoTimestampSchema.nullable()
242
+ }).strict().superRefine((coverage, context) => {
243
+ if ((coverage.latestEvidenceAt || coverage.coverageStart || coverage.coverageEnd) &&
244
+ !coverage.checkedAt) {
245
+ context.addIssue({
246
+ code: "custom",
247
+ path: ["checkedAt"],
248
+ message: "Provider evidence timestamps require a receipt-bound check time."
249
+ });
250
+ }
251
+ if (coverage.latestEvidenceAt && coverage.checkedAt &&
252
+ Date.parse(coverage.latestEvidenceAt) > Date.parse(coverage.checkedAt)) {
253
+ context.addIssue({
254
+ code: "custom",
255
+ path: ["latestEvidenceAt"],
256
+ message: "Latest provider evidence cannot be newer than the receipt-bound check."
257
+ });
258
+ }
259
+ if ((coverage.coverageStart === null) !== (coverage.coverageEnd === null)) {
260
+ context.addIssue({
261
+ code: "custom",
262
+ path: ["coverageEnd"],
263
+ message: "Provider coverage bounds must be supplied together."
264
+ });
265
+ }
266
+ if (coverage.coverageStart && coverage.coverageEnd &&
267
+ Date.parse(coverage.coverageStart) > Date.parse(coverage.coverageEnd)) {
268
+ context.addIssue({
269
+ code: "custom",
270
+ path: ["coverageEnd"],
271
+ message: "Provider coverage end must not precede its start."
272
+ });
273
+ }
274
+ if (coverage.coverageEnd && coverage.checkedAt &&
275
+ Date.parse(coverage.coverageEnd) > Date.parse(coverage.checkedAt)) {
276
+ context.addIssue({
277
+ code: "custom",
278
+ path: ["coverageEnd"],
279
+ message: "Provider coverage cannot extend beyond its receipt-bound check."
280
+ });
281
+ }
282
+ if (coverage.latestEvidenceAt && coverage.coverageStart && coverage.coverageEnd &&
283
+ (Date.parse(coverage.latestEvidenceAt) < Date.parse(coverage.coverageStart) ||
284
+ Date.parse(coverage.latestEvidenceAt) > Date.parse(coverage.coverageEnd))) {
285
+ context.addIssue({
286
+ code: "custom",
287
+ path: ["latestEvidenceAt"],
288
+ message: "Latest provider evidence must fall inside the receipt-bound coverage interval."
289
+ });
290
+ }
291
+ });
292
+ export const activitySnapshotCoverageSchema = z.object({
293
+ agents: z.array(activitySnapshotAgentCoverageSchema).max(2),
294
+ providers: z.array(activitySnapshotProviderCoverageSchema).max(5),
295
+ recordsParsed: countSchema,
296
+ recordsPriced: countSchema,
297
+ recordsUnpriced: countSchema,
298
+ validationStatus: z.enum(["complete", "partial", "failed", "not_checked"]),
299
+ pricingAsOf: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
300
+ networkUploaded: z.literal(false)
301
+ }).strict().superRefine((coverage, context) => {
302
+ if (coverage.recordsPriced + coverage.recordsUnpriced !== coverage.recordsParsed) {
303
+ context.addIssue({
304
+ code: "custom",
305
+ path: ["recordsParsed"],
306
+ message: "Priced and unpriced record counts must equal parsed records."
307
+ });
308
+ }
309
+ if (new Set(coverage.agents.map((agent) => agent.agent)).size !== coverage.agents.length) {
310
+ context.addIssue({
311
+ code: "custom",
312
+ path: ["agents"],
313
+ message: "Coverage may contain each agent only once."
314
+ });
315
+ }
316
+ if (new Set(coverage.providers.map((provider) => provider.provider)).size !== coverage.providers.length) {
317
+ context.addIssue({
318
+ code: "custom",
319
+ path: ["providers"],
320
+ message: "Coverage may contain each provider only once."
321
+ });
322
+ }
323
+ });
324
+ export const activitySnapshotSchema = z.object({
325
+ kind: z.literal("aibill.activity_snapshot"),
326
+ schemaVersion: z.literal(1),
327
+ currency: z.literal("USD"),
328
+ asOf: isoTimestampSchema,
329
+ generatedAt: isoTimestampSchema,
330
+ lastAttemptAt: isoTimestampSchema,
331
+ lastSuccessAt: isoTimestampSchema.nullable(),
332
+ refresh: z.discriminatedUnion("status", [
333
+ z.object({ status: z.literal("ok") }).strict(),
334
+ z.object({
335
+ status: z.literal("error"),
336
+ errorCode: z.enum(activitySnapshotRefreshErrorCodeValues)
337
+ }).strict()
338
+ ]),
339
+ mode: z.enum(activitySnapshotModeValues),
340
+ subscription: activitySnapshotSubscriptionSchema.nullable(),
341
+ metered: activitySnapshotMeteredSchema.nullable(),
342
+ unresolved: activitySnapshotUnresolvedSchema.nullable(),
343
+ overage: activitySnapshotOverageSchema.nullable(),
344
+ coverage: activitySnapshotCoverageSchema,
345
+ networkUploaded: z.literal(false)
346
+ }).strict().superRefine((snapshot, context) => {
347
+ const invalid = (message, path) => context.addIssue({
348
+ code: "custom",
349
+ message,
350
+ path
351
+ });
352
+ if (snapshot.mode === "metered" && (!snapshot.metered || snapshot.subscription)) {
353
+ invalid("Metered mode requires a metered cohort and no subscription cohort.", ["mode"]);
354
+ }
355
+ if (snapshot.mode === "subscription" && (!snapshot.subscription || snapshot.metered)) {
356
+ invalid("Subscription mode requires a subscription cohort and no metered cohort.", ["mode"]);
357
+ }
358
+ if (snapshot.mode === "mixed" && (!snapshot.subscription || !snapshot.metered)) {
359
+ invalid("Mixed mode must keep subscription and metered cohorts separate.", ["mode"]);
360
+ }
361
+ if (snapshot.mode === "unresolved" && (!snapshot.unresolved || snapshot.subscription || snapshot.metered)) {
362
+ invalid("Unresolved mode must contain only unresolved API-equivalent evidence.", ["mode"]);
363
+ }
364
+ if ((snapshot.mode === "empty" || snapshot.mode === "error") &&
365
+ (snapshot.subscription || snapshot.metered || snapshot.unresolved || snapshot.overage)) {
366
+ invalid("Empty and error snapshots cannot carry financial cohorts.", ["mode"]);
367
+ }
368
+ if (snapshot.mode === "error" && snapshot.refresh.status !== "error") {
369
+ invalid("Error mode requires an error refresh state.", ["refresh"]);
370
+ }
371
+ if (snapshot.refresh.status === "ok" && snapshot.lastSuccessAt === null) {
372
+ invalid("Successful refreshes require lastSuccessAt.", ["lastSuccessAt"]);
373
+ }
374
+ if (snapshot.overage && !snapshot.metered) {
375
+ invalid("Verified billed overage belongs to the metered cohort.", ["overage"]);
376
+ }
377
+ const cohortAgents = [
378
+ ...(snapshot.subscription?.agents ?? []),
379
+ ...(snapshot.metered?.agents ?? []),
380
+ ...(snapshot.unresolved?.agents ?? [])
381
+ ].map((entry) => entry.agent);
382
+ if (new Set(cohortAgents).size !== cohortAgents.length) {
383
+ invalid("An agent cannot appear in more than one financial cohort.", ["mode"]);
384
+ }
385
+ const asOfMs = Date.parse(snapshot.asOf);
386
+ const generatedAtMs = Date.parse(snapshot.generatedAt);
387
+ const lastAttemptAtMs = Date.parse(snapshot.lastAttemptAt);
388
+ const lastSuccessAtMs = snapshot.lastSuccessAt === null
389
+ ? null
390
+ : Date.parse(snapshot.lastSuccessAt);
391
+ if (asOfMs > generatedAtMs) {
392
+ invalid("Snapshot generation cannot precede its as-of time.", ["generatedAt"]);
393
+ }
394
+ for (let index = 0; index < snapshot.coverage.providers.length; index += 1) {
395
+ const provider = snapshot.coverage.providers[index];
396
+ for (const field of ["checkedAt", "latestEvidenceAt", "coverageEnd"]) {
397
+ const value = provider[field];
398
+ if (value !== null && Date.parse(value) > generatedAtMs) {
399
+ invalid("Provider evidence cannot be newer than snapshot generation.", ["coverage", "providers", String(index), field]);
400
+ }
401
+ }
402
+ }
403
+ if (snapshot.refresh.status === "ok") {
404
+ if (snapshot.mode === "error") {
405
+ invalid("A successful refresh cannot use error mode.", ["mode"]);
406
+ }
407
+ if (lastSuccessAtMs !== generatedAtMs) {
408
+ invalid("A successful refresh must bind lastSuccessAt to generatedAt.", ["lastSuccessAt"]);
409
+ }
410
+ if (lastAttemptAtMs !== asOfMs) {
411
+ invalid("A successful refresh must bind lastAttemptAt to asOf.", ["lastAttemptAt"]);
412
+ }
413
+ }
414
+ else if (snapshot.lastSuccessAt === null) {
415
+ if (snapshot.mode !== "error" || lastAttemptAtMs !== asOfMs || generatedAtMs !== asOfMs) {
416
+ invalid("An initial failed refresh must be a single-time no-evidence error state.", ["refresh"]);
417
+ }
418
+ }
419
+ else {
420
+ if (snapshot.mode === "error") {
421
+ invalid("A retained last-good snapshot keeps its prior financial mode.", ["mode"]);
422
+ }
423
+ if (lastSuccessAtMs !== generatedAtMs || lastAttemptAtMs < generatedAtMs) {
424
+ invalid("A retained failure must follow the last successful generation.", ["lastAttemptAt"]);
425
+ }
426
+ }
427
+ });
428
+ /**
429
+ * Build the privacy-bounded, plan-aware snapshot consumed by the status line.
430
+ * This function never trusts provider billing implicitly: the caller must pass
431
+ * IDs already validated by the external connected-state trust receipt.
432
+ */
433
+ export function buildActivitySnapshot(input) {
434
+ if (input.sampleData === true ||
435
+ input.records.some((record) => isBundledSampleUsage([record]))) {
436
+ throw new Error("Sample data cannot create an activity snapshot.");
437
+ }
438
+ const asOfMs = parseTimestamp(input.asOf, "asOf");
439
+ const generatedAtMs = parseTimestamp(input.generatedAt, "generatedAt");
440
+ if (generatedAtMs < asOfMs) {
441
+ throw new Error("generatedAt must be at or after asOf.");
442
+ }
443
+ const generatedAt = new Date(generatedAtMs).toISOString();
444
+ const plans = safePlanMap(input.detectedPlans ?? []);
445
+ const scans = normalizeAgentScans(input.sourceScans ?? []);
446
+ const providers = normalizeProviderCoverage(input.providerCoverage ?? [], generatedAtMs);
447
+ const trustedIds = new Set(input.trustedProviderRecordIds ?? []);
448
+ const overageIds = new Set(input.billedOverageRecordIds ?? []);
449
+ for (const id of overageIds) {
450
+ if (!trustedIds.has(id)) {
451
+ throw new Error("Billed overage evidence must also be externally trusted provider evidence.");
452
+ }
453
+ }
454
+ const horizonStartMs = asOfMs - 30 * DAY_MS;
455
+ // Horizon filtering must precede ID conflict detection: stale history must
456
+ // neither activate a cohort nor suppress a current row that reused an ID.
457
+ const horizonRecords = input.records.filter((record) => validRecordTimestampInHorizon(record, horizonStartMs, asOfMs));
458
+ const deduplicated = deduplicateRecords(horizonRecords);
459
+ const classified = deduplicated.records
460
+ .map((record) => ({
461
+ record,
462
+ cohort: classifyRecord(record, plans, trustedIds)
463
+ }));
464
+ // Keep later same-day calls available only for proportionally splitting a
465
+ // daily aggregate at asOf; activity and limit selection remain <= asOf.
466
+ const allCalls = dedupeCumulativeSessionCalls([...(input.calls ?? [])].filter((call) =>
467
+ // One preceding bucket is needed to preserve the denominator when a
468
+ // daily aggregate straddles the 30-day cutoff.
469
+ validCallAtOrAfter(call, horizonStartMs - DAY_MS)));
470
+ const calls = allCalls.filter((call) => validCallAtOrBefore(call, asOfMs));
471
+ const subscriptionAgents = activitySubscriptionAgents(classified, calls, allCalls, plans, scans, trustedIds, asOfMs);
472
+ const meteredApiRecords = classified
473
+ .filter((entry) => entry.cohort === "metered_api")
474
+ .map((entry) => entry.record);
475
+ const meteredBilledRecords = classified
476
+ .filter((entry) => entry.cohort === "metered_billed")
477
+ .map((entry) => entry.record);
478
+ const unresolvedRecords = classified
479
+ .filter((entry) => entry.cohort === "unresolved")
480
+ .map((entry) => entry.record);
481
+ const activeAgents = new Set();
482
+ for (const entry of classified) {
483
+ if (isSnapshotAgent(entry.record.agentId))
484
+ activeAgents.add(entry.record.agentId);
485
+ }
486
+ for (const call of calls)
487
+ activeAgents.add(call.agent);
488
+ const meteredAgents = [...activeAgents]
489
+ .filter((agent) => plans.get(agent)?.billing === "api_key")
490
+ .sort()
491
+ .map((agent) => {
492
+ const detected = plans.get(agent);
493
+ return {
494
+ agent,
495
+ billing: "api_key",
496
+ planId: isKnownPlanId(detected?.planId) ? detected.planId : null
497
+ };
498
+ });
499
+ const unresolvedAgents = [...activeAgents]
500
+ .filter((agent) => {
501
+ const billing = plans.get(agent)?.billing;
502
+ return billing !== "subscription" && billing !== "api_key";
503
+ })
504
+ .sort()
505
+ .map((agent) => {
506
+ const detected = plans.get(agent);
507
+ return {
508
+ agent,
509
+ billing: "unknown",
510
+ planId: isKnownPlanId(detected?.planId) ? detected.planId : null
511
+ };
512
+ });
513
+ const hasReceiptProvedMeteredWindow = providerIntervalCoverage(providers, asOfMs - DAY_MS, asOfMs) === "complete";
514
+ const hasMeteredActivity = meteredApiRecords.length > 0 || meteredBilledRecords.length > 0 ||
515
+ meteredAgents.length > 0 || hasReceiptProvedMeteredWindow;
516
+ const hasSubscriptionActivity = subscriptionAgents.length > 0;
517
+ const hasUnresolvedActivity = unresolvedRecords.length > 0 || unresolvedAgents.length > 0;
518
+ let mode;
519
+ if (hasSubscriptionActivity && hasMeteredActivity)
520
+ mode = "mixed";
521
+ else if (hasSubscriptionActivity)
522
+ mode = "subscription";
523
+ else if (hasMeteredActivity)
524
+ mode = "metered";
525
+ else if (hasUnresolvedActivity)
526
+ mode = "unresolved";
527
+ else
528
+ mode = "empty";
529
+ const subscription = hasSubscriptionActivity ? { agents: subscriptionAgents } : null;
530
+ const metered = hasMeteredActivity ? {
531
+ agents: meteredAgents,
532
+ apiEquivalent: buildApiWindows(meteredApiRecords, allCalls.filter((call) => plans.get(call.agent)?.billing === "api_key"), trustedIds, asOfMs, localCoverageForRecords(meteredApiRecords, scans, [...plans.entries()]
533
+ .filter(([, plan]) => plan.billing === "api_key")
534
+ .map(([agent]) => agent))),
535
+ providerBilled: buildBilledWindows(meteredBilledRecords, asOfMs, providers)
536
+ } : null;
537
+ const unresolved = hasUnresolvedActivity ? {
538
+ agents: unresolvedAgents,
539
+ apiEquivalent: buildApiWindows(unresolvedRecords, allCalls.filter((call) => {
540
+ const billing = plans.get(call.agent)?.billing;
541
+ return billing !== "subscription" && billing !== "api_key";
542
+ }), trustedIds, asOfMs, unresolvedCoverageForRecords(unresolvedRecords, scans, providers))
543
+ } : null;
544
+ const overage = buildOverage(meteredBilledRecords, overageIds, asOfMs);
545
+ const coverage = buildCoverage(classified.map((entry) => entry.record), scans, providers, input.pricingAsOf ?? PRICING_TABLE_AS_OF, deduplicated.conflictingIds);
546
+ return activitySnapshotSchema.parse({
547
+ kind: "aibill.activity_snapshot",
548
+ schemaVersion: 1,
549
+ currency: "USD",
550
+ asOf: new Date(asOfMs).toISOString(),
551
+ generatedAt,
552
+ lastAttemptAt: new Date(asOfMs).toISOString(),
553
+ lastSuccessAt: generatedAt,
554
+ refresh: { status: "ok" },
555
+ mode,
556
+ subscription: mode === "metered" || mode === "unresolved" || mode === "empty"
557
+ ? null
558
+ : subscription,
559
+ metered: mode === "subscription" || mode === "unresolved" || mode === "empty"
560
+ ? null
561
+ : metered,
562
+ unresolved,
563
+ overage: mode === "metered" || mode === "mixed" ? overage : null,
564
+ coverage,
565
+ networkUploaded: false
566
+ });
567
+ }
568
+ /** A bounded no-evidence state for an initial failed refresh. */
569
+ export function createActivitySnapshotError(attemptedAt, errorCode) {
570
+ const timestamp = new Date(parseTimestamp(attemptedAt, "attemptedAt")).toISOString();
571
+ return activitySnapshotSchema.parse({
572
+ kind: "aibill.activity_snapshot",
573
+ schemaVersion: 1,
574
+ currency: "USD",
575
+ asOf: timestamp,
576
+ generatedAt: timestamp,
577
+ lastAttemptAt: timestamp,
578
+ lastSuccessAt: null,
579
+ refresh: { status: "error", errorCode },
580
+ mode: "error",
581
+ subscription: null,
582
+ metered: null,
583
+ unresolved: null,
584
+ overage: null,
585
+ coverage: {
586
+ agents: [],
587
+ providers: [],
588
+ recordsParsed: 0,
589
+ recordsPriced: 0,
590
+ recordsUnpriced: 0,
591
+ validationStatus: "failed",
592
+ pricingAsOf: PRICING_TABLE_AS_OF,
593
+ networkUploaded: false
594
+ },
595
+ networkUploaded: false
596
+ });
597
+ }
598
+ function safePlanMap(plans) {
599
+ const byAgent = new Map();
600
+ for (const plan of plans) {
601
+ if (!isSnapshotAgent(plan.agent) || byAgent.has(plan.agent))
602
+ continue;
603
+ byAgent.set(plan.agent, plan);
604
+ }
605
+ return byAgent;
606
+ }
607
+ function classifyRecord(record, plans, trustedProviderIds) {
608
+ if (trustedProviderIds.has(record.id) &&
609
+ record.costConfidence === "verified" &&
610
+ typeof record.amountUsd === "number") {
611
+ return "metered_billed";
612
+ }
613
+ const agent = isSnapshotAgent(record.agentId) ? record.agentId : undefined;
614
+ const billing = agent ? plans.get(agent)?.billing : undefined;
615
+ if (billing === "subscription")
616
+ return "subscription";
617
+ if (billing === "api_key")
618
+ return "metered_api";
619
+ return "unresolved";
620
+ }
621
+ function activitySubscriptionAgents(records, calls, allCalls, plans, scans, trustedProviderIds, asOfMs) {
622
+ const result = [];
623
+ for (const agent of activitySnapshotAgentValues) {
624
+ const plan = plans.get(agent);
625
+ if (plan?.billing !== "subscription")
626
+ continue;
627
+ const agentRecords = records
628
+ .filter((entry) => entry.cohort === "subscription" && entry.record.agentId === agent)
629
+ .map((entry) => entry.record);
630
+ const agentCalls = calls.filter((call) => call.agent === agent);
631
+ if (agentRecords.length === 0 && agentCalls.length === 0)
632
+ continue;
633
+ result.push({
634
+ agent,
635
+ billing: "subscription",
636
+ planId: isKnownPlanId(plan.planId) ? plan.planId : null,
637
+ apiEquivalent: buildApiWindows(agentRecords, allCalls.filter((call) => call.agent === agent), trustedProviderIds, asOfMs, localCoverageForAgent(agent, scans)),
638
+ limits: latestReportedLimits(agentCalls, asOfMs),
639
+ pressure: plan.limitSignal === "extra-usage credits exhausted"
640
+ ? "extra_usage_credits_exhausted"
641
+ : null
642
+ });
643
+ }
644
+ return result;
645
+ }
646
+ function latestReportedLimits(calls, asOfMs) {
647
+ const selected = new Map();
648
+ for (const call of calls) {
649
+ if (!call.rateLimits)
650
+ continue;
651
+ const observedMs = Date.parse(call.rateLimits.observedAt);
652
+ if (!Number.isFinite(observedMs) || observedMs > asOfMs)
653
+ continue;
654
+ for (const window of call.rateLimits.windows) {
655
+ if (window.kind !== "five-hour" && window.kind !== "weekly")
656
+ continue;
657
+ const resetMs = Date.parse(window.resetsAt);
658
+ if (!Number.isFinite(resetMs) || resetMs <= asOfMs)
659
+ continue;
660
+ const prior = selected.get(window.kind);
661
+ if (!prior || Date.parse(prior.observedAt) < observedMs) {
662
+ selected.set(window.kind, { observedAt: call.rateLimits.observedAt, window });
663
+ }
664
+ }
665
+ }
666
+ return ["five-hour", "weekly"].flatMap((kind) => {
667
+ const value = selected.get(kind);
668
+ if (!value)
669
+ return [];
670
+ const usedPercent = roundPercent(value.window.usedPercent);
671
+ return [{
672
+ kind,
673
+ usedPercent,
674
+ remainingPercent: roundPercent(100 - usedPercent),
675
+ observedAt: new Date(Date.parse(value.observedAt)).toISOString(),
676
+ resetsAt: new Date(Date.parse(value.window.resetsAt)).toISOString(),
677
+ source: "transcript_reported"
678
+ }];
679
+ });
680
+ }
681
+ function buildApiWindows(records, calls, trustedProviderIds, asOfMs, sourceCoverage) {
682
+ const observations = apiEquivalentObservations(records, calls, trustedProviderIds);
683
+ return {
684
+ oneDay: buildApiWindow(observations, asOfMs, 1, sourceCoverage),
685
+ sevenDays: buildApiWindow(observations, asOfMs, 7, sourceCoverage),
686
+ thirtyDays: buildApiWindow(observations, asOfMs, 30, sourceCoverage)
687
+ };
688
+ }
689
+ function buildApiWindow(observations, asOfMs, days, sourceCoverage) {
690
+ const inWindow = observationsForWindow(observations, asOfMs, days);
691
+ const basisAvailable = inWindow.filter((observation) => observation.apiEquivalentBasisAvailable);
692
+ const priced = basisAvailable.filter((observation) => typeof observation.amountUsd === "number");
693
+ const unpriced = basisAvailable.length - priced.length;
694
+ const basisUnavailable = inWindow.length - basisAvailable.length;
695
+ const boundaryLimited = inWindow.some((observation) => observation.precision !== "exact");
696
+ const coverage = basisAvailable.length === 0 && basisUnavailable > 0
697
+ ? "missing"
698
+ : windowCoverage(sourceCoverage, unpriced + basisUnavailable, boundaryLimited);
699
+ const observedAmount = priced.length > 0
700
+ ? roundUsd(priced.reduce((sum, observation) => sum + (observation.amountUsd ?? 0), 0))
701
+ : null;
702
+ const amountUsd = observedAmount !== null &&
703
+ sourceCoverage !== "missing" &&
704
+ (observedAmount > 0 || coverage === "complete")
705
+ ? observedAmount
706
+ : coverage === "complete"
707
+ ? 0
708
+ : null;
709
+ return {
710
+ amountUsd,
711
+ recordCount: inWindow.length,
712
+ basis: "api_equivalent",
713
+ financialEvidence: amountUsd === null ? "missing" : "estimated",
714
+ coverage
715
+ };
716
+ }
717
+ function apiEquivalentObservations(records, calls, trustedProviderIds) {
718
+ const deduplicatedCalls = dedupeCumulativeSessionCalls([...calls]);
719
+ const callsByAggregateId = new Map();
720
+ for (const call of deduplicatedCalls) {
721
+ const aggregateId = aggregateCalls([call])[0]?.id;
722
+ if (!aggregateId)
723
+ continue;
724
+ callsByAggregateId.set(aggregateId, [...(callsByAggregateId.get(aggregateId) ?? []), call]);
725
+ }
726
+ const matchedCalls = new Set();
727
+ const observations = [];
728
+ for (const record of records) {
729
+ const apiEquivalentBasisAvailable = record.providerCostType === "local_agent_logs" ||
730
+ (record.providerCostType === "anthropic_claude_code_usage" &&
731
+ trustedProviderIds.has(record.id));
732
+ if (!apiEquivalentBasisAvailable) {
733
+ observations.push({
734
+ timestamp: record.timestamp,
735
+ amountUsd: null,
736
+ precision: bucketPrecision(record),
737
+ apiEquivalentBasisAvailable: false
738
+ });
739
+ continue;
740
+ }
741
+ const matchingCalls = callsByAggregateId.get(record.id) ?? [];
742
+ if (matchingCalls.length === 0) {
743
+ observations.push({
744
+ timestamp: record.timestamp,
745
+ amountUsd: record.amountUsd,
746
+ precision: record.usageGranularity === "daily_aggregate" ? "daily_bucket" : bucketPrecision(record),
747
+ apiEquivalentBasisAvailable: true
748
+ });
749
+ continue;
750
+ }
751
+ const allocated = allocateAggregateAmount(record.amountUsd, matchingCalls);
752
+ for (let index = 0; index < matchingCalls.length; index += 1) {
753
+ const call = matchingCalls[index];
754
+ matchedCalls.add(call);
755
+ observations.push({
756
+ timestamp: call.timestamp,
757
+ ...(call.usageScope === "session_cumulative" && call.startedAt
758
+ ? { intervalStart: call.startedAt }
759
+ : {}),
760
+ amountUsd: allocated[index] ?? null,
761
+ precision: call.usageScope === "session_cumulative"
762
+ ? "session_interval"
763
+ : "exact",
764
+ apiEquivalentBasisAvailable: true
765
+ });
766
+ }
767
+ }
768
+ for (const call of deduplicatedCalls) {
769
+ if (matchedCalls.has(call))
770
+ continue;
771
+ observations.push({
772
+ timestamp: call.timestamp,
773
+ ...(call.usageScope === "session_cumulative" && call.startedAt
774
+ ? { intervalStart: call.startedAt }
775
+ : {}),
776
+ amountUsd: null,
777
+ precision: call.usageScope === "session_cumulative"
778
+ ? "session_interval"
779
+ : "exact",
780
+ apiEquivalentBasisAvailable: true
781
+ });
782
+ }
783
+ return observations;
784
+ }
785
+ function allocateAggregateAmount(amountUsd, calls) {
786
+ if (amountUsd === null)
787
+ return calls.map(() => null);
788
+ const weights = calls.map((call) => estimateTokenCostUsd(call.model, call.usage) ?? 0);
789
+ const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
790
+ if (totalWeight <= 0)
791
+ return calls.map(() => amountUsd / calls.length);
792
+ return weights.map((weight) => amountUsd * weight / totalWeight);
793
+ }
794
+ function observationsForWindow(observations, asOfMs, days) {
795
+ const boundaryMs = asOfMs - days * DAY_MS;
796
+ const selected = [];
797
+ for (const observation of observations) {
798
+ const timestampMs = Date.parse(observation.timestamp);
799
+ if (!Number.isFinite(timestampMs) || timestampMs > asOfMs)
800
+ continue;
801
+ if (observation.precision === "exact") {
802
+ if (timestampMs >= boundaryMs)
803
+ selected.push(observation);
804
+ continue;
805
+ }
806
+ if (observation.precision === "session_interval") {
807
+ if (timestampMs < boundaryMs)
808
+ continue;
809
+ const intervalStartMs = observation.intervalStart === undefined
810
+ ? Number.NaN
811
+ : Date.parse(observation.intervalStart);
812
+ if (!Number.isFinite(intervalStartMs) || intervalStartMs > timestampMs ||
813
+ intervalStartMs < boundaryMs) {
814
+ selected.push({ ...observation, amountUsd: null });
815
+ }
816
+ else {
817
+ // A cumulative total wholly observed inside this rolling window is
818
+ // exact for the window; only a cutoff-straddling interval is partial.
819
+ selected.push({ ...observation, precision: "exact" });
820
+ }
821
+ continue;
822
+ }
823
+ if (observation.precision === "daily_bucket") {
824
+ const intervalEndMs = Math.min(timestampMs + DAY_MS, asOfMs);
825
+ if (intervalEndMs <= boundaryMs || timestampMs > asOfMs)
826
+ continue;
827
+ if (timestampMs < boundaryMs) {
828
+ selected.push({ ...observation, amountUsd: null });
829
+ }
830
+ else {
831
+ selected.push(observation);
832
+ }
833
+ continue;
834
+ }
835
+ if (timestampMs >= boundaryMs)
836
+ selected.push(observation);
837
+ }
838
+ return selected;
839
+ }
840
+ function bucketPrecision(record) {
841
+ if (record.usageGranularity === "call" || record.usageGranularity === "invocation") {
842
+ return "exact";
843
+ }
844
+ return record.usageGranularity === "daily_aggregate"
845
+ ? "daily_bucket"
846
+ : "unbounded_bucket";
847
+ }
848
+ function buildBilledWindows(records, asOfMs, providers) {
849
+ return {
850
+ oneDay: buildBilledWindow(records, asOfMs, 1, providers),
851
+ sevenDays: buildBilledWindow(records, asOfMs, 7, providers),
852
+ thirtyDays: buildBilledWindow(records, asOfMs, 30, providers)
853
+ };
854
+ }
855
+ function buildBilledWindow(records, asOfMs, days, providers) {
856
+ const sourceCoverage = providerIntervalCoverage(providers, asOfMs - days * DAY_MS, asOfMs);
857
+ const boundaryMs = asOfMs - days * DAY_MS;
858
+ const fullyContained = [];
859
+ let overlappingRecordCount = 0;
860
+ let boundaryLimited = false;
861
+ for (const record of records) {
862
+ const timestampMs = Date.parse(record.timestamp);
863
+ if (!Number.isFinite(timestampMs) || timestampMs > asOfMs)
864
+ continue;
865
+ const precision = billedBucketPrecision(record);
866
+ if (precision === "exact") {
867
+ if (timestampMs >= boundaryMs) {
868
+ fullyContained.push(record);
869
+ overlappingRecordCount += 1;
870
+ }
871
+ continue;
872
+ }
873
+ if (precision === "daily_bucket") {
874
+ const intervalEndMs = billedBucketObservedEnd(record, providers, asOfMs);
875
+ if (intervalEndMs === null) {
876
+ if (timestampMs >= boundaryMs) {
877
+ overlappingRecordCount += 1;
878
+ boundaryLimited = true;
879
+ }
880
+ continue;
881
+ }
882
+ if (intervalEndMs <= boundaryMs || timestampMs > asOfMs)
883
+ continue;
884
+ overlappingRecordCount += 1;
885
+ if (timestampMs < boundaryMs || intervalEndMs > asOfMs) {
886
+ boundaryLimited = true;
887
+ }
888
+ else {
889
+ fullyContained.push(record);
890
+ }
891
+ continue;
892
+ }
893
+ if (timestampMs >= boundaryMs) {
894
+ overlappingRecordCount += 1;
895
+ boundaryLimited = true;
896
+ }
897
+ }
898
+ if (overlappingRecordCount === 0) {
899
+ const intervalProved = sourceCoverage === "complete";
900
+ return {
901
+ amountUsd: intervalProved ? 0 : null,
902
+ recordCount: 0,
903
+ basis: "provider_billed",
904
+ financialEvidence: intervalProved ? "verified" : "missing",
905
+ coverage: intervalProved ? "complete" : "missing"
906
+ };
907
+ }
908
+ const coverage = windowCoverage(sourceCoverage === "complete" ? "complete" : "partial", 0, boundaryLimited);
909
+ const amountUsd = boundaryLimited
910
+ ? null
911
+ : roundUsd(fullyContained.reduce((sum, record) => sum + (record.amountUsd ?? 0), 0));
912
+ return {
913
+ amountUsd,
914
+ recordCount: overlappingRecordCount,
915
+ basis: "provider_billed",
916
+ financialEvidence: amountUsd === null ? "missing" : "verified",
917
+ coverage
918
+ };
919
+ }
920
+ function billedBucketPrecision(record) {
921
+ if (record.usageGranularity === "call" || record.usageGranularity === "invocation") {
922
+ return "exact";
923
+ }
924
+ if (record.usageGranularity === "billing_bucket" ||
925
+ record.usageGranularity === "daily_aggregate") {
926
+ return "daily_bucket";
927
+ }
928
+ return "unbounded_bucket";
929
+ }
930
+ function billedBucketObservedEnd(record, providers, asOfMs) {
931
+ const timestampMs = Date.parse(record.timestamp);
932
+ const provider = activitySnapshotProviderForRecord(record);
933
+ const checkedAt = providers
934
+ .filter((candidate) => candidate.provider === provider && candidate.checkedAt !== undefined)
935
+ .map((candidate) => Date.parse(candidate.checkedAt))
936
+ .filter(Number.isFinite)
937
+ .sort((left, right) => right - left)[0];
938
+ if (checkedAt === undefined)
939
+ return null;
940
+ return Math.min(timestampMs + DAY_MS, checkedAt, asOfMs);
941
+ }
942
+ function activitySnapshotProviderForRecord(record) {
943
+ const provider = record.source.provider.toLowerCase();
944
+ if (provider === "openai")
945
+ return "openai";
946
+ if (provider === "anthropic")
947
+ return "anthropic";
948
+ if (provider === "cursor")
949
+ return "cursor";
950
+ if (provider === "github-copilot" || provider === "github" || provider === "copilot") {
951
+ return "github-copilot";
952
+ }
953
+ return "other";
954
+ }
955
+ function providerIntervalCoverage(providers, windowStartMs, windowEndMs) {
956
+ if (providers.length === 0)
957
+ return "missing";
958
+ const spansWindow = (provider) => provider.status === "complete" &&
959
+ provider.checkedAt !== undefined &&
960
+ provider.coverageStart !== undefined &&
961
+ provider.coverageEnd !== undefined &&
962
+ Date.parse(provider.coverageStart) <= windowStartMs &&
963
+ Date.parse(provider.coverageEnd) >= windowEndMs &&
964
+ Date.parse(provider.checkedAt) >= Date.parse(provider.coverageEnd);
965
+ if (providers.every(spansWindow))
966
+ return "complete";
967
+ const overlapsWindow = providers.some((provider) => (provider.status === "complete" || provider.status === "partial") &&
968
+ provider.coverageStart !== undefined &&
969
+ provider.coverageEnd !== undefined &&
970
+ Date.parse(provider.coverageStart) < windowEndMs &&
971
+ Date.parse(provider.coverageEnd) > windowStartMs);
972
+ return overlapsWindow ? "partial" : "missing";
973
+ }
974
+ function buildOverage(billedRecords, overageIds, asOfMs) {
975
+ const records = billedRecords.filter((record) => overageIds.has(record.id) &&
976
+ typeof record.amountUsd === "number" &&
977
+ record.amountUsd > 0 &&
978
+ inRollingWindow(record.timestamp, asOfMs, 30));
979
+ if (records.length === 0)
980
+ return null;
981
+ return {
982
+ amountUsd: roundUsd(records.reduce((sum, record) => sum + (record.amountUsd ?? 0), 0)),
983
+ currency: "USD",
984
+ basis: "provider_billed",
985
+ financialEvidence: "verified",
986
+ alertEligible: true,
987
+ recordCount: records.length
988
+ };
989
+ }
990
+ function buildCoverage(records, scans, providers, pricingAsOf, conflictingIds) {
991
+ const priced = records.filter((record) => typeof record.amountUsd === "number").length;
992
+ const unpriced = records.length - priced;
993
+ const failed = scans.some((scan) => scan.directoryStatus === "unreadable") ||
994
+ providers.some((provider) => provider.status === "error" || provider.validationCoverage === "failed");
995
+ const partial = conflictingIds > 0 || unpriced > 0 ||
996
+ scans.some((scan) => scan.directoryStatus === "missing" ||
997
+ scan.malformedLines > 0 || scan.unreadableFiles > 0 ||
998
+ scan.unsupportedUsageSnapshots > 0 ||
999
+ scan.jsonlValidationCoverage !== "complete") ||
1000
+ providers.some((provider) => provider.status === "partial" || provider.status === "unavailable");
1001
+ const checked = scans.length > 0 || providers.length > 0;
1002
+ const validationStatus = failed
1003
+ ? "failed"
1004
+ : partial
1005
+ ? "partial"
1006
+ : checked
1007
+ ? "complete"
1008
+ : "not_checked";
1009
+ return activitySnapshotCoverageSchema.parse({
1010
+ agents: scans.map((scan) => ({
1011
+ agent: scan.agent,
1012
+ directoryStatus: scan.directoryStatus,
1013
+ filesDiscovered: scan.filesDiscovered,
1014
+ filesParsed: scan.filesParsed,
1015
+ malformedLines: scan.malformedLines,
1016
+ unreadableFiles: scan.unreadableFiles,
1017
+ unsupportedUsageSnapshots: scan.unsupportedUsageSnapshots,
1018
+ filesSkippedBeforeWindow: scan.filesSkippedBeforeWindow ?? 0,
1019
+ filesReadFinancially: scan.filesReadFinancially ?? 0,
1020
+ bytesSkippedAsNonFinancialHistory: scan.bytesSkippedAsNonFinancialHistory ?? 0,
1021
+ nonFinancialLinesPrefiltered: scan.nonFinancialLinesPrefiltered ?? 0,
1022
+ nonFinancialBytesPrefiltered: scan.nonFinancialBytesPrefiltered ?? 0,
1023
+ jsonlValidationCoverage: scan.jsonlValidationCoverage ?? "not_reported"
1024
+ })),
1025
+ providers: providers.map((provider) => ({
1026
+ ...provider,
1027
+ checkedAt: provider.checkedAt ?? null,
1028
+ latestEvidenceAt: provider.latestEvidenceAt ?? null,
1029
+ coverageStart: provider.coverageStart ?? null,
1030
+ coverageEnd: provider.coverageEnd ?? null
1031
+ })),
1032
+ recordsParsed: records.length,
1033
+ recordsPriced: priced,
1034
+ recordsUnpriced: unpriced,
1035
+ validationStatus,
1036
+ pricingAsOf,
1037
+ networkUploaded: false
1038
+ });
1039
+ }
1040
+ function normalizeAgentScans(scans) {
1041
+ const latest = new Map();
1042
+ for (const scan of scans) {
1043
+ if (isSnapshotAgent(scan.agent))
1044
+ latest.set(scan.agent, scan);
1045
+ }
1046
+ return [...latest.values()].sort((left, right) => left.agent.localeCompare(right.agent));
1047
+ }
1048
+ function normalizeProviderCoverage(providers, generatedAtMs) {
1049
+ const latest = new Map();
1050
+ for (const provider of providers) {
1051
+ if (!activitySnapshotProviderValues.includes(provider.provider))
1052
+ continue;
1053
+ if (provider.checkedAt !== undefined)
1054
+ parseTimestamp(provider.checkedAt, "provider checkedAt");
1055
+ if (provider.latestEvidenceAt !== undefined) {
1056
+ parseTimestamp(provider.latestEvidenceAt, "provider latestEvidenceAt");
1057
+ }
1058
+ if (provider.coverageStart !== undefined) {
1059
+ parseTimestamp(provider.coverageStart, "provider coverageStart");
1060
+ }
1061
+ if (provider.coverageEnd !== undefined) {
1062
+ parseTimestamp(provider.coverageEnd, "provider coverageEnd");
1063
+ }
1064
+ if ((provider.coverageStart === undefined) !== (provider.coverageEnd === undefined)) {
1065
+ throw new Error("Provider coverageStart and coverageEnd must be supplied together.");
1066
+ }
1067
+ if ((provider.latestEvidenceAt !== undefined || provider.coverageStart !== undefined) &&
1068
+ provider.checkedAt === undefined) {
1069
+ throw new Error("Provider evidence timestamps require checkedAt.");
1070
+ }
1071
+ if (provider.checkedAt && provider.latestEvidenceAt &&
1072
+ Date.parse(provider.latestEvidenceAt) > Date.parse(provider.checkedAt)) {
1073
+ throw new Error("Provider latestEvidenceAt must not be newer than checkedAt.");
1074
+ }
1075
+ if (provider.coverageStart && provider.coverageEnd &&
1076
+ Date.parse(provider.coverageStart) > Date.parse(provider.coverageEnd)) {
1077
+ throw new Error("Provider coverageEnd must not precede coverageStart.");
1078
+ }
1079
+ if (provider.coverageEnd && provider.checkedAt &&
1080
+ Date.parse(provider.coverageEnd) > Date.parse(provider.checkedAt)) {
1081
+ throw new Error("Provider coverageEnd must not be newer than checkedAt.");
1082
+ }
1083
+ if (provider.checkedAt && Date.parse(provider.checkedAt) > generatedAtMs) {
1084
+ throw new Error("Provider checkedAt must not be newer than generatedAt.");
1085
+ }
1086
+ if (provider.latestEvidenceAt && provider.coverageStart && provider.coverageEnd &&
1087
+ (Date.parse(provider.latestEvidenceAt) < Date.parse(provider.coverageStart) ||
1088
+ Date.parse(provider.latestEvidenceAt) > Date.parse(provider.coverageEnd))) {
1089
+ throw new Error("Provider latestEvidenceAt must fall inside its coverage interval.");
1090
+ }
1091
+ latest.set(provider.provider, provider);
1092
+ }
1093
+ return [...latest.values()].sort((left, right) => left.provider.localeCompare(right.provider));
1094
+ }
1095
+ function localCoverageForAgent(agent, scans) {
1096
+ const scan = scans.find((candidate) => candidate.agent === agent);
1097
+ if (!scan)
1098
+ return "missing";
1099
+ if (scan.directoryStatus !== "readable")
1100
+ return "missing";
1101
+ return scan.malformedLines > 0 || scan.unreadableFiles > 0 || scan.unsupportedUsageSnapshots > 0
1102
+ || scan.jsonlValidationCoverage !== "complete"
1103
+ ? "partial"
1104
+ : "complete";
1105
+ }
1106
+ function localCoverageForRecords(records, scans, fallbackAgents = []) {
1107
+ const agents = new Set([
1108
+ ...records.map((record) => record.agentId).filter(isSnapshotAgent),
1109
+ ...fallbackAgents
1110
+ ]);
1111
+ if (agents.size === 0) {
1112
+ return "missing";
1113
+ }
1114
+ const statuses = [...agents].map((agent) => localCoverageForAgent(agent, scans));
1115
+ if (statuses.every((status) => status === "complete"))
1116
+ return "complete";
1117
+ if (statuses.some((status) => status !== "missing"))
1118
+ return "partial";
1119
+ return "missing";
1120
+ }
1121
+ function unresolvedCoverageForRecords(records, scans, providers) {
1122
+ const localRecords = records.filter((record) => record.providerCostType === "local_agent_logs");
1123
+ const providerRecords = records.filter((record) => record.providerCostType !== "local_agent_logs");
1124
+ const statuses = [];
1125
+ if (localRecords.length > 0)
1126
+ statuses.push(localCoverageForRecords(localRecords, scans));
1127
+ if (providerRecords.length > 0)
1128
+ statuses.push(providerCoverageCompleteness(providers));
1129
+ if (statuses.length === 0)
1130
+ return "missing";
1131
+ if (statuses.every((status) => status === "complete"))
1132
+ return "complete";
1133
+ if (statuses.some((status) => status !== "missing"))
1134
+ return "partial";
1135
+ return "missing";
1136
+ }
1137
+ function providerCoverageCompleteness(providers) {
1138
+ if (providers.length === 0)
1139
+ return "missing";
1140
+ if (providers.every((provider) => provider.status === "complete"))
1141
+ return "complete";
1142
+ if (providers.some((provider) => provider.status === "complete" || provider.status === "partial"))
1143
+ return "partial";
1144
+ return "missing";
1145
+ }
1146
+ function windowCoverage(sourceCoverage, unpriced, boundaryLimited = false) {
1147
+ if (sourceCoverage === "missing")
1148
+ return "missing";
1149
+ return sourceCoverage === "partial" || unpriced > 0 || boundaryLimited
1150
+ ? "partial"
1151
+ : "complete";
1152
+ }
1153
+ function deduplicateRecords(records) {
1154
+ const byId = new Map();
1155
+ for (const record of records)
1156
+ byId.set(record.id, [...(byId.get(record.id) ?? []), record]);
1157
+ const output = [];
1158
+ let conflictingIds = 0;
1159
+ for (const group of byId.values()) {
1160
+ const signatures = new Set(group.map(financialRecordSignature));
1161
+ if (signatures.size > 1) {
1162
+ conflictingIds += 1;
1163
+ continue;
1164
+ }
1165
+ output.push(group[0]);
1166
+ }
1167
+ return { records: output, conflictingIds };
1168
+ }
1169
+ function financialRecordSignature(record) {
1170
+ return JSON.stringify([
1171
+ record.timestamp,
1172
+ record.agentId ?? null,
1173
+ record.amountUsd,
1174
+ record.costConfidence,
1175
+ record.providerCostType ?? null,
1176
+ record.inputTokens,
1177
+ record.outputTokens
1178
+ ]);
1179
+ }
1180
+ function validRecordTimestampInHorizon(record, horizonStartMs, asOfMs) {
1181
+ const timestamp = Date.parse(record.timestamp);
1182
+ if (!Number.isFinite(timestamp) || timestamp > asOfMs)
1183
+ return false;
1184
+ if (record.usageGranularity === "daily_aggregate" ||
1185
+ record.usageGranularity === "billing_bucket") {
1186
+ return timestamp + DAY_MS > horizonStartMs;
1187
+ }
1188
+ return timestamp >= horizonStartMs;
1189
+ }
1190
+ function validCallAtOrBefore(call, asOfMs) {
1191
+ const timestamp = Date.parse(call.timestamp);
1192
+ return Number.isFinite(timestamp) && timestamp <= asOfMs;
1193
+ }
1194
+ function validCallAtOrAfter(call, horizonStartMs) {
1195
+ const timestamp = Date.parse(call.timestamp);
1196
+ return Number.isFinite(timestamp) && timestamp >= horizonStartMs;
1197
+ }
1198
+ function inRollingWindow(timestamp, asOfMs, days) {
1199
+ const value = Date.parse(timestamp);
1200
+ return Number.isFinite(value) && value >= asOfMs - days * DAY_MS && value <= asOfMs;
1201
+ }
1202
+ function isSnapshotAgent(value) {
1203
+ return typeof value === "string" && activitySnapshotAgentValues.includes(value);
1204
+ }
1205
+ function isKnownPlanId(value) {
1206
+ return typeof value === "string" && activitySnapshotPlanIdValues.includes(value);
1207
+ }
1208
+ function parseTimestamp(value, label) {
1209
+ const parsed = Date.parse(value);
1210
+ if (!Number.isFinite(parsed))
1211
+ throw new Error(`${label} must be a valid ISO timestamp.`);
1212
+ return parsed;
1213
+ }
1214
+ function roundUsd(value) {
1215
+ return Math.round((value + Number.EPSILON) * 1_000_000) / 1_000_000;
1216
+ }
1217
+ function roundPercent(value) {
1218
+ return Math.round(Math.max(0, Math.min(100, value)) * 10) / 10;
1219
+ }
1220
+ //# sourceMappingURL=activitySnapshot.js.map