1import type { UsageModelPriceOverride } from "@t3tools/contracts";
2
3export const USAGE_PRICE_FIELDS = [
4 { key: "inputCostPerMillionTokens", label: "Input", optional: false },
5 { key: "outputCostPerMillionTokens", label: "Output", optional: false },
6 { key: "cacheReadCostPerMillionTokens", label: "Cache read", optional: true },
7 { key: "cacheWriteCostPerMillionTokens", label: "Cache write", optional: true },
8] as const;
9
10export type UsagePriceForm = { model: string } & Record<
11 (typeof USAGE_PRICE_FIELDS)[number]["key"],
12 string
13>;
14
15export function usagePriceForm(model = "", price?: UsageModelPriceOverride): UsagePriceForm {
16 return {
17 model,
18 inputCostPerMillionTokens: price?.inputCostPerMillionTokens.toString() ?? "",
19 outputCostPerMillionTokens: price?.outputCostPerMillionTokens.toString() ?? "",
20 cacheReadCostPerMillionTokens: price?.cacheReadCostPerMillionTokens?.toString() ?? "",
21 cacheWriteCostPerMillionTokens: price?.cacheWriteCostPerMillionTokens?.toString() ?? "",
22 };
23}
24
25/** Blank cache prices use the input rate; explicit zero means free. */
26export function parseUsagePriceForm(
27 form: UsagePriceForm,
28): { model: string; price: UsageModelPriceOverride } | null {
29 const model = form.model.trim();
30 if (model.length === 0) return null;
31 const rates: Partial<Record<(typeof USAGE_PRICE_FIELDS)[number]["key"], number>> = {};
32 for (const field of USAGE_PRICE_FIELDS) {
33 const raw = form[field.key].trim();
34 if (raw === "") {
35 if (field.optional) continue;
36 return null;
37 }
38 if (!/^(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i.test(raw)) return null;
39 const value = Number(raw);
40 if (!Number.isFinite(value) || value < 0) return null;
41 rates[field.key] = value;
42 }
43 if (
44 rates.inputCostPerMillionTokens === undefined ||
45 rates.outputCostPerMillionTokens === undefined
46 ) {
47 return null;
48 }
49 return {
50 model,
51 price: {
52 inputCostPerMillionTokens: rates.inputCostPerMillionTokens,
53 outputCostPerMillionTokens: rates.outputCostPerMillionTokens,
54 ...(rates.cacheReadCostPerMillionTokens === undefined
55 ? {}
56 : { cacheReadCostPerMillionTokens: rates.cacheReadCostPerMillionTokens }),
57 ...(rates.cacheWriteCostPerMillionTokens === undefined
58 ? {}
59 : { cacheWriteCostPerMillionTokens: rates.cacheWriteCostPerMillionTokens }),
60 },
61 };
62}