1import type {
2 EnvironmentId,
3 ServerSettingsPatch,
4 UsageModelPriceOverride,
5} from "@t3tools/contracts";
6
7export interface UsagePriceTarget {
8 readonly environmentId: EnvironmentId;
9 readonly label: string;
10 readonly prices: Readonly<Record<string, UsageModelPriceOverride>> | null;
11 /** Model mappings. `null` when they are not loaded or the server cannot store them. */
12 readonly aliases: Readonly<Record<string, string>> | null;
13 readonly unavailable: string | null;
14}
15
16/** `null` removes the model's custom price or mapping. */
17export type UsagePriceChange =
18 | { readonly model: string; readonly price: UsageModelPriceOverride | null }
19 | { readonly model: string; readonly alias: string | null };
20
21export type UsagePriceWriteResult =
22 | { readonly status: "saved" }
23 | { readonly status: "failed"; readonly error: string };
24
25/**
26 * Each destination settles independently; retry callers pass only the failed destinations.
27 * Prices and mappings for one destination go out in a single patch.
28 */
29export async function writeUsagePrices(input: {
30 readonly targets: readonly UsagePriceTarget[];
31 readonly changes: ReadonlyMap<EnvironmentId, readonly UsagePriceChange[]>;
32 readonly write: (input: {
33 environmentId: EnvironmentId;
34 input: { patch: ServerSettingsPatch };
35 }) => Promise<{ readonly _tag: "Success" | "Failure" }>;
36 readonly onResult: (environmentId: EnvironmentId, result: UsagePriceWriteResult) => void;
37}) {
38 await Promise.all(
39 input.targets.map(async (target) => {
40 let result: UsagePriceWriteResult;
41 const changes = input.changes.get(target.environmentId) ?? [];
42 const prices = changes.flatMap((change) =>
43 "price" in change ? [[change.model, change.price] as const] : [],
44 );
45 const aliases = changes.flatMap((change) =>
46 "alias" in change ? [[change.model, change.alias] as const] : [],
47 );
48 if (target.unavailable !== null) {
49 result = { status: "failed", error: target.unavailable };
50 } else if (aliases.length > 0 && target.aliases === null) {
51 result = { status: "failed", error: "Update server to map models" };
52 } else {
53 try {
54 const saved =
55 changes.length === 0
56 ? { _tag: "Success" as const }
57 : await input.write({
58 environmentId: target.environmentId,
59 input: {
60 patch: {
61 ...(prices.length > 0
62 ? { usagePriceOverrides: Object.fromEntries(prices) }
63 : {}),
64 ...(aliases.length > 0
65 ? { usageModelAliases: Object.fromEntries(aliases) }
66 : {}),
67 },
68 },
69 });
70 result =
71 saved._tag === "Success"
72 ? { status: "saved" }
73 : { status: "failed", error: "Could not save. Try again." };
74 } catch {
75 result = { status: "failed", error: "Could not save. Try again." };
76 }
77 }
78 input.onResult(target.environmentId, result);
79 }),
80 );
81}