@lcv-ideas-software/cross-review 4.0.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.
Files changed (122) hide show
  1. package/CHANGELOG.md +2568 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +26 -0
  4. package/README.md +208 -0
  5. package/SECURITY.md +52 -0
  6. package/dist/scripts/api-streaming-smoke.d.ts +1 -0
  7. package/dist/scripts/api-streaming-smoke.js +78 -0
  8. package/dist/scripts/api-streaming-smoke.js.map +1 -0
  9. package/dist/scripts/runtime-default-smoke.d.ts +1 -0
  10. package/dist/scripts/runtime-default-smoke.js +88 -0
  11. package/dist/scripts/runtime-default-smoke.js.map +1 -0
  12. package/dist/scripts/runtime-smoke.d.ts +1 -0
  13. package/dist/scripts/runtime-smoke.js +148 -0
  14. package/dist/scripts/runtime-smoke.js.map +1 -0
  15. package/dist/scripts/smoke.d.ts +1 -0
  16. package/dist/scripts/smoke.js +6156 -0
  17. package/dist/scripts/smoke.js.map +1 -0
  18. package/dist/src/core/cache-manifest.d.ts +22 -0
  19. package/dist/src/core/cache-manifest.js +133 -0
  20. package/dist/src/core/cache-manifest.js.map +1 -0
  21. package/dist/src/core/caller-tokens.d.ts +32 -0
  22. package/dist/src/core/caller-tokens.js +240 -0
  23. package/dist/src/core/caller-tokens.js.map +1 -0
  24. package/dist/src/core/config.d.ts +9 -0
  25. package/dist/src/core/config.js +643 -0
  26. package/dist/src/core/config.js.map +1 -0
  27. package/dist/src/core/convergence.d.ts +5 -0
  28. package/dist/src/core/convergence.js +186 -0
  29. package/dist/src/core/convergence.js.map +1 -0
  30. package/dist/src/core/cost.d.ts +59 -0
  31. package/dist/src/core/cost.js +359 -0
  32. package/dist/src/core/cost.js.map +1 -0
  33. package/dist/src/core/file-config.d.ts +316 -0
  34. package/dist/src/core/file-config.js +490 -0
  35. package/dist/src/core/file-config.js.map +1 -0
  36. package/dist/src/core/orchestrator.d.ts +199 -0
  37. package/dist/src/core/orchestrator.js +3430 -0
  38. package/dist/src/core/orchestrator.js.map +1 -0
  39. package/dist/src/core/prompt-parts.d.ts +58 -0
  40. package/dist/src/core/prompt-parts.js +122 -0
  41. package/dist/src/core/prompt-parts.js.map +1 -0
  42. package/dist/src/core/relator-lottery.d.ts +23 -0
  43. package/dist/src/core/relator-lottery.js +112 -0
  44. package/dist/src/core/relator-lottery.js.map +1 -0
  45. package/dist/src/core/reports.d.ts +2 -0
  46. package/dist/src/core/reports.js +82 -0
  47. package/dist/src/core/reports.js.map +1 -0
  48. package/dist/src/core/session-store.d.ts +149 -0
  49. package/dist/src/core/session-store.js +1923 -0
  50. package/dist/src/core/session-store.js.map +1 -0
  51. package/dist/src/core/status.d.ts +61 -0
  52. package/dist/src/core/status.js +249 -0
  53. package/dist/src/core/status.js.map +1 -0
  54. package/dist/src/core/timeouts.d.ts +2 -0
  55. package/dist/src/core/timeouts.js +3 -0
  56. package/dist/src/core/timeouts.js.map +1 -0
  57. package/dist/src/core/types.d.ts +604 -0
  58. package/dist/src/core/types.js +36 -0
  59. package/dist/src/core/types.js.map +1 -0
  60. package/dist/src/dashboard/server.d.ts +2 -0
  61. package/dist/src/dashboard/server.js +339 -0
  62. package/dist/src/dashboard/server.js.map +1 -0
  63. package/dist/src/mcp/server.d.ts +54 -0
  64. package/dist/src/mcp/server.js +1584 -0
  65. package/dist/src/mcp/server.js.map +1 -0
  66. package/dist/src/observability/logger.d.ts +9 -0
  67. package/dist/src/observability/logger.js +24 -0
  68. package/dist/src/observability/logger.js.map +1 -0
  69. package/dist/src/peers/anthropic.d.ts +14 -0
  70. package/dist/src/peers/anthropic.js +290 -0
  71. package/dist/src/peers/anthropic.js.map +1 -0
  72. package/dist/src/peers/base.d.ts +72 -0
  73. package/dist/src/peers/base.js +416 -0
  74. package/dist/src/peers/base.js.map +1 -0
  75. package/dist/src/peers/deepseek.d.ts +12 -0
  76. package/dist/src/peers/deepseek.js +246 -0
  77. package/dist/src/peers/deepseek.js.map +1 -0
  78. package/dist/src/peers/errors.d.ts +2 -0
  79. package/dist/src/peers/errors.js +185 -0
  80. package/dist/src/peers/errors.js.map +1 -0
  81. package/dist/src/peers/gemini.d.ts +13 -0
  82. package/dist/src/peers/gemini.js +215 -0
  83. package/dist/src/peers/gemini.js.map +1 -0
  84. package/dist/src/peers/grok.d.ts +17 -0
  85. package/dist/src/peers/grok.js +346 -0
  86. package/dist/src/peers/grok.js.map +1 -0
  87. package/dist/src/peers/model-selection.d.ts +4 -0
  88. package/dist/src/peers/model-selection.js +260 -0
  89. package/dist/src/peers/model-selection.js.map +1 -0
  90. package/dist/src/peers/openai.d.ts +14 -0
  91. package/dist/src/peers/openai.js +299 -0
  92. package/dist/src/peers/openai.js.map +1 -0
  93. package/dist/src/peers/perplexity.d.ts +18 -0
  94. package/dist/src/peers/perplexity.js +375 -0
  95. package/dist/src/peers/perplexity.js.map +1 -0
  96. package/dist/src/peers/registry.d.ts +3 -0
  97. package/dist/src/peers/registry.js +77 -0
  98. package/dist/src/peers/registry.js.map +1 -0
  99. package/dist/src/peers/retry.d.ts +2 -0
  100. package/dist/src/peers/retry.js +36 -0
  101. package/dist/src/peers/retry.js.map +1 -0
  102. package/dist/src/peers/stub.d.ts +13 -0
  103. package/dist/src/peers/stub.js +344 -0
  104. package/dist/src/peers/stub.js.map +1 -0
  105. package/dist/src/peers/text.d.ts +18 -0
  106. package/dist/src/peers/text.js +39 -0
  107. package/dist/src/peers/text.js.map +1 -0
  108. package/dist/src/security/redact.d.ts +2 -0
  109. package/dist/src/security/redact.js +128 -0
  110. package/dist/src/security/redact.js.map +1 -0
  111. package/docs/api-keys.md +34 -0
  112. package/docs/architecture.md +118 -0
  113. package/docs/caching.md +135 -0
  114. package/docs/costs.md +40 -0
  115. package/docs/evidence-preflight.md +88 -0
  116. package/docs/github-security-baseline.md +32 -0
  117. package/docs/model-selection.md +105 -0
  118. package/docs/reports/cross-review-v2-api-capability-smoke-2026-04-30.md +354 -0
  119. package/docs/reports/cross-review-v2-format-recovery-findings-2026-04-28.md +223 -0
  120. package/docs/reports/cross-review-v2-official-provider-docs-refresh-2026-05-05.md +60 -0
  121. package/docs/reports/cross-review-v2-token-streaming-smoke-2026-04-30.md +119 -0
  122. package/package.json +88 -0
@@ -0,0 +1,359 @@
1
+ // v2.21.0 (caching): cost layer extends to merge cache_read/cache_write
2
+ // tokens and surface estimated cache savings on CostEstimate.
3
+ // v2.26.0 (full pricing model + no-hardcoded-financials): cost_rates is
4
+ // now a complete schema supporting base + extended-tier (>threshold) +
5
+ // cache (read/write) + promo (limited-time discount until
6
+ // promo_expires_at). selectRate() chooses the right value per
7
+ // (category, tier, promo-active?) at estimation time. The legacy
8
+ // cache-rates.json fallback was REMOVED per operator directive
9
+ // 2026-05-11 ("nada hardcoded para preços financeiros — o sistema deve
10
+ // travar até o operador configurar as variáveis"). When cache rate env
11
+ // vars are absent, selectRate() gracefully degrades to the input rate
12
+ // (zero savings) instead of synthesizing prices from a static file.
13
+ /**
14
+ * v2.26.0: select the right per-million USD rate for a given (category,
15
+ * tier, promo-active?) combination, with graceful fallback to the next
16
+ * available rate when fields are absent or expired.
17
+ *
18
+ * Selection priority (each step falls through to the next when the
19
+ * corresponding field is unset OR the condition does not apply):
20
+ * 1. promo_<category>_extended_per_million — IFF in promo period AND
21
+ * large prompt AND field set
22
+ * 2. promo_<category>_per_million — IFF in promo period AND field set
23
+ * 3. <category>_extended_per_million — IFF large prompt AND field set
24
+ * 4. <category>_per_million — base rate, set
25
+ * 5. (cache_read / cache_write only) recurse into "input" category
26
+ * — gracefully degrade to "no cache discount" billing when the
27
+ * operator stops configuring cache rates entirely
28
+ *
29
+ * Fallback semantics intent (operator directive 2026-05-11):
30
+ * - When promo expires (today >= promo_expires_at) or promo fields are
31
+ * unset, automatically use base rates without operator intervention.
32
+ * - When extended-tier rates are unset for a tier-aware provider, use
33
+ * base for ALL prompt sizes (no penalty for missing config).
34
+ * - When cache rates are unset entirely, treat cache tokens as fresh
35
+ * input (no discount, no penalty). The provider may still bill them
36
+ * at a lower rate; we just stop modeling that detail.
37
+ *
38
+ * `now` is injected so tests and FinOps replays can pin the clock.
39
+ */
40
+ export function selectRate(rate, category, totalInputTokens, now = new Date()) {
41
+ const inPromo = rate.promo_expires_at != null && Date.parse(rate.promo_expires_at) > now.getTime();
42
+ const isExtended = rate.threshold_tokens != null && totalInputTokens > rate.threshold_tokens;
43
+ const promoBase = {
44
+ input: rate.promo_input_per_million,
45
+ output: rate.promo_output_per_million,
46
+ cache_read: rate.promo_cache_read_per_million,
47
+ cache_write: rate.promo_cache_write_per_million,
48
+ }[category];
49
+ const promoExtended = {
50
+ input: rate.promo_input_extended_per_million,
51
+ output: rate.promo_output_extended_per_million,
52
+ cache_read: rate.promo_cache_read_extended_per_million,
53
+ cache_write: rate.promo_cache_write_extended_per_million,
54
+ }[category];
55
+ const base = {
56
+ input: rate.input_per_million,
57
+ output: rate.output_per_million,
58
+ cache_read: rate.cache_read_per_million,
59
+ cache_write: rate.cache_write_per_million,
60
+ }[category];
61
+ const extended = {
62
+ input: rate.input_extended_per_million,
63
+ output: rate.output_extended_per_million,
64
+ cache_read: rate.cache_read_extended_per_million,
65
+ cache_write: rate.cache_write_extended_per_million,
66
+ }[category];
67
+ if (inPromo) {
68
+ if (isExtended && promoExtended != null) {
69
+ return { rate_per_million: promoExtended, tier_used: "promo_extended" };
70
+ }
71
+ if (promoBase != null) {
72
+ return { rate_per_million: promoBase, tier_used: "promo" };
73
+ }
74
+ // No promo rate for this category → fall through to non-promo cascade.
75
+ }
76
+ if (isExtended && extended != null) {
77
+ return { rate_per_million: extended, tier_used: "extended" };
78
+ }
79
+ if (base != null) {
80
+ return { rate_per_million: base, tier_used: "base" };
81
+ }
82
+ // v2.26.0 graceful degradation: when a cache category has no rate at
83
+ // all (operator stopped configuring it OR provider discontinued the
84
+ // cache discount), bill cache tokens at the input rate instead of
85
+ // dropping them silently. The tier_used reflects the input tier so
86
+ // FinOps still sees promo/extended applied to the input fallback.
87
+ if (category === "cache_read" || category === "cache_write") {
88
+ return selectRate(rate, "input", totalInputTokens, now);
89
+ }
90
+ return undefined;
91
+ }
92
+ export function mergeUsage(items) {
93
+ const total = {};
94
+ for (const item of items) {
95
+ if (!item)
96
+ continue;
97
+ total.input_tokens = (total.input_tokens ?? 0) + (item.input_tokens ?? 0);
98
+ total.output_tokens = (total.output_tokens ?? 0) + (item.output_tokens ?? 0);
99
+ total.total_tokens = (total.total_tokens ?? 0) + (item.total_tokens ?? 0);
100
+ total.reasoning_tokens = (total.reasoning_tokens ?? 0) + (item.reasoning_tokens ?? 0);
101
+ // v2.21.0 (caching): merge cache telemetry. cache_read/write are
102
+ // additive across calls; mode/key_hash are NOT merged because they
103
+ // are per-call attributes (different rounds may hit different
104
+ // cache scopes or modes).
105
+ total.cache_read_tokens = (total.cache_read_tokens ?? 0) + (item.cache_read_tokens ?? 0);
106
+ total.cache_write_tokens = (total.cache_write_tokens ?? 0) + (item.cache_write_tokens ?? 0);
107
+ }
108
+ return total;
109
+ }
110
+ export function estimateCost(config, peer, usage) {
111
+ const rate = config.cost_rates[peer];
112
+ if (!usage || !rate) {
113
+ return { currency: "USD", estimated: false, source: "unknown-rate" };
114
+ }
115
+ const inputTokens = usage.input_tokens ?? 0;
116
+ const outputTokens = usage.output_tokens ?? 0;
117
+ const cacheReadTokens = usage.cache_read_tokens ?? 0;
118
+ const cacheWriteTokens = usage.cache_write_tokens ?? 0;
119
+ // v2.26.0: tier selection considers the FULL prompt size (fresh input +
120
+ // cached read + cache write) because providers like Gemini price by
121
+ // total prompt length, not by post-cache fresh input.
122
+ const totalInputForTier = inputTokens + cacheReadTokens + cacheWriteTokens;
123
+ const inputSel = selectRate(rate, "input", totalInputForTier);
124
+ const outputSel = selectRate(rate, "output", totalInputForTier);
125
+ const cacheReadSel = cacheReadTokens > 0 ? selectRate(rate, "cache_read", totalInputForTier) : undefined;
126
+ const cacheWriteSel = cacheWriteTokens > 0 ? selectRate(rate, "cache_write", totalInputForTier) : undefined;
127
+ // selectRate always returns a value for input/output because base
128
+ // _INPUT/_OUTPUT_USD_PER_MILLION is required at parse time.
129
+ const inputCost = inputSel ? (inputTokens / 1_000_000) * inputSel.rate_per_million : 0;
130
+ const outputCost = outputSel ? (outputTokens / 1_000_000) * outputSel.rate_per_million : 0;
131
+ const cacheReadCost = cacheReadSel
132
+ ? (cacheReadTokens / 1_000_000) * cacheReadSel.rate_per_million
133
+ : 0;
134
+ const cacheWriteCost = cacheWriteSel
135
+ ? (cacheWriteTokens / 1_000_000) * cacheWriteSel.rate_per_million
136
+ : 0;
137
+ // v3.0.0 (Perplexity 6th peer): three additional cost dimensions —
138
+ // (1) per-1000-requests fee scaled by search_context_size,
139
+ // (2) citation_tokens (sonar-deep-research only),
140
+ // (3) deep_research_reasoning_tokens (sonar-deep-research only),
141
+ // (4) search_queries per-1000 fee (sonar-deep-research only).
142
+ // All four are zero for non-perplexity peers (their cost_rates entry
143
+ // never defines these fields) AND zero for non-deep-research
144
+ // perplexity models (operator leaves citation/reasoning/queries
145
+ // rates unset; usage.citation_tokens / usage.num_search_queries are
146
+ // absent). When `disable_search` is true the request fee is zero
147
+ // because no web search runs; the operator config-driven check
148
+ // mirrors the adapter's `disable_search` flag exactly.
149
+ let requestCost = 0;
150
+ let citationTokensCost = 0;
151
+ let deepResearchReasoningTokensCost = 0;
152
+ let searchQueriesCost = 0;
153
+ // v3.0.0 R1 fix (codex cross-review catch 2026-05-12): the per-call
154
+ // `usage.search_performed` signal from PerplexityAdapter overrides
155
+ // the global config for request-fee attribution. The relator
156
+ // (generate) role ALWAYS forces disable_search:true on the wire
157
+ // regardless of operator config, so charging a request fee based
158
+ // only on the config would bill for searches that did not run.
159
+ // When the signal is unset (legacy / stub / non-perplexity peers),
160
+ // fall back to the config check to preserve backward compatibility.
161
+ // Defensive against minimal test configs without a `perplexity`
162
+ // sub-config: the gate first narrows by peer === "perplexity", so
163
+ // for any other peer the search-cost block is never entered.
164
+ if (peer === "perplexity") {
165
+ const callSearchPerformed = usage.search_performed ?? !config.perplexity?.disable_search;
166
+ if (callSearchPerformed) {
167
+ const size = config.perplexity?.search_context_size ?? "low";
168
+ const requestFeePer1000 = size === "high"
169
+ ? rate.request_fee_high_per_1000
170
+ : size === "medium"
171
+ ? rate.request_fee_medium_per_1000
172
+ : rate.request_fee_low_per_1000;
173
+ if (typeof requestFeePer1000 === "number" && requestFeePer1000 > 0) {
174
+ requestCost = requestFeePer1000 / 1000;
175
+ }
176
+ }
177
+ }
178
+ if (peer === "perplexity") {
179
+ const citationTokens = usage.citation_tokens ?? 0;
180
+ if (citationTokens > 0 && typeof rate.citation_tokens_per_million === "number") {
181
+ citationTokensCost = (citationTokens / 1_000_000) * rate.citation_tokens_per_million;
182
+ }
183
+ // sonar-deep-research bills reasoning_tokens at a separate rate
184
+ // from output. Other Perplexity models fold reasoning into output
185
+ // (the field is undefined). usage.reasoning_tokens is reused —
186
+ // there's no separate field — and only the presence of the rate
187
+ // config decides whether to bill separately.
188
+ const reasoningTokens = usage.reasoning_tokens ?? 0;
189
+ if (reasoningTokens > 0 &&
190
+ typeof rate.deep_research_reasoning_tokens_per_million === "number") {
191
+ deepResearchReasoningTokensCost =
192
+ (reasoningTokens / 1_000_000) * rate.deep_research_reasoning_tokens_per_million;
193
+ }
194
+ const numSearchQueries = usage.num_search_queries ?? 0;
195
+ if (numSearchQueries > 0 && typeof rate.search_queries_per_1000 === "number") {
196
+ searchQueriesCost = (numSearchQueries / 1000) * rate.search_queries_per_1000;
197
+ }
198
+ }
199
+ const total = inputCost +
200
+ outputCost +
201
+ cacheReadCost +
202
+ cacheWriteCost +
203
+ requestCost +
204
+ citationTokensCost +
205
+ deepResearchReasoningTokensCost +
206
+ searchQueriesCost;
207
+ const base = {
208
+ currency: "USD",
209
+ input_cost: inputCost,
210
+ output_cost: outputCost,
211
+ total_cost: total,
212
+ estimated: true,
213
+ source: "configured-rate",
214
+ };
215
+ if (cacheReadCost > 0)
216
+ base.cache_read_cost = cacheReadCost;
217
+ if (cacheWriteCost > 0)
218
+ base.cache_write_cost = cacheWriteCost;
219
+ if (requestCost > 0)
220
+ base.request_cost = requestCost;
221
+ if (citationTokensCost > 0)
222
+ base.citation_tokens_cost = citationTokensCost;
223
+ if (deepResearchReasoningTokensCost > 0) {
224
+ base.deep_research_reasoning_tokens_cost = deepResearchReasoningTokensCost;
225
+ }
226
+ if (searchQueriesCost > 0)
227
+ base.search_queries_cost = searchQueriesCost;
228
+ // Surface the selected tier (priority: extended/promo over base) for
229
+ // FinOps audit. When categories disagree (rare; only when promo or
230
+ // extended apply to one but not the other), pick input's tier as the
231
+ // representative — operators reading reports care most about which
232
+ // BILLING regime the call landed under.
233
+ if (inputSel)
234
+ base.tier_used = inputSel.tier_used;
235
+ // v2.21.0 (caching): when cache telemetry is present, populate
236
+ // savings on the CostEstimate so dashboards + reports see it next to
237
+ // input/output cost.
238
+ if (cacheReadTokens > 0 || cacheWriteTokens > 0) {
239
+ const savings = estimateCacheSavings(peer, usage, rate);
240
+ if (savings.unknown) {
241
+ base.cache_savings_unknown = true;
242
+ }
243
+ else if (savings.savings_usd > 0) {
244
+ base.cache_savings_usd = savings.savings_usd;
245
+ }
246
+ }
247
+ return base;
248
+ }
249
+ export function mergeCost(costs) {
250
+ let known = false;
251
+ let total = 0;
252
+ let cacheRead = 0;
253
+ let cacheWrite = 0;
254
+ let savings = 0;
255
+ let savingsKnown = false;
256
+ let savingsUnknown = false;
257
+ // v3.0.0 (Perplexity 6th peer): accumulate the four Perplexity-specific
258
+ // line items so multi-call sessions show the full pricing breakdown
259
+ // in session reports + the dashboard. These remain zero for sessions
260
+ // that don't include perplexity peer calls.
261
+ let request = 0;
262
+ let citationTokens = 0;
263
+ let deepResearchReasoningTokens = 0;
264
+ let searchQueries = 0;
265
+ for (const cost of costs) {
266
+ if (cost?.total_cost == null) {
267
+ // continue to inspect savings even when total is missing
268
+ }
269
+ else {
270
+ known = true;
271
+ total += cost.total_cost;
272
+ }
273
+ if (cost?.cache_read_cost != null)
274
+ cacheRead += cost.cache_read_cost;
275
+ if (cost?.cache_write_cost != null)
276
+ cacheWrite += cost.cache_write_cost;
277
+ if (cost?.cache_savings_usd != null) {
278
+ savings += cost.cache_savings_usd;
279
+ savingsKnown = true;
280
+ }
281
+ if (cost?.cache_savings_unknown) {
282
+ savingsUnknown = true;
283
+ }
284
+ if (cost?.request_cost != null)
285
+ request += cost.request_cost;
286
+ if (cost?.citation_tokens_cost != null)
287
+ citationTokens += cost.citation_tokens_cost;
288
+ if (cost?.deep_research_reasoning_tokens_cost != null) {
289
+ deepResearchReasoningTokens += cost.deep_research_reasoning_tokens_cost;
290
+ }
291
+ if (cost?.search_queries_cost != null)
292
+ searchQueries += cost.search_queries_cost;
293
+ }
294
+ if (!known) {
295
+ return { currency: "USD", estimated: false, source: "unknown-rate" };
296
+ }
297
+ const merged = {
298
+ currency: "USD",
299
+ total_cost: total,
300
+ estimated: true,
301
+ source: "configured-rate",
302
+ };
303
+ if (cacheRead > 0)
304
+ merged.cache_read_cost = cacheRead;
305
+ if (cacheWrite > 0)
306
+ merged.cache_write_cost = cacheWrite;
307
+ if (savingsKnown && savings > 0)
308
+ merged.cache_savings_usd = savings;
309
+ if (savingsUnknown)
310
+ merged.cache_savings_unknown = true;
311
+ if (request > 0)
312
+ merged.request_cost = request;
313
+ if (citationTokens > 0)
314
+ merged.citation_tokens_cost = citationTokens;
315
+ if (deepResearchReasoningTokens > 0) {
316
+ merged.deep_research_reasoning_tokens_cost = deepResearchReasoningTokens;
317
+ }
318
+ if (searchQueries > 0)
319
+ merged.search_queries_cost = searchQueries;
320
+ return merged;
321
+ }
322
+ /**
323
+ * v2.21.0 (caching): estimate the savings that flowed from a cache hit
324
+ * vs the fresh input rate for the same peer. Returns { unknown: true }
325
+ * when the rate card has no entry for the provider — operators see
326
+ * "we got a cache hit but cannot price it" instead of a silent zero.
327
+ */
328
+ /**
329
+ * v2.26.0: estimate cache-read savings using ONLY env-configured rates
330
+ * via selectRate(). The legacy `cache-rates.json` fallback was removed
331
+ * per operator directive 2026-05-11 ("nada hardcoded para preços
332
+ * financeiros") — when an operator omits cache rate env vars, the
333
+ * intelligent fallback in selectRate() treats cache reads as priced at
334
+ * the input rate (zero savings) rather than synthesizing a fictional
335
+ * cached rate from a hardcoded JSON. Returns `unknown: true` only when
336
+ * no configRate is provided at all (defensive — `estimateCost()` already
337
+ * short-circuits with "unknown-rate" before reaching this path).
338
+ */
339
+ export function estimateCacheSavings(peer, usage, configRate) {
340
+ void peer; // peer is kept in the signature for API stability + future telemetry
341
+ const readTokens = usage.cache_read_tokens ?? 0;
342
+ if (readTokens <= 0)
343
+ return { savings_usd: 0, unknown: false };
344
+ if (!configRate)
345
+ return { savings_usd: 0, unknown: true };
346
+ const totalInputForTier = (usage.input_tokens ?? 0) + readTokens + (usage.cache_write_tokens ?? 0);
347
+ const freshSel = selectRate(configRate, "input", totalInputForTier);
348
+ const cachedSel = selectRate(configRate, "cache_read", totalInputForTier);
349
+ // Both should be defined because input is required and cache_read
350
+ // gracefully degrades to input. Defensive guard for type narrowing.
351
+ if (!freshSel || !cachedSel)
352
+ return { savings_usd: 0, unknown: true };
353
+ if (freshSel.rate_per_million <= cachedSel.rate_per_million) {
354
+ return { savings_usd: 0, unknown: false };
355
+ }
356
+ const delta = freshSel.rate_per_million - cachedSel.rate_per_million;
357
+ return { savings_usd: (delta * readTokens) / 1_000_000, unknown: false };
358
+ }
359
+ //# sourceMappingURL=cost.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost.js","sourceRoot":"","sources":["../../../src/core/cost.ts"],"names":[],"mappings":"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"}