@trendyvideo/billing 1.2.1 → 1.4.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.
package/dist/pricing.d.ts CHANGED
@@ -6,26 +6,107 @@ export declare const MODEL_PRICING: Record<string, ModelPricing>;
6
6
  */
7
7
  export declare const DEFAULT_PRICING: ModelPricing;
8
8
  /**
9
- * Look up pricing for a model. Tries exact match first, then prefix match
10
- * (stripping date suffixes like `-20250514`), then falls back to DEFAULT_PRICING.
9
+ * Look up the raw pricing row for a model, ignoring any scheduled change.
10
+ * Tries exact match first, then prefix match, then strips a trailing date
11
+ * suffix (like `-20250514`). Returns `undefined` when nothing matches.
11
12
  */
12
- export declare function getPricing(model: string): ModelPricing;
13
+ export declare function findPricingRow(model: string): ModelPricing | undefined;
13
14
  /**
14
- * Markup applied to all API costs to ensure profit margin.
15
- * 1.5x means we charge 50% more than raw cost → ~33% margin.
16
- * Applied to both token-based costs AND flat-rate overrides.
15
+ * Look up pricing for a model as of `at` (default: now). Resolution order is
16
+ * exact match, prefix match, date-suffix strip, then DEFAULT_PRICING.
17
+ * Rows with a `scheduledChange` return the scheduled rates once `at` reaches
18
+ * the effective date.
19
+ */
20
+ export declare function getPricing(model: string, at?: Date): ModelPricing;
21
+ /**
22
+ * Does this provider report cached tokens as a SUBSET of `inputTokens`?
23
+ *
24
+ * Gemini's `usageMetadata.promptTokenCount` already includes
25
+ * `cachedContentTokenCount`, and the provider forwards them as
26
+ * `inputTokens` / `cacheReadTokens` unchanged. Anthropic reports
27
+ * `input_tokens` and `cache_read_input_tokens` disjointly. Billing the
28
+ * Gemini pair with the Anthropic formula charges every cached token twice
29
+ * (once at the input rate inside `inputTokens`, again at the cache-read
30
+ * rate). See docs/architecture/gemini-cache-with-tools.md §5.
31
+ */
32
+ export declare function promptTokensIncludeCacheReads(model: string): boolean;
33
+ /**
34
+ * Margin applied to every charge. 1.5x means the customer pays 50% more than
35
+ * the provider charges us (~33% margin).
36
+ *
37
+ * WHERE it is applied (Sully, 2026-09-04: "token to credit should be exact,
38
+ * markup after conversion"): to the CREDIT figure, in `calculateBilledCredits`,
39
+ * after the exact microdollar-to-credit conversion. It is NEVER applied to
40
+ * `cost_microdollars`: that column stores the true provider cost so it
41
+ * reconciles against Google's and Anthropic's invoices. `recordUsage` is the
42
+ * only writer and runs the two steps in that order.
43
+ *
44
+ * History: 1.0.0 to 1.2.1 multiplied inside `calculateCostMicrodollars`, so
45
+ * the margin was baked into the stored cost. #118 made that function return
46
+ * raw cost and nothing picked the markup back up (zero margin, never
47
+ * published). PR #255 first restored it on the microdollar side; this
48
+ * revision moves it to the credit side per the decision above.
49
+ *
50
+ * Callers that pass a `costMicrodollarsOverride` (voice, Seedance, image gen,
51
+ * music) pass the RAW provider cost. It is stored as-is and marked up on the
52
+ * credit side like everything else.
17
53
  */
18
54
  export declare const COST_MARKUP = 1.5;
19
55
  /**
20
- * Calculate cost in microdollars (1 microdollar = $0.000001).
56
+ * True provider cost in microdollars (1 microdollar = $0.000001), NO markup.
21
57
  *
22
58
  * Since pricing is per-million tokens:
23
59
  * cost_usd = (tokens / 1_000_000) * rate_per_million
24
60
  * cost_microdollars = cost_usd * 1_000_000 = tokens * rate_per_million
25
61
  *
26
- * All costs include COST_MARKUP (1.5x) for profit margin.
62
+ * This is what Google or Anthropic invoices us and what `recordUsage` stores
63
+ * in `usage_events.cost_microdollars`. It is NOT what the customer is
64
+ * charged: `calculateBilledCredits` derives `credits_consumed` from it.
65
+ *
66
+ * Rounding: the exact figure is rounded to the nearest whole microdollar
67
+ * because the column is `bigint`. Maximum error 0.5 µ$ ($0.0000005), below
68
+ * any invoice's precision. An override gets the same treatment (callers pass
69
+ * integers, so in practice it passes through untouched). The full rounding
70
+ * rule for a charge is documented once, on `calculateBilledCredits`.
71
+ */
72
+ export declare function calculateCostMicrodollars(model: string, inputTokens: number, outputTokens: number, cacheReadTokens?: number, cacheCreationTokens?: number, costMicrodollarsOverride?: number, at?: Date): number;
73
+ export type GeminiEmbedding2Modality = 'text' | 'image' | 'audio' | 'video';
74
+ /** USD per 1M input tokens, by modality. */
75
+ export declare const GEMINI_EMBEDDING_2_INPUT_PRICING: Record<GeminiEmbedding2Modality, number>;
76
+ /** Per-unit equivalents (raw microdollars) for when the API reports no token count. */
77
+ export declare const GEMINI_EMBEDDING_2_UNIT_RATES: {
78
+ readonly microdollarsPerImage: 120;
79
+ readonly microdollarsPerAudioSecond: 160;
80
+ readonly microdollarsPerVideoFrame: 790;
81
+ readonly videoMaxFrames: 32;
82
+ /** Rough text fallback: ~4 chars per token. */
83
+ readonly charsPerTextToken: 4;
84
+ };
85
+ /** Frames Google samples from a video of `durationSec` (1 fps, capped at 32). */
86
+ export declare function geminiEmbedding2VideoFrames(durationSec: number): number;
87
+ export interface GeminiEmbedding2CostInput {
88
+ modality: GeminiEmbedding2Modality;
89
+ /** `usageMetadata.promptTokenCount` from the embedContent response, when present. */
90
+ promptTokens?: number | undefined;
91
+ /** Fallback inputs, used only when `promptTokens` is absent. */
92
+ textChars?: number | undefined;
93
+ imageCount?: number | undefined;
94
+ durationSec?: number | undefined;
95
+ }
96
+ export interface GeminiEmbedding2Cost {
97
+ /** RAW microdollars — pass as `costMicrodollarsOverride`; COST_MARKUP is applied by recordUsage. */
98
+ costMicrodollars: number;
99
+ /** Token count to store as `inputTokens` (reported, or estimated for text). 0 when unknown. */
100
+ inputTokens: number;
101
+ basis: 'reported_tokens' | 'estimated_units';
102
+ }
103
+ /**
104
+ * Raw cost of one `gemini-embedding-2:embedContent` call. Prefers the token
105
+ * count Google reports; otherwise falls back to the published per-unit rate
106
+ * for the modality. Always returns a cost for a non-empty input so a call can
107
+ * never be recorded as free by accident.
27
108
  */
28
- export declare function calculateCostMicrodollars(model: string, inputTokens: number, outputTokens: number, cacheReadTokens?: number, cacheCreationTokens?: number, costMicrodollarsOverride?: number): number;
109
+ export declare function calculateGeminiEmbedding2CostMicrodollars(input: GeminiEmbedding2CostInput): GeminiEmbedding2Cost;
29
110
  export interface MediaRate {
30
111
  /** Pricing shape: per-unit (e.g. one image) or per-second (e.g. a video clip). */
31
112
  unit: 'per_clip' | 'per_second';
@@ -38,8 +119,9 @@ export declare const MEDIA_RATE_PRICING: Record<string, MediaRate>;
38
119
  /**
39
120
  * Estimate the RAW microdollar cost of a media-generation call before
40
121
  * COST_MARKUP is applied. Callers pass the result as
41
- * `costMicrodollarsOverride` into `recordUsage`; the markup is applied
42
- * inside `calculateCostMicrodollars`.
122
+ * `costMicrodollarsOverride` into `recordUsage`, which stores it as the
123
+ * row's `cost_microdollars` and applies the markup on the credit side via
124
+ * `calculateBilledCredits`.
43
125
  *
44
126
  * - For `per_clip` rates, pass `units` (default 1).
45
127
  * - For `per_second` rates, pass `durationSec`.
@@ -52,15 +134,70 @@ export declare function estimateMediaCostMicrodollars(model: string, opts?: {
52
134
  units?: number;
53
135
  }): number;
54
136
  /**
55
- * Same estimate but with COST_MARKUP applied i.e. what the user is
56
- * actually billed. Use this for cost-preview UIs that need to match
57
- * the final `usage_events.credits_consumed` write.
137
+ * Same estimate with COST_MARKUP applied, still in microdollars: what the
138
+ * user is billed, expressed as credits × 10,000. Use it for cost-preview
139
+ * UIs. `microdollarsToCredits` of this value equals the `credits_consumed`
140
+ * that `recordUsage` writes for the raw estimate, to the 0.01 credit, for
141
+ * every rate in the table (tests in usage-tracker.test.ts). It is NOT what
142
+ * `cost_microdollars` stores; that column holds the raw estimate.
58
143
  */
59
144
  export declare function estimateBilledMediaCostMicrodollars(model: string, opts?: {
60
145
  durationSec?: number;
61
146
  units?: number;
62
147
  }): number;
63
148
  export declare const MICRODOLLARS_PER_CREDIT = 10000;
149
+ /**
150
+ * Trendy Voice (Gemini Live) per-minute billing rate.
151
+ *
152
+ * 12 credits = $0.12 = 120,000 microdollars per session-minute.
153
+ *
154
+ * Minutes are rounded UP — partial minutes are billed as a full minute.
155
+ * Fairness goes to Trendy on the round-up; very short sessions (<10s)
156
+ * are not billed at all (see the meter's startup guard).
157
+ */
158
+ export declare const TRENDY_VOICE_CREDITS_PER_MINUTE = 12;
159
+ export declare const TRENDY_VOICE_MICRODOLLARS_PER_MINUTE: number;
160
+ export declare const TRENDY_VOICE_SKU: "trendy_voice_minute";
161
+ /**
162
+ * Precision of `usage_events.credits_consumed` (`numeric(12,2)`): charges are
163
+ * kept to 0.01 credit = 100 µ$ = $0.0001.
164
+ */
165
+ export declare const CREDIT_DECIMALS = 2;
166
+ /** Exact conversion. No rounding, no markup. */
64
167
  export declare function microdollarsToCredits(microdollars: number): number;
65
168
  export declare function creditsToMicrodollars(credits: number): number;
169
+ /**
170
+ * Customer-billed credits for a true-cost `costMicrodollars` figure. This is
171
+ * the ONLY place COST_MARKUP is applied and the ONLY place a charge is
172
+ * rounded. `recordUsage` writes the result to `credits_consumed`.
173
+ *
174
+ * billed credits = round2( (cost_microdollars / 10,000) × COST_MARKUP )
175
+ *
176
+ * 1. exact conversion cost_microdollars / MICRODOLLARS_PER_CREDIT
177
+ * 2. margin × COST_MARKUP
178
+ * 3. one rounding half up, to 0.01 credit (CREDIT_DECIMALS), so the
179
+ * value passed to the RPC is the value stored and
180
+ * SQL reproduces it exactly:
181
+ * ROUND(cost_microdollars * 1.5 / 10000, 2)
182
+ *
183
+ * THE ROUNDING RULE, in full. Nothing else in this package rounds a charge.
184
+ * - `cost_microdollars`: nearest whole microdollar (bigint column), in
185
+ * `calculateCostMicrodollars`. A storage step, not a pricing step.
186
+ * - `credits_consumed`: here, once, half up, to 0.01 credit, computed from
187
+ * the stored `cost_microdollars` so every row is self-consistent.
188
+ * - No rounding up to a whole credit (1.2.1 did that with Math.ceil), no
189
+ * rounding before the markup, no rounding in `microdollarsToCredits`.
190
+ * - A charge below 0.005 credit (true cost of 33 µ$ or less) rounds to
191
+ * 0.00. The `record_usage_atomic` RPC then falls back to
192
+ * ROUND(cost_microdollars / 10000, 2), which is also 0.00 for every such
193
+ * value, so the two never disagree (test in pricing.test.ts).
194
+ *
195
+ * Implementation note: steps 1 and 2 commute, and the arithmetic is done in
196
+ * whole microdollars (`cost × 1.5` is exact for any integer) before the one
197
+ * division, because `1500 / 10000 * 1.5` is 0.224999… in floating point and
198
+ * would round to 0.22 instead of 0.23. `pricing.test.ts` checks the result
199
+ * against an integer reference implementation for every value up to
200
+ * 5,000,000 µ$ and a set of larger ones.
201
+ */
202
+ export declare function calculateBilledCredits(costMicrodollars: number): number;
66
203
  //# sourceMappingURL=pricing.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAOvC,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAkDtD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,YAK7B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAgBtD;AAED;;;;GAIG;AACH,eAAO,MAAM,WAAW,MAAM,CAAC;AAE/B;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,eAAe,SAAI,EACnB,mBAAmB,SAAI,EACvB,wBAAwB,CAAC,EAAE,MAAM,GAChC,MAAM,CAaR;AAaD,MAAM,WAAW,SAAS;IACxB,kFAAkF;IAClF,IAAI,EAAE,UAAU,GAAG,YAAY,CAAC;IAChC,yCAAyC;IACzC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CA2CxD,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,MAAM,EACb,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAClD,MAAM,CASR;AAED;;;;GAIG;AACH,wBAAgB,mCAAmC,CACjD,KAAK,EAAE,MAAM,EACb,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAClD,MAAM,CAER;AAMD,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAElE;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE7D"}
1
+ {"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AA4BvC,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAiGtD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,YAK7B,CAAC;AAoBF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAgBtE;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,GAAE,IAAiB,GAAG,YAAY,CAI7E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEpE;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,WAAW,MAAM,CAAC;AAE/B;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,eAAe,SAAI,EACnB,mBAAmB,SAAI,EACvB,wBAAwB,CAAC,EAAE,MAAM,EACjC,EAAE,GAAE,IAAiB,GACpB,MAAM,CA4BR;AAiBD,MAAM,MAAM,wBAAwB,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;AAE5E,4CAA4C;AAC5C,eAAO,MAAM,gCAAgC,EAAE,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAKrF,CAAC;AAEF,uFAAuF;AACvF,eAAO,MAAM,6BAA6B;;;;;IAKxC,+CAA+C;;CAEvC,CAAC;AAEX,iFAAiF;AACjF,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAGvE;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,wBAAwB,CAAC;IACnC,qFAAqF;IACrF,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AAED,MAAM,WAAW,oBAAoB;IACnC,oGAAoG;IACpG,gBAAgB,EAAE,MAAM,CAAC;IACzB,+FAA+F;IAC/F,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,iBAAiB,GAAG,iBAAiB,CAAC;CAC9C;AAED;;;;;GAKG;AACH,wBAAgB,yCAAyC,CACvD,KAAK,EAAE,yBAAyB,GAC/B,oBAAoB,CAsCtB;AAcD,MAAM,WAAW,SAAS;IACxB,kFAAkF;IAClF,IAAI,EAAE,UAAU,GAAG,YAAY,CAAC;IAChC,yCAAyC;IACzC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAqFxD,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,MAAM,EACb,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAClD,MAAM,CASR;AAED;;;;;;;GAOG;AACH,wBAAgB,mCAAmC,CACjD,KAAK,EAAE,MAAM,EACb,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAClD,MAAM,CAER;AAMD,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C;;;;;;;;GAQG;AACH,eAAO,MAAM,+BAA+B,KAAK,CAAC;AAClD,eAAO,MAAM,oCAAoC,QACU,CAAC;AAC5D,eAAO,MAAM,gBAAgB,EAAG,qBAA8B,CAAC;AAE/D;;;GAGG;AACH,eAAO,MAAM,eAAe,IAAI,CAAC;AAKjC,gDAAgD;AAChD,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAElE;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,sBAAsB,CAAC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAIvE"}