-
Notifications
You must be signed in to change notification settings - Fork 61
Expand file tree
/
Copy pathusageDisplay.ts
More file actions
124 lines (111 loc) · 3.77 KB
/
Copy pathusageDisplay.ts
File metadata and controls
124 lines (111 loc) · 3.77 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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import type { UsageBucket, UsageOutput } from "../usage/schemas";
export const CODE_INCLUDED_USAGE_USD = 20;
/** Confirmed free tier only — an absent `code_usage_subscribed` is unknown, never free. */
export function isCodeUsageFreeTier(
usage: Pick<UsageOutput, "code_usage_subscribed"> | null | undefined,
): boolean {
return usage?.code_usage_subscribed === false;
}
export function codeOrgSpendLimitUsd(
usage:
| Pick<UsageOutput, "code_usage_subscribed" | "ai_credits">
| null
| undefined,
): number | null {
if (usage?.code_usage_subscribed !== true) return null;
const limitUsd = usage.ai_credits?.limit_usd;
if (limitUsd == null || limitUsd < CODE_INCLUDED_USAGE_USD) return null;
return Math.round((limitUsd - CODE_INCLUDED_USAGE_USD) * 100) / 100;
}
export interface CodeUsageBreakdown {
includedUsd: number;
spendLimitUsd: number;
}
export type CodeUsageMeter =
| {
kind: "dollars";
usedUsd: number;
limitUsd: number;
percent: number;
exceeded: boolean;
resetAt: string;
breakdown: CodeUsageBreakdown | null;
}
| { kind: "bucket"; bucket: UsageBucket }
| { kind: "hidden" };
/**
* What the usage meter should show. Billing's org-level dollars win when
* present; a free-tier org without them falls back to its per-user valve
* bucket; anything else shows nothing — per-user valve percentages are
* meaningless for a subscribed org, and unknown must not render as free.
*/
export function codeUsageMeter(
usage: UsageOutput | null | undefined,
): CodeUsageMeter {
if (!usage) return { kind: "hidden" };
const usedUsd = usage.ai_credits?.used_usd;
const limitUsd = usage.ai_credits?.limit_usd;
if (usedUsd != null && limitUsd != null && limitUsd > 0) {
const spendLimitUsd = codeOrgSpendLimitUsd(usage);
return {
kind: "dollars",
usedUsd,
limitUsd,
percent: Math.min(100, Math.round((usedUsd / limitUsd) * 100)),
exceeded: usage.ai_credits?.exhausted === true,
resetAt: usage.billing_period_end ?? usage.sustained.reset_at,
breakdown:
spendLimitUsd != null
? { includedUsd: CODE_INCLUDED_USAGE_USD, spendLimitUsd }
: null,
};
}
if (isCodeUsageFreeTier(usage)) {
return { kind: "bucket", bucket: usage.sustained };
}
return { kind: "hidden" };
}
export function formatUsdAmount(amount: number): string {
return Number.isInteger(amount) ? `$${amount}` : `$${amount.toFixed(2)}`;
}
export function formatUsageBreakdown(breakdown: CodeUsageBreakdown): string {
return `${formatUsdAmount(breakdown.includedUsd)} included + ${formatUsdAmount(breakdown.spendLimitUsd)} org spend limit`;
}
export function isUsageExceeded(usage: UsageOutput): boolean {
return (
usage.is_rate_limited || usage.sustained.exceeded || usage.burst.exceeded
);
}
export function formatResetTime(
resetAtIso: string,
now: number = Date.now(),
): string {
const parsed = Date.parse(resetAtIso);
const ms = Number.isNaN(parsed) ? 0 : Math.max(0, parsed - now);
const totalMinutes = Math.ceil(ms / 60_000);
if (totalMinutes <= 0) return "Resets shortly";
if (totalMinutes < 60) return `Resets in ${totalMinutes}m`;
const totalHours = ms / 3_600_000;
if (totalHours < 24) {
let hours = Math.floor(totalHours);
let minutes = Math.round((totalHours - hours) * 60);
if (minutes === 60) {
hours += 1;
minutes = 0;
}
return minutes === 0
? `Resets in ${hours}h`
: `Resets in ${hours}h ${minutes}m`;
}
const target = new Date(now + ms);
const date = target.toLocaleDateString(undefined, {
month: "short",
day: "numeric",
});
const time = target.toLocaleTimeString(undefined, {
hour: "numeric",
minute: "2-digit",
timeZoneName: "short",
});
return `Resets ${date} at ${time}`;
}