@agent-finops/core 0.9.4 → 0.9.6

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.
@@ -673,10 +673,10 @@ declare const experimentBodySchema: z.ZodObject<{
673
673
  }, z.core.$strict>>;
674
674
  invalidation: z.ZodOptional<z.ZodObject<{
675
675
  reason: z.ZodEnum<{
676
- manual: "manual";
677
676
  scope_changed: "scope_changed";
678
677
  source_semantics_changed: "source_semantics_changed";
679
678
  concurrent_change: "concurrent_change";
679
+ manual: "manual";
680
680
  }>;
681
681
  invalidatedAt: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
682
682
  }, z.core.$strict>>;
@@ -1115,10 +1115,10 @@ declare const experimentObjectSchema: z.ZodObject<{
1115
1115
  }, z.core.$strict>>;
1116
1116
  invalidation: z.ZodOptional<z.ZodObject<{
1117
1117
  reason: z.ZodEnum<{
1118
- manual: "manual";
1119
1118
  scope_changed: "scope_changed";
1120
1119
  source_semantics_changed: "source_semantics_changed";
1121
1120
  concurrent_change: "concurrent_change";
1121
+ manual: "manual";
1122
1122
  }>;
1123
1123
  invalidatedAt: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
1124
1124
  }, z.core.$strict>>;
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { aggregateCalls, dedupeCumulativeSessionCalls } from "./localAgentLogs.js";
3
3
  import { localAgentFormatDescriptors } from "./localAgentFormats/registry.js";
4
- import { canPriceTokenUsageAtScope, estimateTokenCostUsd, PRICING_TABLE_AS_OF } from "./modelPricing.js";
4
+ import { canPriceTokenUsageAtScope, estimateTokenCostsUsd, PRICING_TABLE_AS_OF } from "./modelPricing.js";
5
5
  import { subscriptionPlans } from "./planMath.js";
6
6
  import { isBundledSampleUsage } from "./schema.js";
7
7
  import { sourceValidationCoverageValues } from "./sourceStatus.js";
@@ -937,13 +937,22 @@ function apiEquivalentObservations(records, calls, trustedProviderIds) {
937
937
  }
938
938
  return observations;
939
939
  }
940
+ /**
941
+ * One call's API-equivalent cost with its tier taken from the largest single
942
+ * request it contains, matching the report's aggregation. Weighting a
943
+ * session-cumulative slice at its own cache-inflated prompt would put it on the
944
+ * wrong tier and skew the allocation.
945
+ */
946
+ function callAmountUsd(call) {
947
+ return estimateTokenCostsUsd(call.model, [call.usage], [call.maxRequestPromptTokens]);
948
+ }
940
949
  function allocateAggregateAmount(amountUsd, calls) {
941
950
  if (amountUsd === null)
942
951
  return calls.map(() => null);
943
- const priceable = calls.map((call) => canPriceTokenUsageAtScope(call.model, call.usage, call.usageScope === "turn" ? "request" : "aggregate") && estimateTokenCostUsd(call.model, call.usage) !== undefined);
952
+ const priceable = calls.map((call) => canPriceTokenUsageAtScope(call.model, call.usage, call.usageScope === "turn" ? "request" : "aggregate", call.maxRequestPromptTokens) && callAmountUsd(call) !== undefined);
944
953
  if (priceable.some((supported) => !supported))
945
954
  return calls.map(() => null);
946
- const weights = calls.map((call) => estimateTokenCostUsd(call.model, call.usage) ?? 0);
955
+ const weights = calls.map((call) => callAmountUsd(call) ?? 0);
947
956
  const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
948
957
  if (totalWeight <= 0) {
949
958
  return amountUsd === 0 ? calls.map(() => 0) : calls.map(() => null);
package/dist/glance.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { dedupeCumulativeSessionCalls, sanitizeLocalActivityText } from "./localAgentLogs.js";
2
- import { canPriceTokenUsageAtScope, estimateTokenCostUsd, PRICING_TABLE_AS_OF } from "./modelPricing.js";
2
+ import { canPriceTokenUsageAtScope, estimateTokenCostsUsd, PRICING_TABLE_AS_OF } from "./modelPricing.js";
3
3
  import { subscriptionPlans } from "./planMath.js";
4
4
  import { buildContextHealth } from "./contextHealth.js";
5
5
  import { localAgentFormatDescriptors, localAgentFormatSupports } from "./localAgentFormats/registry.js";
@@ -106,7 +106,7 @@ export function buildUsageGlance(calls, options = {}) {
106
106
  "Claude Code transcripts do not report plan headroom. Missing limits remain unavailable instead of being inferred.",
107
107
  "Cursor and GitHub Copilot require their provider connections because their local chat stores are not treated as authoritative billing transcripts.",
108
108
  ...(qualitativeComplete ? [] : [
109
- "Main focus, anomaly, and context-change handoff are unavailable because the bounded qualitative index is incomplete; no global driver was inferred from a selected subset."
109
+ "Main focus, anomaly, and context-change handoff are unavailable because some session transcripts have not been read yet; no global driver was inferred from a partly-read subset."
110
110
  ])
111
111
  ];
112
112
  return {
@@ -202,10 +202,10 @@ function buildCoverageLimitedPrimaryAction(input) {
202
202
  kind: "session_handoff",
203
203
  intent: "inspect_current_work",
204
204
  label: project ? `Refresh evidence · ${project}` : "Refresh evidence",
205
- detail: `Main focus unavailable · qualitative index ${status}`,
205
+ detail: `Main focus unavailable · session transcripts ${status === "partial" ? "only partly read" : "not available"}`,
206
206
  ...(project ? { project } : {}),
207
207
  agentPrompt: [
208
- "aibill's bounded qualitative evidence is incomplete.",
208
+ "Some coding-agent session transcripts have not been read yet.",
209
209
  "Do not infer a global main focus, waste cause, or context change from the selected subset.",
210
210
  `Run \`${aibillImproveCommandV0()}\` from the exact project root to refresh the private index, then review the new evidence before editing.`
211
211
  ].join("\n"),
@@ -793,9 +793,13 @@ function limitActionName(limit) {
793
793
  function callCost(call) {
794
794
  if (call.usageSupport === "unsupported_token_shape")
795
795
  return undefined;
796
- if (!canPriceTokenUsageAtScope(call.model, call.usage, call.usageScope === "turn" ? "request" : "aggregate"))
796
+ if (!canPriceTokenUsageAtScope(call.model, call.usage, call.usageScope === "turn" ? "request" : "aggregate", call.maxRequestPromptTokens))
797
797
  return undefined;
798
- return estimateTokenCostUsd(call.model, call.usage);
798
+ // Tier must come from the largest single request, exactly as the report's
799
+ // aggregation does — otherwise the statusline and the receipt disagree on
800
+ // the same session (a cache-heavy Codex session voided here while the
801
+ // receipt prices it, or priced here at 2x).
802
+ return estimateTokenCostsUsd(call.model, [call.usage], [call.maxRequestPromptTokens]);
799
803
  }
800
804
  function inputSideTokens(call) {
801
805
  return call.usage.inputTokens +
@@ -49,6 +49,16 @@ export type LocalAgentCall = {
49
49
  latestTurnUsage?: LocalAgentTurnUsage;
50
50
  /** Whether `usage` is one model turn or the session's cumulative financial total. */
51
51
  usageScope?: "turn" | "session_cumulative";
52
+ /**
53
+ * Largest single-request prompt (effective input, cache reads included)
54
+ * observed within a `session_cumulative` `usage`. Tiered per-request pricing
55
+ * is selected per request, never from a cumulative sum: a cache-heavy session
56
+ * routinely clears a per-request tier threshold in aggregate while no single
57
+ * request did. This evidence lets pricing keep such a total on the base tier
58
+ * (exact) instead of failing closed to "missing". Absent for turn-scoped
59
+ * calls, which are already a single request.
60
+ */
61
+ maxRequestPromptTokens?: number;
52
62
  /**
53
63
  * Whether the transcript exposed the input/output components required for
54
64
  * pricing. A total-only snapshot is still usage evidence, but pricing it as
@@ -374,7 +384,7 @@ export type LocalAgentStreamCheckpointAdapter = {
374
384
  writeStreamCheckpoint: (agent: LocalAgentFormatId, pathHash: string, checkpoint: Readonly<LocalAgentStreamCheckpointRecord>) => Promise<void>;
375
385
  deleteStreamCheckpoint: (agent: LocalAgentFormatId, pathHash: string) => Promise<void>;
376
386
  };
377
- export declare const localAgentFinancialParserVersion = 1;
387
+ export declare const localAgentFinancialParserVersion = 3;
378
388
  /**
379
389
  * Qualitative parser contract version. Bumped to 2 with the checkpointed
380
390
  * streaming path (A4b): entries and checkpoints written by the pre-streaming
@@ -385,9 +395,15 @@ export declare const localAgentFinancialParserVersion = 1;
385
395
  * cross-file completion evidence (`subagentCompletions`, from both Task tool
386
396
  * results and background task-notifications): entries persisted by the
387
397
  * collapsing parser must re-parse rather than silently keep merging subagent
388
- * runs into their parent session.
398
+ * runs into their parent session. Bumped to 5 when Codex reducer checkpoints
399
+ * gained `maxTurnPromptTokens` (largest single request): a checkpoint written
400
+ * by v4 lacks the running per-request maximum, so restoring it could under-read
401
+ * the tier evidence for a cumulative session that crossed the boundary — a
402
+ * version mismatch discards it and re-parses from scratch instead. Bumped to 6
403
+ * with the user-fork inherited-baseline fix: v5 entries and checkpoints treated
404
+ * a fork's replayed parent history as the child's own usage and activity.
389
405
  */
390
- export declare const localAgentQualitativeParserVersion = 4;
406
+ export declare const localAgentQualitativeParserVersion = 6;
391
407
  /**
392
408
  * Conservative launch defaults for action-capable qualitative evidence.
393
409
  * Callers must still inspect `qualitativeCoverage` before deriving a finding:
@@ -22,7 +22,15 @@ export function latestObservedWorkingDirectory(calls) {
22
22
  .sort((left, right) => right.timestamp.localeCompare(left.timestamp))[0]
23
23
  ?.workingDirectory;
24
24
  }
25
- export const localAgentFinancialParserVersion = 1;
25
+ // Bumped to 2 when session-cumulative Codex calls gained `maxRequestPromptTokens`
26
+ // (largest single request in the session). Financial caches written by v1 lack
27
+ // this per-request tier evidence, so a >272K-cumulative Codex session would
28
+ // stay voided to "missing" on reuse; a version mismatch re-parses instead.
29
+ // Bumped to 3 when user forks (`forked_from_id`) started resetting their
30
+ // inherited baseline: v2 entries priced a fork's replayed parent history as
31
+ // the child's own usage, so those cached amounts are overstated and must not
32
+ // be reused.
33
+ export const localAgentFinancialParserVersion = 3;
26
34
  /**
27
35
  * Qualitative parser contract version. Bumped to 2 with the checkpointed
28
36
  * streaming path (A4b): entries and checkpoints written by the pre-streaming
@@ -33,9 +41,15 @@ export const localAgentFinancialParserVersion = 1;
33
41
  * cross-file completion evidence (`subagentCompletions`, from both Task tool
34
42
  * results and background task-notifications): entries persisted by the
35
43
  * collapsing parser must re-parse rather than silently keep merging subagent
36
- * runs into their parent session.
44
+ * runs into their parent session. Bumped to 5 when Codex reducer checkpoints
45
+ * gained `maxTurnPromptTokens` (largest single request): a checkpoint written
46
+ * by v4 lacks the running per-request maximum, so restoring it could under-read
47
+ * the tier evidence for a cumulative session that crossed the boundary — a
48
+ * version mismatch discards it and re-parses from scratch instead. Bumped to 6
49
+ * with the user-fork inherited-baseline fix: v5 entries and checkpoints treated
50
+ * a fork's replayed parent history as the child's own usage and activity.
37
51
  */
38
- export const localAgentQualitativeParserVersion = 4;
52
+ export const localAgentQualitativeParserVersion = 6;
39
53
  /**
40
54
  * Conservative launch defaults for action-capable qualitative evidence.
41
55
  * Callers must still inspect `qualitativeCoverage` before deriving a finding:
@@ -384,6 +398,42 @@ function parseCodexTurnUsage(value) {
384
398
  }
385
399
  };
386
400
  }
401
+ /**
402
+ * The tier-relevant prompt size of one Codex `last_token_usage` turn: its full
403
+ * request input (Codex `input_tokens` already includes cached input), which is
404
+ * exactly `effectivePromptTokens` of the parsed turn usage. Returned only when
405
+ * the field parses to a non-negative number, so an unreadable turn never lowers
406
+ * a running maximum. Used to prove whether any single request in a cumulative
407
+ * session crossed a per-request tier threshold.
408
+ */
409
+ function codexTurnPromptTokens(turn) {
410
+ const input = tokenComponentOf(turn.input_tokens);
411
+ return input === undefined ? undefined : input;
412
+ }
413
+ /**
414
+ * Whether a Codex rollout replays another session's history before its own
415
+ * work, so the cumulative counter at its first real task is an inherited
416
+ * baseline rather than this session's usage.
417
+ *
418
+ * TWO kinds of rollout do this, and only the first was recognized before:
419
+ * - subagent rollouts (`thread_source: "subagent"`, or a `source.subagent`);
420
+ * - USER FORKS (`forked_from_id`), which carry `thread_source: "user"` and
421
+ * replay the parent transcript verbatim. Observed: a forked rollout whose
422
+ * first 9,051 of 9,442 token_count events are byte-identical copies of the
423
+ * parent's, all with earlier parent timestamps, carrying 1,204,265,192 of
424
+ * its 1,256,637,395 final cumulative input tokens (95.9%) — parent usage
425
+ * that was being billed again under the child, and again down each fork
426
+ * chain.
427
+ *
428
+ * This is deliberately NOT folded into `isSubagent`: that flag also drives
429
+ * activity attribution and checkpoint identity, where a user fork is a normal
430
+ * user session and must not be relabelled a subagent.
431
+ */
432
+ function codexHasInheritedHistory(payload) {
433
+ return stringOf(payload.thread_source) === "subagent" ||
434
+ isRecord(payload.source) && "subagent" in payload.source ||
435
+ stringOf(payload.forked_from_id) !== undefined;
436
+ }
387
437
  /** Parse one Claude Code transcript (JSONL). Exported for tests. */
388
438
  export function parseClaudeCodeTranscript(content, filePath = "", sinceMs, onDiagnostic) {
389
439
  const calls = [];
@@ -634,6 +684,7 @@ function createCodexRolloutParserState() {
634
684
  fileCounts: new Map(),
635
685
  toolCallCount: 0,
636
686
  isSubagent: false,
687
+ hasInheritedHistory: false,
637
688
  malformedLines: 0
638
689
  };
639
690
  }
@@ -666,6 +717,7 @@ function consumeCodexRolloutLine(state, line, onEntry) {
666
717
  state.rootStartedAtMs = timestampMilliseconds(payload.timestamp ?? entry.timestamp);
667
718
  state.isSubagent = stringOf(payload.thread_source) === "subagent" ||
668
719
  isRecord(payload.source) && "subagent" in payload.source;
720
+ state.hasInheritedHistory = codexHasInheritedHistory(payload);
669
721
  state.parentSessionId = stringOf(payload.parent_thread_id);
670
722
  }
671
723
  if (entry.type === "turn_context" && payload) {
@@ -677,7 +729,7 @@ function consumeCodexRolloutLine(state, line, onEntry) {
677
729
  state.rootCwd = stringOf(payload.cwd);
678
730
  }
679
731
  }
680
- if (state.isSubagent &&
732
+ if (state.hasInheritedHistory &&
681
733
  !state.rootTaskStarted &&
682
734
  payload?.type === "task_started" &&
683
735
  isRootSpecificTaskStart(payload.started_at, state.rootStartedAtMs)) {
@@ -688,6 +740,11 @@ function consumeCodexRolloutLine(state, line, onEntry) {
688
740
  // prompts/files cannot become the child's focus. Restored checkpoint
689
741
  // evidence predating the boundary is parent history too and resets with
690
742
  // it (the raw session cwd deliberately survives, exactly as rootCwd).
743
+ //
744
+ // Replayed task_started events keep the PARENT's original started_at, so
745
+ // `isRootSpecificTaskStart` rejects them and the first accepted task is
746
+ // this session's own — which is why the boundary lands exactly at the end
747
+ // of the replay.
691
748
  state.inheritedUsageBaseline = state.lastTotal;
692
749
  state.lastTotal = undefined;
693
750
  state.rootTaskStarted = true;
@@ -699,10 +756,11 @@ function consumeCodexRolloutLine(state, line, onEntry) {
699
756
  state.toolCallCount = 0;
700
757
  state.model = undefined;
701
758
  state.lastTurn = undefined;
759
+ state.maxTurnPromptTokens = undefined;
702
760
  state.lastRateLimits = undefined;
703
761
  state.lastActivityAt = toIso(stringOf(entry.timestamp)) ?? state.startedAt;
704
762
  }
705
- if (payload?.type === "task_started" && (!state.isSubagent || state.rootTaskStarted)) {
763
+ if (payload?.type === "task_started" && (!state.hasInheritedHistory || state.rootTaskStarted)) {
706
764
  state.pendingTaskTurnId = stringOf(payload.turn_id);
707
765
  state.completedTask = undefined;
708
766
  }
@@ -757,6 +815,10 @@ function consumeCodexRolloutLine(state, line, onEntry) {
757
815
  if (turn) {
758
816
  state.lastTurn = turn;
759
817
  state.lastActivityAt = eventTimestamp;
818
+ const turnPrompt = codexTurnPromptTokens(turn);
819
+ if (turnPrompt !== undefined) {
820
+ state.maxTurnPromptTokens = Math.max(state.maxTurnPromptTokens ?? 0, turnPrompt);
821
+ }
760
822
  }
761
823
  const rateLimits = parseCodexRateLimits(payload.rate_limits, eventTimestamp);
762
824
  if (rateLimits) {
@@ -774,7 +836,7 @@ function finishCodexRolloutParse(state, onDiagnostic) {
774
836
  if (state.malformedLines > 0) {
775
837
  onDiagnostic?.({ code: "malformed_jsonl", count: state.malformedLines });
776
838
  }
777
- if (!state.lastTotal || state.isSubagent && !state.rootTaskStarted)
839
+ if (!state.lastTotal || state.hasInheritedHistory && !state.rootTaskStarted)
778
840
  return [];
779
841
  const parsedUsage = parseCodexCumulativeUsage(state.lastTotal, state.inheritedUsageBaseline);
780
842
  const parsedTurn = state.lastTurn
@@ -834,6 +896,9 @@ function finishCodexRolloutParse(state, onDiagnostic) {
834
896
  : {}),
835
897
  usageScope: "session_cumulative",
836
898
  usageSupport,
899
+ ...(usageSupport === "complete" && state.maxTurnPromptTokens !== undefined
900
+ ? { maxRequestPromptTokens: state.maxTurnPromptTokens }
901
+ : {}),
837
902
  ...(parsedUsage.reportedTotalTokens !== undefined
838
903
  ? { reportedTotalTokens: parsedUsage.reportedTotalTokens }
839
904
  : {}),
@@ -1999,6 +2064,7 @@ function serializeCodexReducerState(state) {
1999
2064
  ...(state.rootStartedAtMs !== undefined ? { rootStartedAtMs: state.rootStartedAtMs } : {}),
2000
2065
  rootTaskStarted: state.rootTaskStarted,
2001
2066
  isSubagent: state.isSubagent,
2067
+ hasInheritedHistory: state.hasInheritedHistory,
2002
2068
  ...(state.parentSessionId !== undefined ? { parentSessionId: state.parentSessionId } : {}),
2003
2069
  ...(state.pendingTaskTurnId !== undefined ? { pendingTaskTurnId: state.pendingTaskTurnId } : {}),
2004
2070
  ...(state.completedTask !== undefined ? { completedTask: state.completedTask } : {}),
@@ -2006,6 +2072,9 @@ function serializeCodexReducerState(state) {
2006
2072
  ...(state.lastActivityAt !== undefined ? { lastActivityAt: state.lastActivityAt } : {}),
2007
2073
  ...(state.lastTotal !== undefined ? { lastTotal: state.lastTotal } : {}),
2008
2074
  ...(state.lastTurn !== undefined ? { lastTurn: state.lastTurn } : {}),
2075
+ ...(state.maxTurnPromptTokens !== undefined
2076
+ ? { maxTurnPromptTokens: state.maxTurnPromptTokens }
2077
+ : {}),
2009
2078
  ...(state.inheritedUsageBaseline !== undefined
2010
2079
  ? { inheritedUsageBaseline: state.inheritedUsageBaseline }
2011
2080
  : {}),
@@ -2050,6 +2119,8 @@ function isPersistedCodexReducerState(value) {
2050
2119
  return typeof value.rootSessionMetaSeen === "boolean" &&
2051
2120
  typeof value.rootTaskStarted === "boolean" &&
2052
2121
  typeof value.isSubagent === "boolean" &&
2122
+ (value.hasInheritedHistory === undefined ||
2123
+ typeof value.hasInheritedHistory === "boolean") &&
2053
2124
  optionalString(value.model) &&
2054
2125
  optionalString(value.sessionId) &&
2055
2126
  optionalString(value.sourceVersion) &&
@@ -2062,6 +2133,7 @@ function isPersistedCodexReducerState(value) {
2062
2133
  optionalString(value.lastActivityAt) &&
2063
2134
  optionalRecord(value.lastTotal) &&
2064
2135
  optionalRecord(value.lastTurn) &&
2136
+ optionalFinite(value.maxTurnPromptTokens) &&
2065
2137
  optionalRecord(value.inheritedUsageBaseline) &&
2066
2138
  validRateLimits &&
2067
2139
  validRootCwd &&
@@ -2084,6 +2156,10 @@ function restoreCodexReducerState(persisted) {
2084
2156
  state.rootStartedAtMs = persisted.rootStartedAtMs;
2085
2157
  state.rootTaskStarted = persisted.rootTaskStarted;
2086
2158
  state.isSubagent = persisted.isSubagent;
2159
+ // Pre-fork-fix checkpoints carry no flag; fall back to the subagent bit so a
2160
+ // restored subagent keeps its boundary (a restored user fork re-parses, its
2161
+ // parser version having changed).
2162
+ state.hasInheritedHistory = persisted.hasInheritedHistory ?? persisted.isSubagent;
2087
2163
  state.parentSessionId = persisted.parentSessionId;
2088
2164
  state.pendingTaskTurnId = persisted.pendingTaskTurnId;
2089
2165
  state.completedTask = persisted.completedTask;
@@ -2091,6 +2167,7 @@ function restoreCodexReducerState(persisted) {
2091
2167
  state.lastActivityAt = persisted.lastActivityAt;
2092
2168
  state.lastTotal = persisted.lastTotal;
2093
2169
  state.lastTurn = persisted.lastTurn;
2170
+ state.maxTurnPromptTokens = persisted.maxTurnPromptTokens;
2094
2171
  state.inheritedUsageBaseline = persisted.inheritedUsageBaseline;
2095
2172
  state.lastRateLimits = persisted.lastRateLimits;
2096
2173
  state.restoredRootCwd = persisted.rootCwd;
@@ -2726,8 +2803,11 @@ async function readCodexFinancialFile(file) {
2726
2803
  const rootPayload = rootEntry?.type === "session_meta" && isRecord(rootEntry.payload)
2727
2804
  ? rootEntry.payload
2728
2805
  : undefined;
2729
- const isSubagent = Boolean(rootPayload) && (stringOf(rootPayload?.thread_source) === "subagent" ||
2730
- isRecord(rootPayload?.source) && "subagent" in rootPayload.source);
2806
+ // A rollout that replays another session's history must be read WHOLE:
2807
+ // its inherited-baseline boundary sits mid-file, and a proof-complete tail
2808
+ // would never reach it. User forks need this exactly as subagents do.
2809
+ const hasInheritedHistory = rootPayload !== undefined &&
2810
+ codexHasInheritedHistory(rootPayload);
2731
2811
  const entriesReverse = [];
2732
2812
  let malformedLines = 0;
2733
2813
  let prefilteredLines = 0;
@@ -2738,7 +2818,7 @@ async function readCodexFinancialFile(file) {
2738
2818
  let suffixPartsReverse = [];
2739
2819
  let stoppedEarly = false;
2740
2820
  let bytesSkipped = 0;
2741
- const proof = createCodexReverseProof(isSubagent, !rootPayload);
2821
+ const proof = createCodexReverseProof(hasInheritedHistory, !rootPayload);
2742
2822
  while (position > 0 && !stoppedEarly) {
2743
2823
  const chunkStart = Math.max(0, position - FINANCIAL_REVERSE_CHUNK_BYTES);
2744
2824
  const length = position - chunkStart;
@@ -2979,9 +3059,9 @@ function skipJsonWhitespace(input, start) {
2979
3059
  index += 1;
2980
3060
  return index;
2981
3061
  }
2982
- function createCodexReverseProof(isSubagent, forceFullScan) {
3062
+ function createCodexReverseProof(hasInheritedHistory, forceFullScan) {
2983
3063
  return {
2984
- isSubagent,
3064
+ hasInheritedHistory,
2985
3065
  forceFullScan,
2986
3066
  totalSeen: false,
2987
3067
  turnSeen: false,
@@ -2990,7 +3070,7 @@ function createCodexReverseProof(isSubagent, forceFullScan) {
2990
3070
  };
2991
3071
  }
2992
3072
  function observeCodexReverseProof(proof, entry) {
2993
- if (proof.forceFullScan || proof.isSubagent)
3073
+ if (proof.forceFullScan || proof.hasInheritedHistory)
2994
3074
  return false;
2995
3075
  const payload = isRecord(entry.payload) ? entry.payload : undefined;
2996
3076
  const info = payload?.type === "token_count" && isRecord(payload.info)
@@ -3007,7 +3087,7 @@ function observeCodexReverseProof(proof, entry) {
3007
3087
  if (entry.type === "turn_context" && stringOf(payload?.model)) {
3008
3088
  proof.modelSeen = true;
3009
3089
  }
3010
- return !proof.isSubagent &&
3090
+ return !proof.hasInheritedHistory &&
3011
3091
  proof.totalSeen &&
3012
3092
  proof.turnSeen &&
3013
3093
  proof.rateLimitsSeen &&
@@ -3112,7 +3192,8 @@ function createCodexFinancialStreamState() {
3112
3192
  return {
3113
3193
  rootSessionMetaSeen: false,
3114
3194
  rootTaskStarted: false,
3115
- isSubagent: false
3195
+ isSubagent: false,
3196
+ hasInheritedHistory: false
3116
3197
  };
3117
3198
  }
3118
3199
  function consumeCodexFinancialEntry(state, entry) {
@@ -3126,12 +3207,13 @@ function consumeCodexFinancialEntry(state, entry) {
3126
3207
  state.rootStartedAtMs = timestampMilliseconds(payload.timestamp ?? entry.timestamp);
3127
3208
  state.isSubagent = stringOf(payload.thread_source) === "subagent" ||
3128
3209
  isRecord(payload.source) && "subagent" in payload.source;
3210
+ state.hasInheritedHistory = codexHasInheritedHistory(payload);
3129
3211
  }
3130
3212
  if (entry.type === "turn_context" && payload) {
3131
3213
  state.model = stringOf(payload.model) ?? state.model;
3132
3214
  state.rootCwd ??= stringOf(payload.cwd);
3133
3215
  }
3134
- if (state.isSubagent &&
3216
+ if (state.hasInheritedHistory &&
3135
3217
  !state.rootTaskStarted &&
3136
3218
  payload?.type === "task_started" &&
3137
3219
  isRootSpecificTaskStart(payload.started_at, state.rootStartedAtMs)) {
@@ -3140,6 +3222,7 @@ function consumeCodexFinancialEntry(state, entry) {
3140
3222
  state.rootTaskStarted = true;
3141
3223
  state.model = undefined;
3142
3224
  state.lastTurn = undefined;
3225
+ state.maxTurnPromptTokens = undefined;
3143
3226
  state.lastRateLimits = undefined;
3144
3227
  state.lastActivityAt = toIso(stringOf(entry.timestamp)) ?? state.startedAt;
3145
3228
  }
@@ -3162,13 +3245,17 @@ function consumeCodexFinancialEntry(state, entry) {
3162
3245
  if (turn) {
3163
3246
  state.lastTurn = turn;
3164
3247
  state.lastActivityAt = eventTimestamp;
3248
+ const turnPrompt = codexTurnPromptTokens(turn);
3249
+ if (turnPrompt !== undefined) {
3250
+ state.maxTurnPromptTokens = Math.max(state.maxTurnPromptTokens ?? 0, turnPrompt);
3251
+ }
3165
3252
  }
3166
3253
  const rateLimits = parseCodexRateLimits(payload.rate_limits, eventTimestamp);
3167
3254
  if (rateLimits)
3168
3255
  state.lastRateLimits = rateLimits;
3169
3256
  }
3170
3257
  function finishCodexFinancialStream(state, onDiagnostic) {
3171
- if (!state.lastTotal || state.isSubagent && !state.rootTaskStarted)
3258
+ if (!state.lastTotal || state.hasInheritedHistory && !state.rootTaskStarted)
3172
3259
  return undefined;
3173
3260
  const parsedUsage = parseCodexCumulativeUsage(state.lastTotal, state.inheritedUsageBaseline);
3174
3261
  const parsedTurn = state.lastTurn
@@ -3196,6 +3283,9 @@ function finishCodexFinancialStream(state, onDiagnostic) {
3196
3283
  : {}),
3197
3284
  usageScope: "session_cumulative",
3198
3285
  usageSupport,
3286
+ ...(usageSupport === "complete" && state.maxTurnPromptTokens !== undefined
3287
+ ? { maxRequestPromptTokens: state.maxTurnPromptTokens }
3288
+ : {}),
3199
3289
  ...(parsedUsage.reportedTotalTokens !== undefined
3200
3290
  ? { reportedTotalTokens: parsedUsage.reportedTotalTokens }
3201
3291
  : {}),
@@ -3234,10 +3324,14 @@ export function aggregateCallsForFormats(calls, descriptors) {
3234
3324
  const usageSupported = groupCalls.every((call) => call.usageSupport !== "unsupported_token_shape");
3235
3325
  const sourceVersions = [...new Set(groupCalls.flatMap((call) => call.sourceVersion ? [call.sourceVersion] : []))].sort().slice(0, 8);
3236
3326
  const tieredPricingEvidenceSupported = !usesPromptTieredPricing(model) ||
3237
- groupCalls.every((call) => canPriceTokenUsageAtScope(model, call.usage, call.usageScope === "turn" ? "request" : "aggregate") && (agent !== "gemini-cli" || hasCompleteGeminiPromptEvidence(call)));
3327
+ groupCalls.every((call) => canPriceTokenUsageAtScope(model, call.usage, call.usageScope === "turn" ? "request" : "aggregate", call.maxRequestPromptTokens) && (agent !== "gemini-cli" || hasCompleteGeminiPromptEvidence(call)));
3238
3328
  const amountUsd = usageSupported && tieredPricingEvidenceSupported && format
3239
3329
  ? usesPromptTieredPricing(model)
3240
- ? estimateTokenCostsUsd(model, groupCalls.map((call) => call.usage))
3330
+ ? estimateTokenCostsUsd(model, groupCalls.map((call) => call.usage),
3331
+ // Fix each cumulative slice's tier from its largest single request
3332
+ // rather than its cache-inflated sum. Turn-scoped slices carry no
3333
+ // such evidence and keep selecting their tier from their own prompt.
3334
+ groupCalls.map((call) => call.maxRequestPromptTokens))
3241
3335
  : estimateTokenCostUsd(model, usage)
3242
3336
  : undefined;
3243
3337
  const priced = usageSupported && typeof amountUsd === "number";
@@ -50,8 +50,17 @@ export declare function estimateTokenCostUsd(model: string, usage: TokenUsage):
50
50
  * models whose entire request moves to a higher rate above a prompt-size
51
51
  * threshold; pricing a daily token sum would incorrectly treat many small
52
52
  * requests as one large request.
53
+ *
54
+ * `tierPromptTokens[i]`, when provided, fixes the tier of `usages[i]` from
55
+ * request-level evidence instead of the slice's own prompt total. A
56
+ * session-cumulative slice is a sum of many requests whose prompt total
57
+ * routinely clears a per-request threshold on cache reads alone, even though no
58
+ * single request did; supplying the largest single request's prompt keeps such
59
+ * a slice on the base tier (and pricing it there is exact, since the base rate
60
+ * distributes over the sum). Omitting the array preserves single-request
61
+ * behaviour: each slice's own prompt selects its tier.
53
62
  */
54
- export declare function estimateTokenCostsUsd(model: string, usages: readonly TokenUsage[]): number | undefined;
63
+ export declare function estimateTokenCostsUsd(model: string, usages: readonly TokenUsage[], tierPromptTokens?: readonly (number | undefined)[]): number | undefined;
55
64
  /** Whether this model's rate selection depends on each request's prompt size. */
56
65
  export declare function usesPromptTieredPricing(model: string): boolean;
57
66
  /** Prompt-size threshold for tiered request pricing, when one is published. */
@@ -60,9 +69,13 @@ export declare function promptTierThreshold(model: string): number | undefined;
60
69
  * Tiered prices are selected per request, never from a multi-request sum.
61
70
  * An aggregate is still unambiguous when its entire non-negative prompt-side
62
71
  * total is at or below the threshold; then no constituent request can have
63
- * crossed it. Larger aggregates fail closed until request-level evidence is
64
- * available.
72
+ * crossed it. It is also unambiguous when request-level evidence
73
+ * (`maxRequestPromptTokens`, the largest single request the aggregate contains)
74
+ * proves that no constituent request crossed the threshold: every request was
75
+ * base-tier, so the whole sum is base-tier and prices exactly at the base rate.
76
+ * Larger aggregates without such evidence fail closed to keep an unpriceable
77
+ * total honestly "missing" rather than guessing a tier.
65
78
  */
66
- export declare function canPriceTokenUsageAtScope(model: string, usage: TokenUsage, scope: "request" | "aggregate"): boolean;
79
+ export declare function canPriceTokenUsageAtScope(model: string, usage: TokenUsage, scope: "request" | "aggregate", maxRequestPromptTokens?: number): boolean;
67
80
  export {};
68
81
  //# sourceMappingURL=modelPricing.d.ts.map
@@ -20,21 +20,38 @@ const pricingRules = [
20
20
  { match: /^claude-haiku-4/i, inputPerM: 1, outputPerM: 5 },
21
21
  { match: /^claude-3-7-sonnet|^claude-3-5-sonnet/i, inputPerM: 3, outputPerM: 15 },
22
22
  { match: /^claude-3-5-haiku/i, inputPerM: 0.8, outputPerM: 4 },
23
- // OpenAI (newer and more specific families must precede the GPT-5 fallback)
23
+ // OpenAI (newer and more specific families must precede the GPT-5 fallback).
24
+ // Rates from developers.openai.com/api/docs/pricing cross-checked against each
25
+ // model's own doc page, both fetched 2026-08-25.
26
+ //
27
+ // GPT-5.6 ships exactly three API models — sol, terra, luna
28
+ // (developers.openai.com/api/docs/models, 2026-08-25). Each 5.6/5.5/5.4 rule
29
+ // below is END-ANCHORED on purpose: an undocumented or future sibling
30
+ // (gpt-5.6-cyber, gpt-5.5-pro, gpt-5.7-sol) must fall through to
31
+ // honest-unpriced rather than inherit a neighbour's rate. That is the 0.9.4
32
+ // `^kimi-k2` mistake one family up, and it is the expensive direction here —
33
+ // the pre-0.9.6 `^gpt-5.6(?:-sol)?$` rule carried GPT-5.5's numbers, so every
34
+ // gpt-5.6-sol record was overstated by 25% on input and 50% on output.
35
+ //
36
+ // Long context, published identically on all three 5.6 pages plus 5.5/5.4:
37
+ // "Prompts with >272K input tokens are priced at 2x input and 1.5x output for
38
+ // the full request." Cached input scales with the 2x input leg.
24
39
  {
25
- match: /^gpt-5\.6(?:-sol)?$/i,
26
- inputPerM: 5,
27
- outputPerM: 30,
28
- cacheReadPerM: 0.5,
40
+ // developers.openai.com/api/docs/models/gpt-5.6-sol, 2026-08-25
41
+ match: /^gpt-5\.6-sol$/i,
42
+ inputPerM: 4,
43
+ outputPerM: 20,
44
+ cacheReadPerM: 0.4,
29
45
  abovePromptTokens: {
30
46
  threshold: 272_000,
31
- inputPerM: 10,
32
- outputPerM: 45,
33
- cacheReadPerM: 1
47
+ inputPerM: 8,
48
+ outputPerM: 30,
49
+ cacheReadPerM: 0.8
34
50
  }
35
51
  },
36
52
  {
37
- match: /^gpt-5\.6-terra/i,
53
+ // developers.openai.com/api/docs/models/gpt-5.6-terra, 2026-08-25
54
+ match: /^gpt-5\.6-terra$/i,
38
55
  inputPerM: 2,
39
56
  outputPerM: 12,
40
57
  cacheReadPerM: 0.2,
@@ -46,7 +63,8 @@ const pricingRules = [
46
63
  }
47
64
  },
48
65
  {
49
- match: /^gpt-5\.6-luna/i,
66
+ // developers.openai.com/api/docs/models/gpt-5.6-luna, 2026-08-25
67
+ match: /^gpt-5\.6-luna$/i,
50
68
  inputPerM: 0.2,
51
69
  outputPerM: 1.2,
52
70
  cacheReadPerM: 0.02,
@@ -57,15 +75,50 @@ const pricingRules = [
57
75
  cacheReadPerM: 0.04
58
76
  }
59
77
  },
60
- { match: /^gpt-5\.5(?:-codex)?/i, inputPerM: 5, outputPerM: 30, cacheReadPerM: 0.5 },
78
+ {
79
+ // developers.openai.com/api/docs/models/gpt-5.5, 2026-08-25
80
+ match: /^gpt-5\.5$/i,
81
+ inputPerM: 5,
82
+ outputPerM: 30,
83
+ cacheReadPerM: 0.5,
84
+ abovePromptTokens: {
85
+ threshold: 272_000,
86
+ inputPerM: 10,
87
+ outputPerM: 45,
88
+ cacheReadPerM: 1
89
+ }
90
+ },
91
+ // gpt-5.5-codex bills at the 5.5 base rate but is absent from the published
92
+ // long-context list, so it deliberately carries no >272K tier.
93
+ { match: /^gpt-5\.5-codex$/i, inputPerM: 5, outputPerM: 30, cacheReadPerM: 0.5 },
61
94
  { match: /^gpt-5\.4-mini/i, inputPerM: 0.75, outputPerM: 4.5, cacheReadPerM: 0.075 },
62
95
  { match: /^gpt-5\.4-nano/i, inputPerM: 0.2, outputPerM: 1.25, cacheReadPerM: 0.02 },
63
- { match: /^gpt-5\.4/i, inputPerM: 2.5, outputPerM: 15, cacheReadPerM: 0.25 },
96
+ {
97
+ // developers.openai.com/api/docs/models/gpt-5.4, 2026-08-25
98
+ match: /^gpt-5\.4$/i,
99
+ inputPerM: 2.5,
100
+ outputPerM: 15,
101
+ cacheReadPerM: 0.25,
102
+ abovePromptTokens: {
103
+ threshold: 272_000,
104
+ inputPerM: 5,
105
+ outputPerM: 22.5,
106
+ cacheReadPerM: 0.5
107
+ }
108
+ },
64
109
  { match: /^gpt-5\.3-codex/i, inputPerM: 1.75, outputPerM: 14, cacheReadPerM: 0.175 },
65
110
  { match: /^gpt-5\.2(?:-codex)?/i, inputPerM: 1.75, outputPerM: 14, cacheReadPerM: 0.175 },
66
111
  { match: /^gpt-5(?:\.1)?-codex/i, inputPerM: 1.25, outputPerM: 10, cacheReadPerM: 0.125 },
67
112
  { match: /^gpt-5(?:\.1)?-mini/i, inputPerM: 0.25, outputPerM: 2, cacheReadPerM: 0.025 },
68
- { match: /^gpt-5/i, inputPerM: 1.25, outputPerM: 10, cacheReadPerM: 0.125 },
113
+ // developers.openai.com/api/docs/models/gpt-5-nano, 2026-08-25. Before 0.9.6
114
+ // this fell through to the ^gpt-5 fallback and billed at $1.25/$10 — 25x the
115
+ // real rate in both directions. No published long-context tier.
116
+ { match: /^gpt-5-nano/i, inputPerM: 0.05, outputPerM: 0.4, cacheReadPerM: 0.005 },
117
+ // GPT-5 base and its dash-suffixed snapshots only. The `-|$` boundary stops
118
+ // this fallback from swallowing dot-minor families it knows nothing about:
119
+ // gpt-5.7-*, gpt-5.6-cyber and any future gpt-5.6-<variant> now return
120
+ // undefined -> "missing" instead of silently billing at GPT-5's $1.25/$10.
121
+ { match: /^gpt-5(?:-|$)/i, inputPerM: 1.25, outputPerM: 10, cacheReadPerM: 0.125 },
69
122
  { match: /^gpt-4\.1-nano/i, inputPerM: 0.1, outputPerM: 0.4 },
70
123
  { match: /^gpt-4\.1-mini/i, inputPerM: 0.4, outputPerM: 1.6 },
71
124
  { match: /^gpt-4\.1/i, inputPerM: 2, outputPerM: 8, cacheReadPerM: 0.5 },
@@ -122,6 +175,23 @@ const pricingRules = [
122
175
  // - deepseek-v4-* (api-docs.deepseek.com/quick_start/pricing): published
123
176
  // rates are time-of-day (off-peak = half price, up to 2x swing), so any
124
177
  // flat number here would be dishonest; needs timestamp-aware pricing.
178
+ //
179
+ // Deliberate deferrals (2026-08-25 OpenAI review), same honest path:
180
+ // - gpt-5.6-cyber / gpt-5.5-cyber ($12.50/$1.25/$75 on the pricing page):
181
+ // the two canonical sources disagree on whether the >272K tier applies —
182
+ // the pricing page omits cyber from its long-context list while
183
+ // developers.openai.com/api/docs/models/gpt-5.6-cyber states the 2x/1.5x
184
+ // rule does apply. Access is gated behind the Daybreak program, so the
185
+ // cost of leaving it unpriced is near zero and a coin-flip on the tier
186
+ // would be a real number that is wrong on long requests.
187
+ // - gpt-5.5-pro / gpt-5.4-pro: listed as long-context-capable but no
188
+ // per-model rate is published on either canonical source.
189
+ // - bare gpt-5.1 / gpt-5.3: the pricing page quotes the 5/5.1/5.2 group as a
190
+ // RANGE ($1.25-$1.75) and neither has a resolved per-model figure. Their
191
+ // -codex and -mini variants keep their own verified rules above.
192
+ // - gpt-5.6-codex / gpt-5.5-mini: NOT OpenAI model ids (both 404 on the model
193
+ // docs and are absent from developers.openai.com/api/docs/models). They
194
+ // appear only in this repo's fixtures and sample CSVs.
125
195
  ];
126
196
  export function findPricingRule(model) {
127
197
  return pricingRules.find((rule) => rule.match.test(model));
@@ -139,11 +209,20 @@ export function estimateTokenCostUsd(model, usage) {
139
209
  * models whose entire request moves to a higher rate above a prompt-size
140
210
  * threshold; pricing a daily token sum would incorrectly treat many small
141
211
  * requests as one large request.
212
+ *
213
+ * `tierPromptTokens[i]`, when provided, fixes the tier of `usages[i]` from
214
+ * request-level evidence instead of the slice's own prompt total. A
215
+ * session-cumulative slice is a sum of many requests whose prompt total
216
+ * routinely clears a per-request threshold on cache reads alone, even though no
217
+ * single request did; supplying the largest single request's prompt keeps such
218
+ * a slice on the base tier (and pricing it there is exact, since the base rate
219
+ * distributes over the sum). Omitting the array preserves single-request
220
+ * behaviour: each slice's own prompt selects its tier.
142
221
  */
143
- export function estimateTokenCostsUsd(model, usages) {
222
+ export function estimateTokenCostsUsd(model, usages, tierPromptTokens) {
144
223
  let total = 0;
145
- for (const usage of usages) {
146
- const usd = rawTokenCostUsd(model, usage);
224
+ for (let index = 0; index < usages.length; index += 1) {
225
+ const usd = rawTokenCostUsd(model, usages[index], tierPromptTokens?.[index]);
147
226
  if (usd === undefined)
148
227
  return undefined;
149
228
  total += usd;
@@ -162,21 +241,34 @@ export function promptTierThreshold(model) {
162
241
  * Tiered prices are selected per request, never from a multi-request sum.
163
242
  * An aggregate is still unambiguous when its entire non-negative prompt-side
164
243
  * total is at or below the threshold; then no constituent request can have
165
- * crossed it. Larger aggregates fail closed until request-level evidence is
166
- * available.
244
+ * crossed it. It is also unambiguous when request-level evidence
245
+ * (`maxRequestPromptTokens`, the largest single request the aggregate contains)
246
+ * proves that no constituent request crossed the threshold: every request was
247
+ * base-tier, so the whole sum is base-tier and prices exactly at the base rate.
248
+ * Larger aggregates without such evidence fail closed to keep an unpriceable
249
+ * total honestly "missing" rather than guessing a tier.
167
250
  */
168
- export function canPriceTokenUsageAtScope(model, usage, scope) {
251
+ export function canPriceTokenUsageAtScope(model, usage, scope, maxRequestPromptTokens) {
169
252
  const threshold = promptTierThreshold(model);
170
253
  if (threshold === undefined || scope === "request")
171
254
  return true;
172
- return effectivePromptTokens(usage) <= threshold;
255
+ if (effectivePromptTokens(usage) <= threshold)
256
+ return true;
257
+ return maxRequestPromptTokens !== undefined && maxRequestPromptTokens <= threshold;
173
258
  }
174
- function rawTokenCostUsd(model, usage) {
259
+ /**
260
+ * @param tierPromptTokens Prompt size used ONLY to select the request tier,
261
+ * when it differs from the priced slice's own prompt total (e.g. a
262
+ * session-cumulative slice whose tier is fixed by its largest single
263
+ * request). Component pricing always uses `usage`; defaults to the slice's
264
+ * own effective prompt so single-request callers are unchanged.
265
+ */
266
+ function rawTokenCostUsd(model, usage, tierPromptTokens) {
175
267
  const rule = findPricingRule(model);
176
268
  if (!rule) {
177
269
  return undefined;
178
270
  }
179
- const promptTokens = effectivePromptTokens(usage);
271
+ const promptTokens = tierPromptTokens ?? effectivePromptTokens(usage);
180
272
  const rates = rule.abovePromptTokens &&
181
273
  promptTokens > rule.abovePromptTokens.threshold
182
274
  ? rule.abovePromptTokens
@@ -7,7 +7,7 @@ export declare const projectIndexStoreLockFileName = ".project-index-v2.lock";
7
7
  export declare const projectIndexMaxDocumentBytes: number;
8
8
  /** Null-window variant plus the newest bounded windows (BLOCKER-2 option i). */
9
9
  export declare const projectIndexMaxWindowedVariants = 4;
10
- export declare const projectIndexFinancialParserVersion = 1;
10
+ export declare const projectIndexFinancialParserVersion = 3;
11
11
  declare const financialKeySchema: z.ZodObject<{
12
12
  schemaVersion: z.ZodLiteral<2>;
13
13
  section: z.ZodLiteral<"financial">;
@@ -18,7 +18,7 @@ declare const financialKeySchema: z.ZodObject<{
18
18
  }>;
19
19
  pathHash: z.ZodString;
20
20
  fileIdentity: z.ZodString;
21
- financialParserVersion: z.ZodLiteral<1>;
21
+ financialParserVersion: z.ZodLiteral<3>;
22
22
  }, z.core.$strict>;
23
23
  /**
24
24
  * Header-pass ownership evidence (A4a consumer). "unknown" is a first-class
@@ -53,7 +53,7 @@ declare const documentSchema: z.ZodObject<{
53
53
  qualitative: z.ZodArray<z.ZodObject<{
54
54
  key: z.ZodObject<{
55
55
  schemaVersion: z.ZodLiteral<1>;
56
- parserVersion: z.ZodLiteral<4>;
56
+ parserVersion: z.ZodLiteral<6>;
57
57
  agent: z.ZodEnum<{
58
58
  "claude-code": "claude-code";
59
59
  codex: "codex";
@@ -89,19 +89,20 @@ declare const documentSchema: z.ZodObject<{
89
89
  contextTokens: z.ZodNumber;
90
90
  totalTokens: z.ZodNumber;
91
91
  source: z.ZodEnum<{
92
- transcript_last_token_usage: "transcript_last_token_usage";
93
92
  assistant_message_usage: "assistant_message_usage";
93
+ transcript_last_token_usage: "transcript_last_token_usage";
94
94
  call_usage: "call_usage";
95
95
  }>;
96
96
  }, z.core.$strict>>;
97
97
  usageScope: z.ZodOptional<z.ZodEnum<{
98
- session_cumulative: "session_cumulative";
99
98
  turn: "turn";
99
+ session_cumulative: "session_cumulative";
100
100
  }>>;
101
101
  usageSupport: z.ZodOptional<z.ZodEnum<{
102
102
  complete: "complete";
103
103
  unsupported_token_shape: "unsupported_token_shape";
104
104
  }>>;
105
+ maxRequestPromptTokens: z.ZodOptional<z.ZodNumber>;
105
106
  reportedTotalTokens: z.ZodOptional<z.ZodNumber>;
106
107
  tokenComponentEvidence: z.ZodOptional<z.ZodObject<{
107
108
  inputTokens: z.ZodLiteral<"observed">;
@@ -150,8 +151,8 @@ declare const documentSchema: z.ZodObject<{
150
151
  total: z.ZodOptional<z.ZodNumber>;
151
152
  cacheAccounting: z.ZodEnum<{
152
153
  unknown: "unknown";
153
- none: "none";
154
154
  included: "included";
155
+ none: "none";
155
156
  }>;
156
157
  }, z.core.$strict>>;
157
158
  usage: z.ZodObject<{
@@ -190,12 +191,11 @@ declare const documentSchema: z.ZodObject<{
190
191
  kind: z.ZodEnum<{
191
192
  file: "file";
192
193
  agent: "agent";
193
- task: "task";
194
194
  project: "project";
195
+ task: "task";
195
196
  automation: "automation";
196
197
  }>;
197
198
  action: z.ZodEnum<{
198
- working: "working";
199
199
  building: "building";
200
200
  refining: "refining";
201
201
  fixing: "fixing";
@@ -205,11 +205,12 @@ declare const documentSchema: z.ZodObject<{
205
205
  configuring: "configuring";
206
206
  publishing: "publishing";
207
207
  running: "running";
208
+ working: "working";
208
209
  }>;
209
210
  source: z.ZodEnum<{
210
- user_prompts: "user_prompts";
211
211
  project: "project";
212
212
  agent_title: "agent_title";
213
+ user_prompts: "user_prompts";
213
214
  file_activity: "file_activity";
214
215
  }>;
215
216
  promptCount: z.ZodNumber;
@@ -261,9 +262,9 @@ declare const documentSchema: z.ZodObject<{
261
262
  }, z.core.$strict>>;
262
263
  diagnostics: z.ZodArray<z.ZodObject<{
263
264
  code: z.ZodEnum<{
265
+ unsupported_token_shape: "unsupported_token_shape";
264
266
  malformed_jsonl: "malformed_jsonl";
265
267
  malformed_session_file: "malformed_session_file";
266
- unsupported_token_shape: "unsupported_token_shape";
267
268
  }>;
268
269
  count: z.ZodNumber;
269
270
  }, z.core.$strict>>;
@@ -280,7 +281,7 @@ declare const documentSchema: z.ZodObject<{
280
281
  }>;
281
282
  pathHash: z.ZodString;
282
283
  fileIdentity: z.ZodString;
283
- financialParserVersion: z.ZodLiteral<1>;
284
+ financialParserVersion: z.ZodLiteral<3>;
284
285
  }, z.core.$strict>;
285
286
  storedAt: z.ZodString;
286
287
  value: z.ZodObject<{
@@ -307,19 +308,20 @@ declare const documentSchema: z.ZodObject<{
307
308
  contextTokens: z.ZodNumber;
308
309
  totalTokens: z.ZodNumber;
309
310
  source: z.ZodEnum<{
310
- transcript_last_token_usage: "transcript_last_token_usage";
311
311
  assistant_message_usage: "assistant_message_usage";
312
+ transcript_last_token_usage: "transcript_last_token_usage";
312
313
  call_usage: "call_usage";
313
314
  }>;
314
315
  }, z.core.$strict>>;
315
316
  usageScope: z.ZodOptional<z.ZodEnum<{
316
- session_cumulative: "session_cumulative";
317
317
  turn: "turn";
318
+ session_cumulative: "session_cumulative";
318
319
  }>>;
319
320
  usageSupport: z.ZodOptional<z.ZodEnum<{
320
321
  complete: "complete";
321
322
  unsupported_token_shape: "unsupported_token_shape";
322
323
  }>>;
324
+ maxRequestPromptTokens: z.ZodOptional<z.ZodNumber>;
323
325
  reportedTotalTokens: z.ZodOptional<z.ZodNumber>;
324
326
  tokenComponentEvidence: z.ZodOptional<z.ZodObject<{
325
327
  inputTokens: z.ZodLiteral<"observed">;
@@ -368,8 +370,8 @@ declare const documentSchema: z.ZodObject<{
368
370
  total: z.ZodOptional<z.ZodNumber>;
369
371
  cacheAccounting: z.ZodEnum<{
370
372
  unknown: "unknown";
371
- none: "none";
372
373
  included: "included";
374
+ none: "none";
373
375
  }>;
374
376
  }, z.core.$strict>>;
375
377
  usage: z.ZodObject<{
@@ -408,12 +410,11 @@ declare const documentSchema: z.ZodObject<{
408
410
  kind: z.ZodEnum<{
409
411
  file: "file";
410
412
  agent: "agent";
411
- task: "task";
412
413
  project: "project";
414
+ task: "task";
413
415
  automation: "automation";
414
416
  }>;
415
417
  action: z.ZodEnum<{
416
- working: "working";
417
418
  building: "building";
418
419
  refining: "refining";
419
420
  fixing: "fixing";
@@ -423,11 +424,12 @@ declare const documentSchema: z.ZodObject<{
423
424
  configuring: "configuring";
424
425
  publishing: "publishing";
425
426
  running: "running";
427
+ working: "working";
426
428
  }>;
427
429
  source: z.ZodEnum<{
428
- user_prompts: "user_prompts";
429
430
  project: "project";
430
431
  agent_title: "agent_title";
432
+ user_prompts: "user_prompts";
431
433
  file_activity: "file_activity";
432
434
  }>;
433
435
  promptCount: z.ZodNumber;
@@ -479,9 +481,9 @@ declare const documentSchema: z.ZodObject<{
479
481
  }, z.core.$strict>>;
480
482
  diagnostics: z.ZodArray<z.ZodObject<{
481
483
  code: z.ZodEnum<{
484
+ unsupported_token_shape: "unsupported_token_shape";
482
485
  malformed_jsonl: "malformed_jsonl";
483
486
  malformed_session_file: "malformed_session_file";
484
- unsupported_token_shape: "unsupported_token_shape";
485
487
  }>;
486
488
  count: z.ZodNumber;
487
489
  }, z.core.$strict>>;
@@ -25,7 +25,7 @@ export declare class QualitativeIndexCacheError extends Error {
25
25
  }
26
26
  declare const keySchema: z.ZodObject<{
27
27
  schemaVersion: z.ZodLiteral<1>;
28
- parserVersion: z.ZodLiteral<4>;
28
+ parserVersion: z.ZodLiteral<6>;
29
29
  agent: z.ZodEnum<{
30
30
  "claude-code": "claude-code";
31
31
  codex: "codex";
@@ -60,19 +60,20 @@ declare const valueSchema: z.ZodObject<{
60
60
  contextTokens: z.ZodNumber;
61
61
  totalTokens: z.ZodNumber;
62
62
  source: z.ZodEnum<{
63
- transcript_last_token_usage: "transcript_last_token_usage";
64
63
  assistant_message_usage: "assistant_message_usage";
64
+ transcript_last_token_usage: "transcript_last_token_usage";
65
65
  call_usage: "call_usage";
66
66
  }>;
67
67
  }, z.core.$strict>>;
68
68
  usageScope: z.ZodOptional<z.ZodEnum<{
69
- session_cumulative: "session_cumulative";
70
69
  turn: "turn";
70
+ session_cumulative: "session_cumulative";
71
71
  }>>;
72
72
  usageSupport: z.ZodOptional<z.ZodEnum<{
73
73
  complete: "complete";
74
74
  unsupported_token_shape: "unsupported_token_shape";
75
75
  }>>;
76
+ maxRequestPromptTokens: z.ZodOptional<z.ZodNumber>;
76
77
  reportedTotalTokens: z.ZodOptional<z.ZodNumber>;
77
78
  tokenComponentEvidence: z.ZodOptional<z.ZodObject<{
78
79
  inputTokens: z.ZodLiteral<"observed">;
@@ -121,8 +122,8 @@ declare const valueSchema: z.ZodObject<{
121
122
  total: z.ZodOptional<z.ZodNumber>;
122
123
  cacheAccounting: z.ZodEnum<{
123
124
  unknown: "unknown";
124
- none: "none";
125
125
  included: "included";
126
+ none: "none";
126
127
  }>;
127
128
  }, z.core.$strict>>;
128
129
  usage: z.ZodObject<{
@@ -161,12 +162,11 @@ declare const valueSchema: z.ZodObject<{
161
162
  kind: z.ZodEnum<{
162
163
  file: "file";
163
164
  agent: "agent";
164
- task: "task";
165
165
  project: "project";
166
+ task: "task";
166
167
  automation: "automation";
167
168
  }>;
168
169
  action: z.ZodEnum<{
169
- working: "working";
170
170
  building: "building";
171
171
  refining: "refining";
172
172
  fixing: "fixing";
@@ -176,11 +176,12 @@ declare const valueSchema: z.ZodObject<{
176
176
  configuring: "configuring";
177
177
  publishing: "publishing";
178
178
  running: "running";
179
+ working: "working";
179
180
  }>;
180
181
  source: z.ZodEnum<{
181
- user_prompts: "user_prompts";
182
182
  project: "project";
183
183
  agent_title: "agent_title";
184
+ user_prompts: "user_prompts";
184
185
  file_activity: "file_activity";
185
186
  }>;
186
187
  promptCount: z.ZodNumber;
@@ -232,9 +233,9 @@ declare const valueSchema: z.ZodObject<{
232
233
  }, z.core.$strict>>;
233
234
  diagnostics: z.ZodArray<z.ZodObject<{
234
235
  code: z.ZodEnum<{
236
+ unsupported_token_shape: "unsupported_token_shape";
235
237
  malformed_jsonl: "malformed_jsonl";
236
238
  malformed_session_file: "malformed_session_file";
237
- unsupported_token_shape: "unsupported_token_shape";
238
239
  }>;
239
240
  count: z.ZodNumber;
240
241
  }, z.core.$strict>>;
@@ -250,7 +251,7 @@ type PersistedValue = PersistedQualitativeValue;
250
251
  */
251
252
  export declare const qualitativeEntryKeySchema: z.ZodObject<{
252
253
  schemaVersion: z.ZodLiteral<1>;
253
- parserVersion: z.ZodLiteral<4>;
254
+ parserVersion: z.ZodLiteral<6>;
254
255
  agent: z.ZodEnum<{
255
256
  "claude-code": "claude-code";
256
257
  codex: "codex";
@@ -285,19 +286,20 @@ export declare const qualitativeEntryValueSchema: z.ZodObject<{
285
286
  contextTokens: z.ZodNumber;
286
287
  totalTokens: z.ZodNumber;
287
288
  source: z.ZodEnum<{
288
- transcript_last_token_usage: "transcript_last_token_usage";
289
289
  assistant_message_usage: "assistant_message_usage";
290
+ transcript_last_token_usage: "transcript_last_token_usage";
290
291
  call_usage: "call_usage";
291
292
  }>;
292
293
  }, z.core.$strict>>;
293
294
  usageScope: z.ZodOptional<z.ZodEnum<{
294
- session_cumulative: "session_cumulative";
295
295
  turn: "turn";
296
+ session_cumulative: "session_cumulative";
296
297
  }>>;
297
298
  usageSupport: z.ZodOptional<z.ZodEnum<{
298
299
  complete: "complete";
299
300
  unsupported_token_shape: "unsupported_token_shape";
300
301
  }>>;
302
+ maxRequestPromptTokens: z.ZodOptional<z.ZodNumber>;
301
303
  reportedTotalTokens: z.ZodOptional<z.ZodNumber>;
302
304
  tokenComponentEvidence: z.ZodOptional<z.ZodObject<{
303
305
  inputTokens: z.ZodLiteral<"observed">;
@@ -346,8 +348,8 @@ export declare const qualitativeEntryValueSchema: z.ZodObject<{
346
348
  total: z.ZodOptional<z.ZodNumber>;
347
349
  cacheAccounting: z.ZodEnum<{
348
350
  unknown: "unknown";
349
- none: "none";
350
351
  included: "included";
352
+ none: "none";
351
353
  }>;
352
354
  }, z.core.$strict>>;
353
355
  usage: z.ZodObject<{
@@ -386,12 +388,11 @@ export declare const qualitativeEntryValueSchema: z.ZodObject<{
386
388
  kind: z.ZodEnum<{
387
389
  file: "file";
388
390
  agent: "agent";
389
- task: "task";
390
391
  project: "project";
392
+ task: "task";
391
393
  automation: "automation";
392
394
  }>;
393
395
  action: z.ZodEnum<{
394
- working: "working";
395
396
  building: "building";
396
397
  refining: "refining";
397
398
  fixing: "fixing";
@@ -401,11 +402,12 @@ export declare const qualitativeEntryValueSchema: z.ZodObject<{
401
402
  configuring: "configuring";
402
403
  publishing: "publishing";
403
404
  running: "running";
405
+ working: "working";
404
406
  }>;
405
407
  source: z.ZodEnum<{
406
- user_prompts: "user_prompts";
407
408
  project: "project";
408
409
  agent_title: "agent_title";
410
+ user_prompts: "user_prompts";
409
411
  file_activity: "file_activity";
410
412
  }>;
411
413
  promptCount: z.ZodNumber;
@@ -457,9 +459,9 @@ export declare const qualitativeEntryValueSchema: z.ZodObject<{
457
459
  }, z.core.$strict>>;
458
460
  diagnostics: z.ZodArray<z.ZodObject<{
459
461
  code: z.ZodEnum<{
462
+ unsupported_token_shape: "unsupported_token_shape";
460
463
  malformed_jsonl: "malformed_jsonl";
461
464
  malformed_session_file: "malformed_session_file";
462
- unsupported_token_shape: "unsupported_token_shape";
463
465
  }>;
464
466
  count: z.ZodNumber;
465
467
  }, z.core.$strict>>;
@@ -129,6 +129,11 @@ const callSchema = z.object({
129
129
  latestTurnUsage: turnUsageSchema.optional(),
130
130
  usageScope: z.enum(["turn", "session_cumulative"]).optional(),
131
131
  usageSupport: z.enum(["complete", "unsupported_token_shape"]).optional(),
132
+ // Per-request tier evidence for session-cumulative slices. The strict schema
133
+ // must carry it: without this key the entry fails validation on write, the
134
+ // failure is swallowed by the caller, and every run re-parses the whole
135
+ // corpus instead of reusing the cache.
136
+ maxRequestPromptTokens: finiteNonnegativeInteger.optional(),
132
137
  reportedTotalTokens: finiteNonnegativeInteger.optional(),
133
138
  tokenComponentEvidence: tokenComponentEvidenceSchema.optional(),
134
139
  sourceVersion: z.string().min(1).max(64).optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-finops/core",
3
- "version": "0.9.4",
3
+ "version": "0.9.6",
4
4
  "funding": "https://asktilden.com",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,7 +0,0 @@
1
- /**
2
- * One intentionally pragmatic normalizer for waitlist and receipt-email
3
- * boundaries. It prevents header/control injection without pretending to be
4
- * a mailbox-verification system; ownership still requires an explicit flow.
5
- */
6
- export declare function normalizeAibillEmailAddress(value: string): string | undefined;
7
- //# sourceMappingURL=emailAddress.d.ts.map
@@ -1,16 +0,0 @@
1
- const controlCharacters = /[\u0000-\u001F\u007F]/u;
2
- const pragmaticEmailAddress = /^[^\s@]+@[^\s@]+\.[^\s@]+$/u;
3
- /**
4
- * One intentionally pragmatic normalizer for waitlist and receipt-email
5
- * boundaries. It prevents header/control injection without pretending to be
6
- * a mailbox-verification system; ownership still requires an explicit flow.
7
- */
8
- export function normalizeAibillEmailAddress(value) {
9
- const normalized = value.trim().toLowerCase();
10
- if (normalized.length < 3 || normalized.length > 254)
11
- return undefined;
12
- if (controlCharacters.test(normalized))
13
- return undefined;
14
- return pragmaticEmailAddress.test(normalized) ? normalized : undefined;
15
- }
16
- //# sourceMappingURL=emailAddress.js.map