Repository navigation
Expand file tree
/
Copy pathusagePriceForm.ts
More file actions
72 lines (67 loc) · 2.86 KB
/
Copy pathusagePriceForm.ts
File metadata and controls
72 lines (67 loc) · 2.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import type { UsageModelPriceOverride } from "@t3tools/contracts";
export const USAGE_PRICE_FIELDS = [
{ key: "inputCostPerMillionTokens", label: "Input", optional: false },
{ key: "outputCostPerMillionTokens", label: "Output", optional: false },
{ key: "cacheReadCostPerMillionTokens", label: "Cache read", optional: true },
{ key: "cacheWriteCostPerMillionTokens", label: "Cache write", optional: true },
] as const;
export type UsagePriceForm = { model: string } & Record<
(typeof USAGE_PRICE_FIELDS)[number]["key"],
string
>;
export function usagePriceForm(model = "", price?: UsageModelPriceOverride): UsagePriceForm {
return {
model,
inputCostPerMillionTokens: price?.inputCostPerMillionTokens.toString() ?? "",
outputCostPerMillionTokens: price?.outputCostPerMillionTokens.toString() ?? "",
cacheReadCostPerMillionTokens: price?.cacheReadCostPerMillionTokens?.toString() ?? "",
cacheWriteCostPerMillionTokens: price?.cacheWriteCostPerMillionTokens?.toString() ?? "",
};
}
/** Allow decimal/exponent edits and all deletions, including temporarily invalid values; saving validates
* the result. */
export function isUsagePriceEdit(previous: string, next: string): boolean {
if (/^\s*(?:\d*[.,]?\d*|(?:\d+[.,]?\d*|[.,]\d+)e[+-]?\d*)\s*$/i.test(next)) return true;
if (next.length >= previous.length) return false;
let start = 0;
while (start < next.length && next[start] === previous[start]) start++;
return next.slice(start) === previous.slice(previous.length - (next.length - start));
}
/** Blank cache prices use the input rate; explicit zero means free. */
export function parseUsagePriceForm(
form: UsagePriceForm,
): { model: string; price: UsageModelPriceOverride } | null {
const model = form.model.trim();
if (model.length === 0) return null;
const rates: Partial<Record<(typeof USAGE_PRICE_FIELDS)[number]["key"], number>> = {};
for (const field of USAGE_PRICE_FIELDS) {
const raw = form[field.key].trim().replace(",", ".");
if (raw === "") {
if (field.optional) continue;
return null;
}
if (!/^(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i.test(raw)) return null;
const value = Number(raw);
if (!Number.isFinite(value) || value < 0) return null;
rates[field.key] = value;
}
if (
rates.inputCostPerMillionTokens === undefined ||
rates.outputCostPerMillionTokens === undefined
) {
return null;
}
return {
model,
price: {
inputCostPerMillionTokens: rates.inputCostPerMillionTokens,
outputCostPerMillionTokens: rates.outputCostPerMillionTokens,
...(rates.cacheReadCostPerMillionTokens === undefined
? {}
: { cacheReadCostPerMillionTokens: rates.cacheReadCostPerMillionTokens }),
...(rates.cacheWriteCostPerMillionTokens === undefined
? {}
: { cacheWriteCostPerMillionTokens: rates.cacheWriteCostPerMillionTokens }),
},
};
}