@agent-finops/core 0.7.3 → 0.8.1

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.
@@ -7,10 +7,13 @@
7
7
  * matched top-down; first match wins. Unknown models return undefined so
8
8
  * callers can label the record "missing" instead of inventing a number.
9
9
  */
10
- export const PRICING_TABLE_AS_OF = "2026-07-28";
10
+ export const PRICING_TABLE_AS_OF = "2026-08-14";
11
11
  const pricingRules = [
12
12
  // Anthropic
13
13
  { match: /^claude-fable-5/i, inputPerM: 10, outputPerM: 50 },
14
+ { match: /^claude-mythos-5/i, inputPerM: 10, outputPerM: 50 },
15
+ { match: /^claude-opus-5/i, inputPerM: 5, outputPerM: 25 },
16
+ { match: /^claude-sonnet-5/i, inputPerM: 2, outputPerM: 10 },
14
17
  { match: /^claude-opus-4-[5-9]/i, inputPerM: 5, outputPerM: 25 },
15
18
  { match: /^claude-opus-4(-[01])?$/i, inputPerM: 15, outputPerM: 75 },
16
19
  { match: /^claude-sonnet-4/i, inputPerM: 3, outputPerM: 15 },
@@ -18,9 +21,42 @@ const pricingRules = [
18
21
  { match: /^claude-3-7-sonnet|^claude-3-5-sonnet/i, inputPerM: 3, outputPerM: 15 },
19
22
  { match: /^claude-3-5-haiku/i, inputPerM: 0.8, outputPerM: 4 },
20
23
  // OpenAI (newer and more specific families must precede the GPT-5 fallback)
21
- { match: /^gpt-5\.6(?:-sol)?$/i, inputPerM: 5, outputPerM: 30, cacheReadPerM: 0.5 },
22
- { match: /^gpt-5\.6-terra/i, inputPerM: 2.5, outputPerM: 15, cacheReadPerM: 0.25 },
23
- { match: /^gpt-5\.6-luna/i, inputPerM: 1, outputPerM: 6, cacheReadPerM: 0.1 },
24
+ {
25
+ match: /^gpt-5\.6(?:-sol)?$/i,
26
+ inputPerM: 5,
27
+ outputPerM: 30,
28
+ cacheReadPerM: 0.5,
29
+ abovePromptTokens: {
30
+ threshold: 272_000,
31
+ inputPerM: 10,
32
+ outputPerM: 45,
33
+ cacheReadPerM: 1
34
+ }
35
+ },
36
+ {
37
+ match: /^gpt-5\.6-terra/i,
38
+ inputPerM: 2,
39
+ outputPerM: 12,
40
+ cacheReadPerM: 0.2,
41
+ abovePromptTokens: {
42
+ threshold: 272_000,
43
+ inputPerM: 4,
44
+ outputPerM: 18,
45
+ cacheReadPerM: 0.4
46
+ }
47
+ },
48
+ {
49
+ match: /^gpt-5\.6-luna/i,
50
+ inputPerM: 0.2,
51
+ outputPerM: 1.2,
52
+ cacheReadPerM: 0.02,
53
+ abovePromptTokens: {
54
+ threshold: 272_000,
55
+ inputPerM: 0.4,
56
+ outputPerM: 1.8,
57
+ cacheReadPerM: 0.04
58
+ }
59
+ },
24
60
  { match: /^gpt-5\.5(?:-codex)?/i, inputPerM: 5, outputPerM: 30, cacheReadPerM: 0.5 },
25
61
  { match: /^gpt-5\.4-mini/i, inputPerM: 0.75, outputPerM: 4.5, cacheReadPerM: 0.075 },
26
62
  { match: /^gpt-5\.4-nano/i, inputPerM: 0.2, outputPerM: 1.25, cacheReadPerM: 0.02 },
@@ -38,9 +74,22 @@ const pricingRules = [
38
74
  { match: /^o3$/i, inputPerM: 2, outputPerM: 8 },
39
75
  { match: /^o4-mini/i, inputPerM: 1.1, outputPerM: 4.4 },
40
76
  // Google (Gemini API list prices)
41
- { match: /^gemini-2\.5-pro/i, inputPerM: 1.25, outputPerM: 10 },
42
- { match: /^gemini-2\.5-flash-lite/i, inputPerM: 0.1, outputPerM: 0.4 },
43
- { match: /^gemini-2\.5-flash/i, inputPerM: 0.3, outputPerM: 2.5 },
77
+ {
78
+ match: /^gemini-2\.5-pro$/i,
79
+ inputPerM: 1.25,
80
+ outputPerM: 10,
81
+ cacheReadPerM: 0.125,
82
+ abovePromptTokens: {
83
+ threshold: 200_000,
84
+ inputPerM: 2.5,
85
+ outputPerM: 15,
86
+ cacheReadPerM: 0.25
87
+ }
88
+ },
89
+ // Gemini CLI's persisted Flash/Flash-Lite summary does not retain token
90
+ // modality, while published audio and non-audio input/cache rates differ.
91
+ // Until modality is explicit, returning undefined is safer than silently
92
+ // applying the text/image/video rate to a potentially multimodal request.
44
93
  // DeepSeek (official API list prices)
45
94
  { match: /^deepseek-chat|^deepseek-v3/i, inputPerM: 0.27, outputPerM: 1.1 },
46
95
  { match: /^deepseek-reasoner|^deepseek-r1/i, inputPerM: 0.55, outputPerM: 2.19 },
@@ -61,19 +110,77 @@ export function findPricingRule(model) {
61
110
  * published price we recognize.
62
111
  */
63
112
  export function estimateTokenCostUsd(model, usage) {
113
+ const usd = rawTokenCostUsd(model, usage);
114
+ return usd === undefined ? undefined : roundUsd(usd);
115
+ }
116
+ /**
117
+ * Price request-scoped usage before aggregating it. This is required for
118
+ * models whose entire request moves to a higher rate above a prompt-size
119
+ * threshold; pricing a daily token sum would incorrectly treat many small
120
+ * requests as one large request.
121
+ */
122
+ export function estimateTokenCostsUsd(model, usages) {
123
+ let total = 0;
124
+ for (const usage of usages) {
125
+ const usd = rawTokenCostUsd(model, usage);
126
+ if (usd === undefined)
127
+ return undefined;
128
+ total += usd;
129
+ }
130
+ return roundUsd(total);
131
+ }
132
+ /** Whether this model's rate selection depends on each request's prompt size. */
133
+ export function usesPromptTieredPricing(model) {
134
+ return promptTierThreshold(model) !== undefined;
135
+ }
136
+ /** Prompt-size threshold for tiered request pricing, when one is published. */
137
+ export function promptTierThreshold(model) {
138
+ return findPricingRule(model)?.abovePromptTokens?.threshold;
139
+ }
140
+ /**
141
+ * Tiered prices are selected per request, never from a multi-request sum.
142
+ * An aggregate is still unambiguous when its entire non-negative prompt-side
143
+ * total is at or below the threshold; then no constituent request can have
144
+ * crossed it. Larger aggregates fail closed until request-level evidence is
145
+ * available.
146
+ */
147
+ export function canPriceTokenUsageAtScope(model, usage, scope) {
148
+ const threshold = promptTierThreshold(model);
149
+ if (threshold === undefined || scope === "request")
150
+ return true;
151
+ return effectivePromptTokens(usage) <= threshold;
152
+ }
153
+ function rawTokenCostUsd(model, usage) {
64
154
  const rule = findPricingRule(model);
65
155
  if (!rule) {
66
156
  return undefined;
67
157
  }
68
- const cacheRead = rule.cacheReadPerM ?? rule.inputPerM * 0.1;
69
- const write5m = rule.cacheWrite5mPerM ?? rule.inputPerM * 1.25;
70
- const write1h = rule.cacheWrite1hPerM ?? rule.inputPerM * 2;
71
- const usd = (usage.inputTokens * rule.inputPerM +
72
- usage.outputTokens * rule.outputPerM +
158
+ const promptTokens = effectivePromptTokens(usage);
159
+ const rates = rule.abovePromptTokens &&
160
+ promptTokens > rule.abovePromptTokens.threshold
161
+ ? rule.abovePromptTokens
162
+ : rule;
163
+ const cacheRead = rates.cacheReadPerM ?? rates.inputPerM * 0.1;
164
+ const write5m = rates.cacheWrite5mPerM ?? rates.inputPerM * 1.25;
165
+ const write1h = rates.cacheWrite1hPerM ?? rates.inputPerM * 2;
166
+ const usd = (usage.inputTokens * rates.inputPerM +
167
+ usage.outputTokens * rates.outputPerM +
73
168
  (usage.cacheReadTokens ?? 0) * cacheRead +
74
169
  (usage.cacheWrite5mTokens ?? 0) * write5m +
75
- (usage.cacheWrite1hTokens ?? 0) * write1h) /
170
+ (usage.cacheWrite1hTokens ?? 0) * write1h +
171
+ (usage.thoughtTokens ?? 0) * rates.outputPerM +
172
+ (usage.toolTokens ?? 0) * rates.inputPerM) /
76
173
  1_000_000;
174
+ return usd;
175
+ }
176
+ function effectivePromptTokens(usage) {
177
+ return usage.inputTokens +
178
+ (usage.cacheReadTokens ?? 0) +
179
+ (usage.cacheWrite5mTokens ?? 0) +
180
+ (usage.cacheWrite1hTokens ?? 0) +
181
+ (usage.toolTokens ?? 0);
182
+ }
183
+ function roundUsd(usd) {
77
184
  return Math.round(usd * 10_000) / 10_000;
78
185
  }
79
186
  //# sourceMappingURL=modelPricing.js.map
package/dist/planMath.js CHANGED
@@ -54,9 +54,9 @@ export function computePlanChecks(records, detectedPlans = []) {
54
54
  const savingsVsApi = roundMoney(monthly - detectedKnown.monthlyUsd);
55
55
  effectiveSavings = savingsVsApi > 0 ? savingsVsApi : undefined;
56
56
  headline =
57
- `${agent}: ~$${monthly.toFixed(2)}/mo at API rates (${basis}) — compared with ${detectedKnown.name} ` +
57
+ `${agent}: ~${formatUsd(monthly)}/mo at API rates (${basis}) — compared with ${detectedKnown.name} ` +
58
58
  `($${detectedKnown.monthlyUsd}/mo; label detected locally): ~${valueMultiple}× the plan price in API-equivalent usage` +
59
- (effectiveSavings ? `, a ~$${effectiveSavings.toFixed(2)}/mo value difference to investigate.` : `.`);
59
+ (effectiveSavings ? `, a ~${formatUsd(effectiveSavings)}/mo value difference to investigate.` : `.`);
60
60
  if (monthly > detectedKnown.coversUpToUsd) {
61
61
  const nextTier = subscriptionPlans.find((plan) => plan.agent === agent && plan.coversUpToUsd > detectedKnown.coversUpToUsd);
62
62
  // A local limit signal upgrades "might hit limits" to hard evidence.
@@ -75,7 +75,7 @@ export function computePlanChecks(records, detectedPlans = []) {
75
75
  // Detected a plan we can't price (e.g. an unrecognized tier): state the
76
76
  // fact, then fall back to suggestion math without pretending certainty.
77
77
  headline =
78
- `${agent}: ~$${monthly.toFixed(2)}/mo at API rates (${basis}) — compared with ${detected.planLabel} ` +
78
+ `${agent}: ~${formatUsd(monthly)}/mo at API rates (${basis}) — compared with ${detected.planLabel} ` +
79
79
  `(label detected locally; price not in our table)` +
80
80
  (suggested ? `; reference listed plan: ${suggested.name} ($${suggested.monthlyUsd}/mo).` : `.`);
81
81
  }
@@ -84,13 +84,13 @@ export function computePlanChecks(records, detectedPlans = []) {
84
84
  valueMultiple = covered ? Math.round((monthly / suggested.monthlyUsd) * 10) / 10 : undefined;
85
85
  effectiveSavings = covered ? savings : undefined;
86
86
  if (!suggested) {
87
- headline = `${agent}: ~$${monthly.toFixed(2)}/mo at API rates (${basis}).`;
87
+ headline = `${agent}: ~${formatUsd(monthly)}/mo at API rates (${basis}).`;
88
88
  }
89
89
  else if (covered) {
90
- headline = `${agent}: ~$${monthly.toFixed(2)}/mo at API rates (${basis}) — ${suggested.name} is a $${suggested.monthlyUsd}/mo reference point. That is ~${valueMultiple}× the plan price in API-equivalent usage, a ~$${savings.toFixed(2)}/mo value difference to investigate; it does not prove plan coverage.`;
90
+ headline = `${agent}: ~${formatUsd(monthly)}/mo at API rates (${basis}) — ${suggested.name} is a $${suggested.monthlyUsd}/mo reference point. That is ~${valueMultiple}× the plan price in API-equivalent usage, a ~${formatUsd(savings)}/mo value difference to investigate; it does not prove plan coverage.`;
91
91
  }
92
92
  else {
93
- headline = `${agent}: ~$${monthly.toFixed(2)}/mo at API rates (${basis}) — below the $${suggested.monthlyUsd}/mo price of ${suggested.name}; compare account benefits and provider-reported charges before changing plans.`;
93
+ headline = `${agent}: ~${formatUsd(monthly)}/mo at API rates (${basis}) — below the $${suggested.monthlyUsd}/mo price of ${suggested.name}; compare account benefits and provider-reported charges before changing plans.`;
94
94
  }
95
95
  }
96
96
  checks.push({
@@ -108,6 +108,11 @@ export function computePlanChecks(records, detectedPlans = []) {
108
108
  return checks.sort((left, right) => right.apiEquivalentMonthlyUsd - left.apiEquivalentMonthlyUsd);
109
109
  }
110
110
  function roundMoney(value) {
111
- return Math.round(value * 100) / 100;
111
+ return Math.round(value * 10_000) / 10_000;
112
+ }
113
+ function formatUsd(value) {
114
+ if (value > 0 && value < 0.01)
115
+ return "<$0.01";
116
+ return `$${value.toFixed(2)}`;
112
117
  }
113
118
  //# sourceMappingURL=planMath.js.map
@@ -56,6 +56,21 @@ export type ProviderQaSummary = {
56
56
  instructions: string[];
57
57
  };
58
58
  export type ProviderId = "openai" | "anthropic" | "github-copilot" | "cursor" | string;
59
+ export type ProviderConnectorErrorCode = "authentication_error" | "provider_request_error";
60
+ /**
61
+ * Trusted connector failure metadata. Provider prose remains untrusted and is
62
+ * used only as a sanitized human-readable message; callers classify failures
63
+ * from this product-authored code and the observed HTTP status instead.
64
+ */
65
+ export declare class ProviderConnectorError extends Error {
66
+ readonly code: ProviderConnectorErrorCode;
67
+ readonly status?: number;
68
+ constructor(message: string, options: {
69
+ code: ProviderConnectorErrorCode;
70
+ status?: number;
71
+ });
72
+ }
73
+ export declare function isProviderAuthenticationError(error: unknown): boolean;
59
74
  export type ProviderConnectorInput = {
60
75
  provider: ProviderId;
61
76
  sourceId?: string;
@@ -109,6 +124,7 @@ export declare function normalizeGitHubCopilotMetricsResponse(response: unknown,
109
124
  export declare function normalizeCursorSpendResponse(response: unknown, options: NormalizerOptions): UsageRecord[];
110
125
  export declare function fetchProviderUsageRecords(input: ProviderConnectorInput): Promise<ProviderConnectorResult>;
111
126
  export declare function summarizeProviderFinancials(records: UsageRecord[]): ProviderFinancialSummary;
127
+ export declare function providerFinancialCompleteness(records: UsageRecord[], coverage: ProviderCoverageStatus): ProviderConnectorResult["completeness"];
112
128
  /**
113
129
  * Keep evidence records available to callers, but never add estimates to a
114
130
  * provider's official billed total. This selection is intended for aggregate