@agent-finops/core 0.7.3 → 0.8.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.
@@ -7,7 +7,7 @@
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-13";
11
11
  const pricingRules = [
12
12
  // Anthropic
13
13
  { match: /^claude-fable-5/i, inputPerM: 10, outputPerM: 50 },
@@ -18,9 +18,42 @@ const pricingRules = [
18
18
  { match: /^claude-3-7-sonnet|^claude-3-5-sonnet/i, inputPerM: 3, outputPerM: 15 },
19
19
  { match: /^claude-3-5-haiku/i, inputPerM: 0.8, outputPerM: 4 },
20
20
  // 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 },
21
+ {
22
+ match: /^gpt-5\.6(?:-sol)?$/i,
23
+ inputPerM: 5,
24
+ outputPerM: 30,
25
+ cacheReadPerM: 0.5,
26
+ abovePromptTokens: {
27
+ threshold: 272_000,
28
+ inputPerM: 10,
29
+ outputPerM: 45,
30
+ cacheReadPerM: 1
31
+ }
32
+ },
33
+ {
34
+ match: /^gpt-5\.6-terra/i,
35
+ inputPerM: 2,
36
+ outputPerM: 12,
37
+ cacheReadPerM: 0.2,
38
+ abovePromptTokens: {
39
+ threshold: 272_000,
40
+ inputPerM: 4,
41
+ outputPerM: 18,
42
+ cacheReadPerM: 0.4
43
+ }
44
+ },
45
+ {
46
+ match: /^gpt-5\.6-luna/i,
47
+ inputPerM: 0.2,
48
+ outputPerM: 1.2,
49
+ cacheReadPerM: 0.02,
50
+ abovePromptTokens: {
51
+ threshold: 272_000,
52
+ inputPerM: 0.4,
53
+ outputPerM: 1.8,
54
+ cacheReadPerM: 0.04
55
+ }
56
+ },
24
57
  { match: /^gpt-5\.5(?:-codex)?/i, inputPerM: 5, outputPerM: 30, cacheReadPerM: 0.5 },
25
58
  { match: /^gpt-5\.4-mini/i, inputPerM: 0.75, outputPerM: 4.5, cacheReadPerM: 0.075 },
26
59
  { match: /^gpt-5\.4-nano/i, inputPerM: 0.2, outputPerM: 1.25, cacheReadPerM: 0.02 },
@@ -38,9 +71,22 @@ const pricingRules = [
38
71
  { match: /^o3$/i, inputPerM: 2, outputPerM: 8 },
39
72
  { match: /^o4-mini/i, inputPerM: 1.1, outputPerM: 4.4 },
40
73
  // 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 },
74
+ {
75
+ match: /^gemini-2\.5-pro$/i,
76
+ inputPerM: 1.25,
77
+ outputPerM: 10,
78
+ cacheReadPerM: 0.125,
79
+ abovePromptTokens: {
80
+ threshold: 200_000,
81
+ inputPerM: 2.5,
82
+ outputPerM: 15,
83
+ cacheReadPerM: 0.25
84
+ }
85
+ },
86
+ // Gemini CLI's persisted Flash/Flash-Lite summary does not retain token
87
+ // modality, while published audio and non-audio input/cache rates differ.
88
+ // Until modality is explicit, returning undefined is safer than silently
89
+ // applying the text/image/video rate to a potentially multimodal request.
44
90
  // DeepSeek (official API list prices)
45
91
  { match: /^deepseek-chat|^deepseek-v3/i, inputPerM: 0.27, outputPerM: 1.1 },
46
92
  { match: /^deepseek-reasoner|^deepseek-r1/i, inputPerM: 0.55, outputPerM: 2.19 },
@@ -61,19 +107,77 @@ export function findPricingRule(model) {
61
107
  * published price we recognize.
62
108
  */
63
109
  export function estimateTokenCostUsd(model, usage) {
110
+ const usd = rawTokenCostUsd(model, usage);
111
+ return usd === undefined ? undefined : roundUsd(usd);
112
+ }
113
+ /**
114
+ * Price request-scoped usage before aggregating it. This is required for
115
+ * models whose entire request moves to a higher rate above a prompt-size
116
+ * threshold; pricing a daily token sum would incorrectly treat many small
117
+ * requests as one large request.
118
+ */
119
+ export function estimateTokenCostsUsd(model, usages) {
120
+ let total = 0;
121
+ for (const usage of usages) {
122
+ const usd = rawTokenCostUsd(model, usage);
123
+ if (usd === undefined)
124
+ return undefined;
125
+ total += usd;
126
+ }
127
+ return roundUsd(total);
128
+ }
129
+ /** Whether this model's rate selection depends on each request's prompt size. */
130
+ export function usesPromptTieredPricing(model) {
131
+ return promptTierThreshold(model) !== undefined;
132
+ }
133
+ /** Prompt-size threshold for tiered request pricing, when one is published. */
134
+ export function promptTierThreshold(model) {
135
+ return findPricingRule(model)?.abovePromptTokens?.threshold;
136
+ }
137
+ /**
138
+ * Tiered prices are selected per request, never from a multi-request sum.
139
+ * An aggregate is still unambiguous when its entire non-negative prompt-side
140
+ * total is at or below the threshold; then no constituent request can have
141
+ * crossed it. Larger aggregates fail closed until request-level evidence is
142
+ * available.
143
+ */
144
+ export function canPriceTokenUsageAtScope(model, usage, scope) {
145
+ const threshold = promptTierThreshold(model);
146
+ if (threshold === undefined || scope === "request")
147
+ return true;
148
+ return effectivePromptTokens(usage) <= threshold;
149
+ }
150
+ function rawTokenCostUsd(model, usage) {
64
151
  const rule = findPricingRule(model);
65
152
  if (!rule) {
66
153
  return undefined;
67
154
  }
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 +
155
+ const promptTokens = effectivePromptTokens(usage);
156
+ const rates = rule.abovePromptTokens &&
157
+ promptTokens > rule.abovePromptTokens.threshold
158
+ ? rule.abovePromptTokens
159
+ : rule;
160
+ const cacheRead = rates.cacheReadPerM ?? rates.inputPerM * 0.1;
161
+ const write5m = rates.cacheWrite5mPerM ?? rates.inputPerM * 1.25;
162
+ const write1h = rates.cacheWrite1hPerM ?? rates.inputPerM * 2;
163
+ const usd = (usage.inputTokens * rates.inputPerM +
164
+ usage.outputTokens * rates.outputPerM +
73
165
  (usage.cacheReadTokens ?? 0) * cacheRead +
74
166
  (usage.cacheWrite5mTokens ?? 0) * write5m +
75
- (usage.cacheWrite1hTokens ?? 0) * write1h) /
167
+ (usage.cacheWrite1hTokens ?? 0) * write1h +
168
+ (usage.thoughtTokens ?? 0) * rates.outputPerM +
169
+ (usage.toolTokens ?? 0) * rates.inputPerM) /
76
170
  1_000_000;
171
+ return usd;
172
+ }
173
+ function effectivePromptTokens(usage) {
174
+ return usage.inputTokens +
175
+ (usage.cacheReadTokens ?? 0) +
176
+ (usage.cacheWrite5mTokens ?? 0) +
177
+ (usage.cacheWrite1hTokens ?? 0) +
178
+ (usage.toolTokens ?? 0);
179
+ }
180
+ function roundUsd(usd) {
77
181
  return Math.round(usd * 10_000) / 10_000;
78
182
  }
79
183
  //# sourceMappingURL=modelPricing.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