coding-usage-bar 0.5.1 → 0.6.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/README.md +13 -0
- package/README.zh-CN.md +11 -0
- package/dist/deepseek.js +4 -1
- package/dist/format.js +18 -0
- package/dist/glm.js +8 -2
- package/dist/menubar.js +14 -2
- package/dist/pricing.d.ts +5 -0
- package/dist/pricing.js +115 -0
- package/dist/types.d.ts +9 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -114,6 +114,8 @@ Coding Usage Bar calls the Zhipu AI quota API (`GET /api/monitor/usage/quota/lim
|
|
|
114
114
|
|
|
115
115
|
Get your API key from the [Zhipu AI console](https://open.bigmodel.cn). Without this key, GLM monitoring will report `GLM_API_KEY_MISSING`.
|
|
116
116
|
|
|
117
|
+
Zhipu meters newer Coding Plans in credits (积分) instead of raw tokens and reports them as `CREDIT_LIMIT` windows. Coding Usage Bar keys off the response shape, records `quotaUnit` as `credits` or `tokens` in `status.json`, and labels the dropdown with `metered in credits` so the 5h/7d percentages read correctly for either plan generation. This detection has not been verified against a real V3 (points-based) account yet; it is based on the `CREDIT_LIMIT` shape observed on existing plans. If you are on a V3 plan, a redacted `coding-usage-bar status --refresh` output in an issue would confirm it.
|
|
118
|
+
|
|
117
119
|
## DeepSeek
|
|
118
120
|
|
|
119
121
|
Coding Usage Bar calls `GET https://api.deepseek.com/user/balance` to read your account balance. DeepSeek exposes balance rather than 5h/7d usage windows, so the menu bar shows `Available` or `Depleted` plus the currency amount. Set `deepseek.apiKey` in `~/.coding-usage-bar/config.json`:
|
|
@@ -129,6 +131,17 @@ Coding Usage Bar calls `GET https://api.deepseek.com/user/balance` to read your
|
|
|
129
131
|
|
|
130
132
|
Without this key, DeepSeek monitoring will report `DEEPSEEK_API_KEY_MISSING`.
|
|
131
133
|
|
|
134
|
+
## Peak and off-peak pricing
|
|
135
|
+
|
|
136
|
+
GLM Coding Plan and the DeepSeek API both publish a clock-based discount, and both state it in Beijing time. Coding Usage Bar computes the current tier on the producer side, stores it in `status.json` as `pricing`, and shows it in the CLI table (`price` row), the dropdown, and the usage card, together with the time of the next flip:
|
|
137
|
+
|
|
138
|
+
| Provider | Peak (Asia/Shanghai) | Off-peak | Holidays |
|
|
139
|
+
|---|---|---|---|
|
|
140
|
+
| DeepSeek API | Mon–Fri 09:00–12:00 and 14:00–18:00 | 50% of the peak rate | Chinese public holidays are off-peak all day |
|
|
141
|
+
| GLM Coding Plan | Mon–Fri 14:00–18:00 | credits deducted at 50% | Not excluded by the published rule |
|
|
142
|
+
|
|
143
|
+
Sources: [DeepSeek pricing](https://api-docs.deepseek.com/quick_start/pricing) and the [GLM Coding Plan overview](https://docs.bigmodel.cn/cn/coding-plan/overview). The 2026 holiday table follows the State Council notice (国办发明电〔2025〕7号); adjusted working weekends stay off-peak because both vendors phrase the rule as Monday to Friday. Time-boxed promotions are not modelled. The tier only tells you what the provider charges right now; it does not change the burn analysis.
|
|
144
|
+
|
|
132
145
|
## MiniMax (M3)
|
|
133
146
|
|
|
134
147
|
Coding Usage Bar calls `GET https://api.minimaxi.com/v1/token_plan/remains` to read 5h and 7d usage windows. MiniMax returns two signals per model: a count-based ratio (`current_*_usage_count` / `current_*_total_count`) for tiered models like `video`, and a credit-based `current_*_remaining_percent` (0-100) for the `general` model where the count stays at 0. Coding Usage Bar prefers the count ratio when total > 0 and otherwise derives used% as `100 - remaining_percent`. Set `minimax.apiKey` and (optionally) `minimax.region` in `~/.coding-usage-bar/config.json`:
|
package/README.zh-CN.md
CHANGED
|
@@ -50,6 +50,17 @@ coding-usage-bar status
|
|
|
50
50
|
- Claude Code、Codex 本地优先,不依赖额外遥测服务
|
|
51
51
|
- 展示层只读稳定的 `status.json`,不会在打开菜单时临时访问数据源
|
|
52
52
|
- 接近限额或燃烧节奏异常时发送系统通知
|
|
53
|
+
- 国产套餐规则内置:GLM Coding Plan 与 DeepSeek 的峰谷时段按北京时间计算,法定节假日按国办通知处理,CLI、下拉菜单和卡片都会显示当前是高峰还是谷时、几折、几点切换
|
|
54
|
+
- GLM 新版 Coding Plan 按积分(`CREDIT_LIMIT`)计量时自动识别,标注"按积分计量",5h/7d 百分比不会因为套餐换代而失真。该识别基于现有套餐观测到的 `CREDIT_LIMIT` 形状,尚未用真实 V3 积分制账号实测;V3 用户可在 issue 里贴脱敏的 `coding-usage-bar status --refresh` 输出帮助确认
|
|
55
|
+
|
|
56
|
+
## 峰谷时段
|
|
57
|
+
|
|
58
|
+
| Provider | 高峰(北京时间) | 谷时 | 法定节假日 |
|
|
59
|
+
|---|---|---|---|
|
|
60
|
+
| DeepSeek API | 周一至周五 09:00–12:00、14:00–18:00 | 高峰价的 50% | 全天谷时 |
|
|
61
|
+
| GLM Coding Plan | 周一至周五 14:00–18:00 | 积分按 50% 抵扣 | 官方规则未排除,按工作日字面处理 |
|
|
62
|
+
|
|
63
|
+
依据 [DeepSeek 定价页](https://api-docs.deepseek.com/quick_start/pricing)与 [GLM Coding Plan 概览](https://docs.bigmodel.cn/cn/coding-plan/overview)。2026 年节假日表来自国办发明电〔2025〕7号;调休上班的周末仍按周末处理,因为两家都把规则写成"周一至周五"。限时活动(如双节全天谷时)不建模。峰谷只说明厂商此刻怎么计费,不改变燃烧节奏分析。
|
|
53
64
|
|
|
54
65
|
完整命令、Provider 配置和实现边界请查看[英文 README](README.md)。
|
|
55
66
|
|
package/dist/deepseek.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { deepseekPricing } from "./pricing.js";
|
|
1
2
|
const DEFAULT_BASE_URL = "https://api.deepseek.com";
|
|
2
3
|
export async function fetchDeepseekBalance(config) {
|
|
3
4
|
const url = `${DEFAULT_BASE_URL}/user/balance`;
|
|
@@ -19,11 +20,13 @@ export function usageFromDeepseekBalance(balance, options) {
|
|
|
19
20
|
if (!info) {
|
|
20
21
|
return null;
|
|
21
22
|
}
|
|
23
|
+
const observedAt = options.observedAt ?? new Date().toISOString();
|
|
22
24
|
return {
|
|
23
25
|
provider: "deepseek",
|
|
24
26
|
source: options.source,
|
|
25
|
-
observedAt
|
|
27
|
+
observedAt,
|
|
26
28
|
planType: null,
|
|
29
|
+
pricing: deepseekPricing(new Date(observedAt)),
|
|
27
30
|
windows: [],
|
|
28
31
|
balance: {
|
|
29
32
|
total: info.total_balance,
|
package/dist/format.js
CHANGED
|
@@ -62,10 +62,24 @@ export function usageBar(usedPercent, width = 12) {
|
|
|
62
62
|
const usedCells = Math.max(0, Math.min(width, Math.round((usedPercent / 100) * width)));
|
|
63
63
|
return `[${"#".repeat(usedCells)}${"-".repeat(width - usedCells)}] ${Math.round(usedPercent)}%`;
|
|
64
64
|
}
|
|
65
|
+
function pricingRow(usage) {
|
|
66
|
+
if (!usage.pricing) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const tier = usage.pricing.tier === "off_peak" ? `off-peak ${usage.pricing.discountPercent}% off` : "peak, full price";
|
|
70
|
+
return [
|
|
71
|
+
providerLabel(usage.provider).padEnd(8),
|
|
72
|
+
"price".padEnd(8),
|
|
73
|
+
tier.padEnd(19),
|
|
74
|
+
formatDurationUntil(usage.pricing.until).padEnd(10),
|
|
75
|
+
usage.quotaUnit === "credits" ? "credits" : "",
|
|
76
|
+
].join("");
|
|
77
|
+
}
|
|
65
78
|
export function formatStatusRows(usages, analyses) {
|
|
66
79
|
const rows = ["Provider Period Usage Reset State"];
|
|
67
80
|
for (const usage of usages) {
|
|
68
81
|
const analysis = analyses.find((item) => item.provider === usage.provider);
|
|
82
|
+
const price = pricingRow(usage);
|
|
69
83
|
if (usage.balance && usage.windows.length === 0) {
|
|
70
84
|
const currency = usage.balance.currency === "CNY" ? "¥" : "$";
|
|
71
85
|
rows.push([
|
|
@@ -75,6 +89,8 @@ export function formatStatusRows(usages, analyses) {
|
|
|
75
89
|
"".padEnd(10),
|
|
76
90
|
analysis?.state ?? "RAW",
|
|
77
91
|
].join(""));
|
|
92
|
+
if (price)
|
|
93
|
+
rows.push(price);
|
|
78
94
|
continue;
|
|
79
95
|
}
|
|
80
96
|
for (const window of usage.windows) {
|
|
@@ -86,6 +102,8 @@ export function formatStatusRows(usages, analyses) {
|
|
|
86
102
|
analysis?.state ?? "RAW",
|
|
87
103
|
].join(""));
|
|
88
104
|
}
|
|
105
|
+
if (price)
|
|
106
|
+
rows.push(price);
|
|
89
107
|
}
|
|
90
108
|
return rows.join("\n");
|
|
91
109
|
}
|
package/dist/glm.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { makeProviderUsage, normalizeWindow } from "./usage.js";
|
|
2
|
+
import { glmPricing } from "./pricing.js";
|
|
2
3
|
const DEFAULT_BASE_URL = "https://open.bigmodel.cn";
|
|
3
4
|
export async function fetchGlmQuota(config) {
|
|
4
5
|
const baseUrl = config.baseUrl || DEFAULT_BASE_URL;
|
|
@@ -50,7 +51,7 @@ function pickTokenWindows(limits) {
|
|
|
50
51
|
for (const type of ["TOKENS_LIMIT", "CREDIT_LIMIT"]) {
|
|
51
52
|
const picked = pickTokenWindowsOfType(limits.filter((limit) => limit.type === type));
|
|
52
53
|
if (picked) {
|
|
53
|
-
return picked;
|
|
54
|
+
return { ...picked, unit: type === "CREDIT_LIMIT" ? "credits" : "tokens" };
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
return null;
|
|
@@ -77,7 +78,7 @@ export function usageFromGlmQuota(quota, options) {
|
|
|
77
78
|
if (!fiveHour || !sevenDay) {
|
|
78
79
|
return null;
|
|
79
80
|
}
|
|
80
|
-
|
|
81
|
+
const usage = makeProviderUsage({
|
|
81
82
|
provider: "glm",
|
|
82
83
|
source: options.source,
|
|
83
84
|
observedAt: options.observedAt,
|
|
@@ -85,6 +86,11 @@ export function usageFromGlmQuota(quota, options) {
|
|
|
85
86
|
fiveHour,
|
|
86
87
|
sevenDay,
|
|
87
88
|
});
|
|
89
|
+
return {
|
|
90
|
+
...usage,
|
|
91
|
+
quotaUnit: picked.unit,
|
|
92
|
+
pricing: glmPricing(new Date(usage.observedAt)),
|
|
93
|
+
};
|
|
88
94
|
}
|
|
89
95
|
export async function collectGlmUsage(config) {
|
|
90
96
|
if (!config.apiKey) {
|
package/dist/menubar.js
CHANGED
|
@@ -4,6 +4,7 @@ import { execFileSync } from "node:child_process";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { ensureDir, isFile } from "./fs-util.js";
|
|
6
6
|
import { formatProviderLabel, formatResetTime } from "./format.js";
|
|
7
|
+
import { pricingLabel } from "./pricing.js";
|
|
7
8
|
import { buildPaths } from "./paths.js";
|
|
8
9
|
import { stableNodeExecutable } from "./node-runtime.js";
|
|
9
10
|
import { loadDisplayStatusSnapshot } from "./runtime.js";
|
|
@@ -768,7 +769,9 @@ function cardProviderBlock(item, now) {
|
|
|
768
769
|
darkColor,
|
|
769
770
|
headline: `${currency}${item.usage.balance.total}`,
|
|
770
771
|
rows: [],
|
|
771
|
-
note:
|
|
772
|
+
note: item.usage.pricing
|
|
773
|
+
? `Pay-as-you-go · ${pricingLabel(item.usage.pricing, formatResetTime(item.usage.pricing.until, now))}`
|
|
774
|
+
: "Pay-as-you-go · no windowed quota",
|
|
772
775
|
message: null,
|
|
773
776
|
updated: observedUpdatedLabel(item, now),
|
|
774
777
|
};
|
|
@@ -792,7 +795,9 @@ function cardProviderBlock(item, now) {
|
|
|
792
795
|
darkColor,
|
|
793
796
|
headline: headlinePct ? `${Math.round(headlinePct.usedPercent)}%` : STATE_LABEL[item.analysis.state],
|
|
794
797
|
rows,
|
|
795
|
-
note:
|
|
798
|
+
note: item.usage.pricing
|
|
799
|
+
? `${pricingLabel(item.usage.pricing, formatResetTime(item.usage.pricing.until, now))}${item.usage.quotaUnit === "credits" ? " · credits" : ""}`
|
|
800
|
+
: null,
|
|
796
801
|
message: shortCardMessage(item),
|
|
797
802
|
updated: observedUpdatedLabel(item, now),
|
|
798
803
|
};
|
|
@@ -901,6 +906,9 @@ export function renderMenuBar(snapshot = loadDisplayStatusSnapshot(), paths = bu
|
|
|
901
906
|
font: ROW_FONT,
|
|
902
907
|
size: 12,
|
|
903
908
|
}));
|
|
909
|
+
if (item.usage.pricing) {
|
|
910
|
+
lines.push(muted(pricingLabel(item.usage.pricing, formatResetTime(item.usage.pricing.until, now))));
|
|
911
|
+
}
|
|
904
912
|
lines.push(line(item.analysis.message, { color: MUTED_COLOR, size: 12, length: 84 }));
|
|
905
913
|
lines.push("---");
|
|
906
914
|
continue;
|
|
@@ -920,6 +928,10 @@ export function renderMenuBar(snapshot = loadDisplayStatusSnapshot(), paths = bu
|
|
|
920
928
|
if (item.usage.blocked) {
|
|
921
929
|
lines.push(muted(`Blocked ${item.usage.blocked.reason}. 5h/7d numbers above still reflect the window quotas.`));
|
|
922
930
|
}
|
|
931
|
+
if (item.usage.pricing) {
|
|
932
|
+
const unit = item.usage.quotaUnit === "credits" ? " · metered in credits" : "";
|
|
933
|
+
lines.push(muted(`${pricingLabel(item.usage.pricing, formatResetTime(item.usage.pricing.until, now))}${unit}`));
|
|
934
|
+
}
|
|
923
935
|
lines.push(muted(targetLabel(item)));
|
|
924
936
|
lines.push(line(item.analysis.message, { color: MUTED_COLOR, size: 12, length: 84 }));
|
|
925
937
|
lines.push("---");
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { PricingInfo } from "./types.js";
|
|
2
|
+
export declare function isCnPublicHoliday(date: Date): boolean;
|
|
3
|
+
export declare function deepseekPricing(now?: Date): PricingInfo;
|
|
4
|
+
export declare function glmPricing(now?: Date): PricingInfo;
|
|
5
|
+
export declare function pricingLabel(pricing: PricingInfo, until: string): string;
|
package/dist/pricing.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Peak / off-peak pricing tiers for the two providers that publish a clock-based
|
|
2
|
+
// discount. Both rules are stated by the vendor in Beijing time (UTC+8, no DST),
|
|
3
|
+
// so the arithmetic below deliberately avoids the host time zone.
|
|
4
|
+
//
|
|
5
|
+
// DeepSeek (api-docs.deepseek.com/quick_start/pricing): peak is 01:00-04:00 and
|
|
6
|
+
// 06:00-10:00 UTC, Monday through Friday, excluding Chinese public holidays;
|
|
7
|
+
// every other hour is off-peak at 50% of the peak rate. In Beijing time that is
|
|
8
|
+
// 09:00-12:00 and 14:00-18:00.
|
|
9
|
+
//
|
|
10
|
+
// GLM Coding Plan V3 (docs.bigmodel.cn/cn/coding-plan/overview): 高峰时段 is
|
|
11
|
+
// 每周一至周五的 14:00~18:00 (UTC+8) at the base credit deduction; all other
|
|
12
|
+
// hours deduct 50% of the base credits. The page does not exclude public
|
|
13
|
+
// holidays, and time-boxed promotions (e.g. all-day off-peak during a holiday
|
|
14
|
+
// campaign) are not modelled here: the tier follows the standing rule only.
|
|
15
|
+
const BEIJING_OFFSET_MINUTES = 8 * 60;
|
|
16
|
+
const MINUTES_PER_DAY = 24 * 60;
|
|
17
|
+
const LOOKAHEAD_DAYS = 40;
|
|
18
|
+
// Rest days from 国务院办公厅关于2026年部分节假日安排的通知 (国办发明电〔2025〕7号,
|
|
19
|
+
// 2025-11-04). Adjusted working weekends (调休上班) are intentionally not
|
|
20
|
+
// promoted to weekdays: both vendors phrase the peak rule as Monday-Friday.
|
|
21
|
+
const CN_PUBLIC_HOLIDAYS = {
|
|
22
|
+
2026: new Set([
|
|
23
|
+
"01-01", "01-02", "01-03",
|
|
24
|
+
"02-15", "02-16", "02-17", "02-18", "02-19", "02-20", "02-21", "02-22", "02-23",
|
|
25
|
+
"04-04", "04-05", "04-06",
|
|
26
|
+
"05-01", "05-02", "05-03", "05-04", "05-05",
|
|
27
|
+
"06-19", "06-20", "06-21",
|
|
28
|
+
"09-25", "09-26", "09-27",
|
|
29
|
+
"10-01", "10-02", "10-03", "10-04", "10-05", "10-06", "10-07",
|
|
30
|
+
]),
|
|
31
|
+
};
|
|
32
|
+
const DEEPSEEK_RULE = {
|
|
33
|
+
peakSegments: [[9 * 60, 12 * 60], [14 * 60, 18 * 60]],
|
|
34
|
+
excludePublicHolidays: true,
|
|
35
|
+
offPeakDiscountPercent: 50,
|
|
36
|
+
rule: "DeepSeek API: peak Mon-Fri 09:00-12:00 & 14:00-18:00 Asia/Shanghai excluding CN public holidays; off-peak 50% off",
|
|
37
|
+
};
|
|
38
|
+
const GLM_RULE = {
|
|
39
|
+
peakSegments: [[14 * 60, 18 * 60]],
|
|
40
|
+
excludePublicHolidays: false,
|
|
41
|
+
offPeakDiscountPercent: 50,
|
|
42
|
+
rule: "GLM Coding Plan: peak Mon-Fri 14:00-18:00 Asia/Shanghai; off-peak credits deducted at 50%",
|
|
43
|
+
};
|
|
44
|
+
function beijingParts(date) {
|
|
45
|
+
const shifted = new Date(date.getTime() + BEIJING_OFFSET_MINUTES * 60_000);
|
|
46
|
+
return {
|
|
47
|
+
year: shifted.getUTCFullYear(),
|
|
48
|
+
month: shifted.getUTCMonth() + 1,
|
|
49
|
+
day: shifted.getUTCDate(),
|
|
50
|
+
weekday: shifted.getUTCDay(),
|
|
51
|
+
minutes: shifted.getUTCHours() * 60 + shifted.getUTCMinutes(),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function beijingInstant(year, month, day, minutes) {
|
|
55
|
+
return new Date(Date.UTC(year, month - 1, day, 0, minutes - BEIJING_OFFSET_MINUTES));
|
|
56
|
+
}
|
|
57
|
+
export function isCnPublicHoliday(date) {
|
|
58
|
+
const parts = beijingParts(date);
|
|
59
|
+
const key = `${String(parts.month).padStart(2, "0")}-${String(parts.day).padStart(2, "0")}`;
|
|
60
|
+
return CN_PUBLIC_HOLIDAYS[parts.year]?.has(key) ?? false;
|
|
61
|
+
}
|
|
62
|
+
function tierAt(rule, date) {
|
|
63
|
+
const parts = beijingParts(date);
|
|
64
|
+
if (parts.weekday === 0 || parts.weekday === 6) {
|
|
65
|
+
return "off_peak";
|
|
66
|
+
}
|
|
67
|
+
if (rule.excludePublicHolidays && isCnPublicHoliday(date)) {
|
|
68
|
+
return "off_peak";
|
|
69
|
+
}
|
|
70
|
+
const inPeak = rule.peakSegments.some(([start, end]) => parts.minutes >= start && parts.minutes < end);
|
|
71
|
+
return inPeak ? "peak" : "off_peak";
|
|
72
|
+
}
|
|
73
|
+
// The tier can only flip at a segment edge or at midnight, so walking those
|
|
74
|
+
// instants day by day finds the next change without minute-level scanning.
|
|
75
|
+
function nextTierChange(rule, date) {
|
|
76
|
+
const current = tierAt(rule, date);
|
|
77
|
+
const start = beijingParts(date);
|
|
78
|
+
const edges = Array.from(new Set([0, ...rule.peakSegments.flatMap(([a, b]) => [a, b])]))
|
|
79
|
+
.filter((m) => m < MINUTES_PER_DAY)
|
|
80
|
+
.sort((a, b) => a - b);
|
|
81
|
+
for (let offset = 0; offset <= LOOKAHEAD_DAYS; offset += 1) {
|
|
82
|
+
for (const edge of edges) {
|
|
83
|
+
const candidate = beijingInstant(start.year, start.month, start.day + offset, edge);
|
|
84
|
+
if (candidate.getTime() <= date.getTime()) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (tierAt(rule, candidate) !== current) {
|
|
88
|
+
return candidate;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Unreachable with the rules above (every week has weekdays); keep a sane bound.
|
|
93
|
+
return beijingInstant(start.year, start.month, start.day + LOOKAHEAD_DAYS, 0);
|
|
94
|
+
}
|
|
95
|
+
function pricingFor(rule, date) {
|
|
96
|
+
const tier = tierAt(rule, date);
|
|
97
|
+
return {
|
|
98
|
+
tier,
|
|
99
|
+
discountPercent: tier === "off_peak" ? rule.offPeakDiscountPercent : 0,
|
|
100
|
+
until: nextTierChange(rule, date).toISOString(),
|
|
101
|
+
timezone: "Asia/Shanghai",
|
|
102
|
+
rule: rule.rule,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export function deepseekPricing(now = new Date()) {
|
|
106
|
+
return pricingFor(DEEPSEEK_RULE, now);
|
|
107
|
+
}
|
|
108
|
+
export function glmPricing(now = new Date()) {
|
|
109
|
+
return pricingFor(GLM_RULE, now);
|
|
110
|
+
}
|
|
111
|
+
export function pricingLabel(pricing, until) {
|
|
112
|
+
return pricing.tier === "off_peak"
|
|
113
|
+
? `Off-peak · ${pricing.discountPercent}% off until ${until}`
|
|
114
|
+
: `Peak · full price until ${until}`;
|
|
115
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -8,11 +8,20 @@ export interface UsageWindow {
|
|
|
8
8
|
usedPercent: number;
|
|
9
9
|
resetsAt: string;
|
|
10
10
|
}
|
|
11
|
+
export interface PricingInfo {
|
|
12
|
+
tier: "peak" | "off_peak";
|
|
13
|
+
discountPercent: number;
|
|
14
|
+
until: string;
|
|
15
|
+
timezone: string;
|
|
16
|
+
rule: string;
|
|
17
|
+
}
|
|
11
18
|
export interface ProviderUsage {
|
|
12
19
|
provider: ProviderId;
|
|
13
20
|
source: string;
|
|
14
21
|
observedAt: string;
|
|
15
22
|
planType?: string | null;
|
|
23
|
+
quotaUnit?: "tokens" | "credits";
|
|
24
|
+
pricing?: PricingInfo;
|
|
16
25
|
windows: UsageWindow[];
|
|
17
26
|
balance?: {
|
|
18
27
|
total: string;
|