@navels/neal 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,124 @@
1
+ // Shared pricing type and rate math for token-only provider adapters
2
+ // (openai-compatible, generic-agentic, and openai-codex). Those adapters report
3
+ // token counts but not dollars. Cost is resolved per bucket in this order:
4
+ // operator-configured per-million rates under
5
+ // `providers.openai_compatible.pricing` (the override tier), else the vendored
6
+ // published rate card (`rate-card.ts`, keyed by exact model slug), else
7
+ // tokens-only (`null` — Neal never invents dollars). The Claude adapter does
8
+ // not use this: it passes through the provider-reported `total_cost_usd`
9
+ // instead, and that provider-reported cost always wins upstream (it never
10
+ // reaches `resolveRateCost`).
11
+ import { RATE_CARD } from './rate-card.js';
12
+ // Same coercion as `numberValue` in run-metrics.ts: non-number or non-finite
13
+ // values normalize to 0 so the arithmetic never yields NaN.
14
+ function num(value) {
15
+ return typeof value === 'number' && Number.isFinite(value) ? value : 0;
16
+ }
17
+ /**
18
+ * Rate-compute the USD cost of one turn's token usage.
19
+ *
20
+ * Field-to-rate mapping (kept explicit so the math is reproducible from the
21
+ * source alone):
22
+ *
23
+ * - `totalInput = input_tokens + inputTokens` — the reported prompt/input
24
+ * count. For OpenAI-compatible Chat Completions this count is *inclusive of*
25
+ * cached tokens (`prompt_tokens` already contains
26
+ * `prompt_tokens_details.cached_tokens`; the AI SDK surfaces these as
27
+ * `inputTokens` and `cachedInputTokens`).
28
+ * - `cachedInput = cached_input_tokens + cachedInputTokens +
29
+ * cache_read_input_tokens + cacheReadInputTokens` — tokens billed at the
30
+ * cached rate.
31
+ * - `billedUncachedInput = max(0, totalInput - cachedInput)` — cached tokens
32
+ * are subtracted from the inclusive total so a cached token is billed once,
33
+ * at the cached rate, never also at the full input rate. The `max(0, ...)`
34
+ * clamp is the defined handling for inconsistent counts (cached reported
35
+ * greater than total): treat the excess as fully cached rather than emitting a
36
+ * negative term.
37
+ * - `output = output_tokens + outputTokens` — already includes reasoning
38
+ * tokens for these providers, so reasoning output is not added separately.
39
+ * - Cache-creation tokens (`cache_creation_input_tokens`) are an Anthropic-only
40
+ * concept billed via provider-reported cost, not by these rates, so they are
41
+ * intentionally excluded here.
42
+ *
43
+ * Returns 0 (never NaN) when no tokens are present.
44
+ */
45
+ export function computeRateCostUsd(usage, pricing) {
46
+ const value = (usage && typeof usage === 'object' && !Array.isArray(usage)
47
+ ? usage
48
+ : {});
49
+ const totalInput = num(value.input_tokens) + num(value.inputTokens);
50
+ const cachedInput = num(value.cached_input_tokens) +
51
+ num(value.cachedInputTokens) +
52
+ num(value.cache_read_input_tokens) +
53
+ num(value.cacheReadInputTokens);
54
+ const billedUncachedInput = Math.max(0, totalInput - cachedInput);
55
+ const output = num(value.output_tokens) + num(value.outputTokens);
56
+ return ((billedUncachedInput / 1e6) * pricing.inputPerMillion +
57
+ (cachedInput / 1e6) * pricing.cachedInputPerMillion +
58
+ (output / 1e6) * pricing.outputPerMillion);
59
+ }
60
+ const PER_TOKEN_TO_PER_MILLION = 1e6;
61
+ function isFiniteNonNegativeNumber(value) {
62
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0;
63
+ }
64
+ /**
65
+ * Resolve a model slug to per-million pricing from the vendored rate card.
66
+ *
67
+ * Exact-match only: the slug is looked up (after trimming surrounding
68
+ * whitespace) as a literal key. No provider prefix is stripped and there is no
69
+ * basename fallback, so a slash-qualified slug like `local/gpt-5.5`,
70
+ * `azure/<deployment>`, or a finetune whose basename collides with a listed
71
+ * model does NOT inherit that model's price — pricing an unlisted slug would
72
+ * invent dollars, which the never-invent contract forbids. Provider-qualified
73
+ * slugs are priced only when the card carries that exact key (LiteLLM lists many
74
+ * `vendor/model` keys directly).
75
+ *
76
+ * Returns null for a non-string / empty / whitespace-only model, for an unknown
77
+ * slug, and for a card entry whose input or output cost is not a finite
78
+ * non-negative number (a partial entry is a miss, never a partial rate). The
79
+ * per-token rates are converted to per-million. The cached rate uses
80
+ * `cacheReadInputTokenCost` when it is a finite non-negative number, else falls
81
+ * back to the input rate (no published cache discount means cached reads are
82
+ * billed at the input rate — never free, never an invented discount).
83
+ */
84
+ export function lookupCardPricing(model, card = RATE_CARD) {
85
+ if (typeof model !== 'string') {
86
+ return null;
87
+ }
88
+ const key = model.trim();
89
+ if (key === '') {
90
+ return null;
91
+ }
92
+ const entry = card[key];
93
+ if (entry === undefined) {
94
+ return null;
95
+ }
96
+ if (!isFiniteNonNegativeNumber(entry.inputCostPerToken) || !isFiniteNonNegativeNumber(entry.outputCostPerToken)) {
97
+ return null;
98
+ }
99
+ const cachedPerToken = isFiniteNonNegativeNumber(entry.cacheReadInputTokenCost)
100
+ ? entry.cacheReadInputTokenCost
101
+ : entry.inputCostPerToken;
102
+ return {
103
+ inputPerMillion: entry.inputCostPerToken * PER_TOKEN_TO_PER_MILLION,
104
+ cachedInputPerMillion: cachedPerToken * PER_TOKEN_TO_PER_MILLION,
105
+ outputPerMillion: entry.outputCostPerToken * PER_TOKEN_TO_PER_MILLION,
106
+ };
107
+ }
108
+ /**
109
+ * Resolve a turn's rate-computed cost for a single provider/role bucket.
110
+ *
111
+ * Resolution order per bucket: operator-configured `configPricing` (the
112
+ * override tier) beats the vendored card (`lookupCardPricing(model)`). Returns
113
+ * null when neither yields pricing (tokens-only). When pricing is found, returns
114
+ * the flat base-tier computation from `computeRateCostUsd` tagged
115
+ * `costSource: 'rate'`; no tiered/threshold logic is applied, so a card-listed
116
+ * model is priced at its published base rates regardless of prompt length.
117
+ */
118
+ export function resolveRateCost(args) {
119
+ const pricing = args.configPricing ?? lookupCardPricing(args.model, args.card ?? RATE_CARD);
120
+ if (!pricing) {
121
+ return null;
122
+ }
123
+ return { costUsd: computeRateCostUsd(args.usage, pricing), costSource: 'rate' };
124
+ }