@narumitw/pi-usage 0.60.7 → 0.60.9
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 +1 -1
- package/dist/index.ts +91 -340
- package/dist/index.ts.map +2 -2
- package/docs/providers.md +2 -1
- package/package.json +73 -74
- package/src/codex-fast-runtime.ts +148 -184
- package/src/codex-fast.ts +73 -83
- package/src/codex-resets.ts +249 -278
- package/src/core.ts +204 -206
- package/src/format.ts +519 -574
- package/src/index.ts +77 -77
- package/src/oauth-credential-source.ts +57 -65
- package/src/providers/baseten.ts +32 -36
- package/src/providers/codex.ts +109 -120
- package/src/providers/deepseek.ts +59 -64
- package/src/providers/fireworks.ts +219 -238
- package/src/providers/github-copilot.ts +89 -95
- package/src/providers/kimi-coding.ts +202 -213
- package/src/providers/minimax.ts +214 -219
- package/src/providers/moonshot.ts +41 -46
- package/src/providers/opencode-zen.ts +56 -59
- package/src/providers/openrouter.ts +50 -53
- package/src/providers/vercel-ai-gateway.ts +33 -33
- package/src/providers/xai.ts +141 -148
- package/src/providers/zai-errors.ts +75 -75
- package/src/providers/zai.ts +173 -189
- package/src/query.ts +887 -967
- package/src/settings.ts +275 -304
- package/src/types.ts +145 -145
- package/src/usage-helpers.ts +18 -18
- package/src/usage-settings-ui.ts +117 -126
- package/src/usage-targets.ts +98 -114
- package/src/usage.ts +1264 -1426
package/src/providers/codex.ts
CHANGED
|
@@ -1,150 +1,139 @@
|
|
|
1
1
|
import { sanitizeDisplayText } from "../core.js";
|
|
2
2
|
import type { CodexBackendPayload, UsageBucket, UsageMetric, UsageReport } from "../types.js";
|
|
3
3
|
|
|
4
|
-
export function normalizeCodexBackendPayload(
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
): UsageReport {
|
|
8
|
-
const buckets: UsageBucket[] = [];
|
|
9
|
-
normalizeRateLimitGroup(buckets, "codex", "Codex", payload.rate_limit, false);
|
|
4
|
+
export function normalizeCodexBackendPayload(payload: CodexBackendPayload, capturedAt: number): UsageReport {
|
|
5
|
+
const buckets: UsageBucket[] = [];
|
|
6
|
+
normalizeRateLimitGroup(buckets, "codex", "Codex", payload.rate_limit, false);
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
asString(value.limit_name) ?? id,
|
|
23
|
-
value.rate_limit,
|
|
24
|
-
true,
|
|
25
|
-
);
|
|
26
|
-
} catch {
|
|
27
|
-
// Optional provider-specific buckets must not hide otherwise useful primary data.
|
|
28
|
-
}
|
|
29
|
-
}
|
|
8
|
+
const additional = Array.isArray(payload.additional_rate_limits) ? payload.additional_rate_limits : [];
|
|
9
|
+
for (const item of additional) {
|
|
10
|
+
const value = asObject(item);
|
|
11
|
+
const id = asString(value?.metered_feature) ?? asString(value?.limit_name);
|
|
12
|
+
if (!value || !id) continue;
|
|
13
|
+
try {
|
|
14
|
+
normalizeRateLimitGroup(buckets, id, asString(value.limit_name) ?? id, value.rate_limit, true);
|
|
15
|
+
} catch {
|
|
16
|
+
// Optional provider-specific buckets must not hide otherwise useful primary data.
|
|
17
|
+
}
|
|
18
|
+
}
|
|
30
19
|
|
|
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
|
-
|
|
20
|
+
const metrics: UsageMetric[] = [];
|
|
21
|
+
const credits = asObject(payload.credits);
|
|
22
|
+
if (credits?.has_credits === true) {
|
|
23
|
+
if (credits.unlimited === true) {
|
|
24
|
+
metrics.push({ id: "credits", label: "Credits", value: "unlimited" });
|
|
25
|
+
} else {
|
|
26
|
+
const balance = asNumber(credits.balance);
|
|
27
|
+
if (balance !== undefined) {
|
|
28
|
+
metrics.push({ id: "credits", label: "Credits", value: balance, unit: "count" });
|
|
29
|
+
} else {
|
|
30
|
+
metrics.push({ id: "credits", label: "Credits", value: "available" });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
} else if (credits?.has_credits === false) {
|
|
34
|
+
metrics.push({ id: "credits", label: "Credits", value: "none" });
|
|
35
|
+
}
|
|
36
|
+
const resetCredits = asObject(payload.rate_limit_reset_credits);
|
|
37
|
+
const resetCount = asNonnegativeInteger(resetCredits?.available_count);
|
|
38
|
+
if (resetCount !== undefined) {
|
|
39
|
+
metrics.push({
|
|
40
|
+
id: "reset-credits",
|
|
41
|
+
label: "Usage limit resets",
|
|
42
|
+
value: resetCount,
|
|
43
|
+
unit: "count",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (buckets.length === 0 && metrics.length === 0) {
|
|
47
|
+
throw new Error("Codex usage endpoint returned no displayable usage data.");
|
|
48
|
+
}
|
|
60
49
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
50
|
+
const planType = asString(payload.plan_type);
|
|
51
|
+
return {
|
|
52
|
+
providerId: "openai-codex",
|
|
53
|
+
providerName: "OpenAI Codex",
|
|
54
|
+
capturedAt,
|
|
55
|
+
source: "codex-pi-auth",
|
|
56
|
+
semantics: {
|
|
57
|
+
kind: "consumer-subscription",
|
|
58
|
+
label: "ChatGPT subscription limits",
|
|
59
|
+
},
|
|
60
|
+
buckets,
|
|
61
|
+
metrics,
|
|
62
|
+
...(planType ? { notes: [`Plan: ${planType}`] } : {}),
|
|
63
|
+
};
|
|
75
64
|
}
|
|
76
65
|
|
|
77
66
|
function normalizeRateLimitGroup(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
67
|
+
buckets: UsageBucket[],
|
|
68
|
+
groupId: string,
|
|
69
|
+
groupLabel: string,
|
|
70
|
+
raw: unknown,
|
|
71
|
+
optional: boolean,
|
|
83
72
|
): void {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
73
|
+
if (raw === undefined || raw === null) return;
|
|
74
|
+
const details = asObject(raw);
|
|
75
|
+
if (!details) {
|
|
76
|
+
if (optional) return;
|
|
77
|
+
throw new Error("Codex rate limit was not an object.");
|
|
78
|
+
}
|
|
79
|
+
addWindow(buckets, groupId, groupLabel, "primary", details.primary_window);
|
|
80
|
+
addWindow(buckets, groupId, groupLabel, "secondary", details.secondary_window);
|
|
92
81
|
}
|
|
93
82
|
|
|
94
83
|
function addWindow(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
84
|
+
buckets: UsageBucket[],
|
|
85
|
+
groupId: string,
|
|
86
|
+
groupLabel: string,
|
|
87
|
+
position: "primary" | "secondary",
|
|
88
|
+
raw: unknown,
|
|
100
89
|
): void {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
90
|
+
if (raw === undefined || raw === null) return;
|
|
91
|
+
const value = asObject(raw);
|
|
92
|
+
if (!value) throw new Error("Codex rate-limit window was not an object.");
|
|
93
|
+
const used = asNumber(value.used_percent);
|
|
94
|
+
if (used === undefined) return;
|
|
95
|
+
const seconds = asNumber(value.limit_window_seconds);
|
|
96
|
+
const resetsAt = asNumber(value.reset_at);
|
|
97
|
+
buckets.push({
|
|
98
|
+
id: `${groupId}:${position}`,
|
|
99
|
+
label: position === "primary" ? "Primary limit" : "Secondary limit",
|
|
100
|
+
groupId,
|
|
101
|
+
groupLabel,
|
|
102
|
+
modelKeys: [groupId, groupLabel],
|
|
103
|
+
used,
|
|
104
|
+
remaining: 100 - clampPercent(used),
|
|
105
|
+
limit: 100,
|
|
106
|
+
unit: "percent",
|
|
107
|
+
...(seconds !== undefined && seconds > 0 ? { windowMinutes: Math.ceil(seconds / 60) } : {}),
|
|
108
|
+
...(resetsAt !== undefined ? { resetsAt } : {}),
|
|
109
|
+
});
|
|
121
110
|
}
|
|
122
111
|
|
|
123
112
|
function asObject(value: unknown): Record<string, unknown> | undefined {
|
|
124
|
-
|
|
125
|
-
|
|
113
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
114
|
+
return value as Record<string, unknown>;
|
|
126
115
|
}
|
|
127
116
|
|
|
128
117
|
function asString(value: unknown): string | undefined {
|
|
129
|
-
|
|
130
|
-
|
|
118
|
+
if (typeof value !== "string") return undefined;
|
|
119
|
+
return sanitizeDisplayText(value, 160) || undefined;
|
|
131
120
|
}
|
|
132
121
|
|
|
133
122
|
function asNumber(value: unknown): number | undefined {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
123
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
124
|
+
if (typeof value === "string" && value.trim()) {
|
|
125
|
+
const parsed = Number(value);
|
|
126
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
140
129
|
}
|
|
141
130
|
|
|
142
131
|
function asNonnegativeInteger(value: unknown): number | undefined {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
132
|
+
const parsed = asNumber(value);
|
|
133
|
+
if (parsed === undefined || !Number.isSafeInteger(parsed)) return undefined;
|
|
134
|
+
return Math.max(0, parsed);
|
|
146
135
|
}
|
|
147
136
|
|
|
148
137
|
function clampPercent(value: number): number {
|
|
149
|
-
|
|
138
|
+
return Math.min(100, Math.max(0, value));
|
|
150
139
|
}
|
|
@@ -4,82 +4,77 @@ const CURRENCIES = ["CNY", "USD"] as const;
|
|
|
4
4
|
type DeepSeekCurrency = (typeof CURRENCIES)[number];
|
|
5
5
|
|
|
6
6
|
const BALANCE_FIELDS = [
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
["total", "Total balance", "total_balance"],
|
|
8
|
+
["granted", "Granted balance", "granted_balance"],
|
|
9
|
+
["topped-up", "Topped-up balance", "topped_up_balance"],
|
|
10
10
|
] as const;
|
|
11
11
|
|
|
12
|
-
export function normalizeDeepSeekBalancePayload(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (!Array.isArray(payload.balance_infos) || payload.balance_infos.length === 0) {
|
|
20
|
-
throw new Error("DeepSeek API balance response returned no balance information.");
|
|
21
|
-
}
|
|
12
|
+
export function normalizeDeepSeekBalancePayload(payload: DeepSeekBalancePayload, capturedAt: number): UsageReport {
|
|
13
|
+
if (typeof payload.is_available !== "boolean") {
|
|
14
|
+
throw new Error("DeepSeek API balance response availability was not a boolean.");
|
|
15
|
+
}
|
|
16
|
+
if (!Array.isArray(payload.balance_infos) || payload.balance_infos.length === 0) {
|
|
17
|
+
throw new Error("DeepSeek API balance response returned no balance information.");
|
|
18
|
+
}
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
20
|
+
const balances = new Map<DeepSeekCurrency, Record<string, unknown>>();
|
|
21
|
+
for (const raw of payload.balance_infos) {
|
|
22
|
+
const balance = asObject(raw);
|
|
23
|
+
if (!balance) throw new Error("DeepSeek API balance row was not an object.");
|
|
24
|
+
const currency = deepSeekCurrency(balance.currency);
|
|
25
|
+
if (!currency) throw new Error("DeepSeek API balance row returned an unsupported currency.");
|
|
26
|
+
if (balances.has(currency)) {
|
|
27
|
+
throw new Error(`DeepSeek API balance response repeated ${currency}.`);
|
|
28
|
+
}
|
|
29
|
+
for (const [, label, field] of BALANCE_FIELDS) {
|
|
30
|
+
if (!decimalAmount(balance[field])) {
|
|
31
|
+
throw new Error(`DeepSeek API balance ${label.toLowerCase()} was not a valid amount.`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
balances.set(currency, balance);
|
|
35
|
+
}
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
37
|
+
const metrics: UsageMetric[] = [
|
|
38
|
+
{
|
|
39
|
+
id: "api-availability",
|
|
40
|
+
label: "API calls",
|
|
41
|
+
value: payload.is_available ? "available" : "unavailable",
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
for (const currency of CURRENCIES) {
|
|
45
|
+
const balance = balances.get(currency);
|
|
46
|
+
if (!balance) continue;
|
|
47
|
+
for (const [id, label, field] of BALANCE_FIELDS) {
|
|
48
|
+
metrics.push({
|
|
49
|
+
id: `${currency.toLowerCase()}-${id}`,
|
|
50
|
+
label,
|
|
51
|
+
value: balance[field] as string,
|
|
52
|
+
unit: "currency",
|
|
53
|
+
currency,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
60
57
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
58
|
+
return {
|
|
59
|
+
providerId: "deepseek",
|
|
60
|
+
providerName: "DeepSeek",
|
|
61
|
+
capturedAt,
|
|
62
|
+
source: "deepseek-balance",
|
|
63
|
+
semantics: { kind: "api-key", label: "DeepSeek API balance" },
|
|
64
|
+
buckets: [],
|
|
65
|
+
metrics,
|
|
66
|
+
};
|
|
70
67
|
}
|
|
71
68
|
|
|
72
69
|
function asObject(value: unknown): Record<string, unknown> | undefined {
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
71
|
+
return value as Record<string, unknown>;
|
|
75
72
|
}
|
|
76
73
|
|
|
77
74
|
function deepSeekCurrency(value: unknown): DeepSeekCurrency | undefined {
|
|
78
|
-
|
|
75
|
+
return CURRENCIES.find((currency) => currency === value);
|
|
79
76
|
}
|
|
80
77
|
|
|
81
78
|
function decimalAmount(value: unknown): value is string {
|
|
82
|
-
|
|
83
|
-
typeof value === "string" && value.length <= 64 && /^(?:0|[1-9]\d*)(?:\.\d+)?$/u.test(value)
|
|
84
|
-
);
|
|
79
|
+
return typeof value === "string" && value.length <= 64 && /^(?:0|[1-9]\d*)(?:\.\d+)?$/u.test(value);
|
|
85
80
|
}
|