@leo000001/opencode-quota-sidebar 3.0.0 → 3.0.2
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/CHANGELOG.md +14 -2
- package/CONTRIBUTING.md +4 -1
- package/README.md +210 -514
- package/README.zh-CN.md +337 -0
- package/SECURITY.md +2 -2
- package/assets/OpenCode-Quota-Sidebar.png +0 -0
- package/dist/cost.d.ts +3 -3
- package/dist/cost.js +258 -169
- package/dist/format.d.ts +4 -0
- package/dist/format.js +24 -10
- package/dist/providers/common.d.ts +6 -0
- package/dist/providers/common.js +32 -12
- package/dist/providers/core/anthropic.d.ts +1 -1
- package/dist/providers/core/anthropic.js +43 -39
- package/dist/providers/core/kimi_for_coding.d.ts +1 -1
- package/dist/providers/core/kimi_for_coding.js +44 -64
- package/dist/providers/core/minimax_cn_coding_plan.d.ts +2 -0
- package/dist/providers/core/minimax_cn_coding_plan.js +214 -0
- package/dist/providers/core/zhipu_coding_plan.d.ts +1 -1
- package/dist/providers/core/zhipu_coding_plan.js +41 -61
- package/dist/providers/index.d.ts +3 -3
- package/dist/providers/index.js +5 -5
- package/dist/providers/third_party/rightcode.d.ts +1 -1
- package/dist/providers/third_party/rightcode.js +41 -61
- package/dist/providers/third_party/xyai.d.ts +2 -0
- package/dist/providers/third_party/{xyai_vibe.js → xyai.js} +113 -79
- package/dist/quota.d.ts +2 -2
- package/dist/quota.js +24 -18
- package/dist/quota_render.d.ts +1 -1
- package/dist/quota_render.js +23 -17
- package/dist/storage_parse.js +1 -0
- package/dist/title.js +7 -7
- package/dist/title_apply.js +18 -1
- package/dist/tui.tsx +133 -36
- package/dist/tui_helpers.d.ts +16 -0
- package/dist/tui_helpers.js +146 -0
- package/dist/types.d.ts +2 -0
- package/package.json +9 -2
- package/quota-sidebar.config.example.json +45 -45
- package/dist/providers/third_party/buzz.d.ts +0 -2
- package/dist/providers/third_party/buzz.js +0 -156
- package/dist/providers/third_party/xyai_vibe.d.ts +0 -2
|
@@ -1,39 +1,19 @@
|
|
|
1
|
-
import { isRecord, swallow } from
|
|
2
|
-
import { asNumber, basePathPrefixes, configuredProviderEnabled, fetchWithTimeout, sanitizeBaseURL, toIso, } from
|
|
1
|
+
import { isRecord, swallow } from "../../helpers.js";
|
|
2
|
+
import { asNumber, basePathPrefixes, configuredProviderEnabled, fetchWithTimeout, resolveApiKey, sanitizeBaseURL, toIso, } from "../common.js";
|
|
3
3
|
function isRightCodeBaseURL(value) {
|
|
4
4
|
const normalized = sanitizeBaseURL(value);
|
|
5
5
|
if (!normalized)
|
|
6
6
|
return false;
|
|
7
7
|
try {
|
|
8
8
|
const parsed = new URL(normalized);
|
|
9
|
-
if (parsed.protocol !==
|
|
9
|
+
if (parsed.protocol !== "https:")
|
|
10
10
|
return false;
|
|
11
|
-
return parsed.host ===
|
|
11
|
+
return parsed.host === "www.right.codes" || parsed.host === "right.codes";
|
|
12
12
|
}
|
|
13
13
|
catch {
|
|
14
14
|
return false;
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
-
function resolveApiKey(auth, providerOptions) {
|
|
18
|
-
const optionKey = providerOptions?.apiKey;
|
|
19
|
-
if (typeof optionKey === 'string' && optionKey)
|
|
20
|
-
return optionKey;
|
|
21
|
-
if (!auth)
|
|
22
|
-
return undefined;
|
|
23
|
-
if (auth.type === 'api' && typeof auth.key === 'string' && auth.key) {
|
|
24
|
-
return auth.key;
|
|
25
|
-
}
|
|
26
|
-
if (auth.type === 'wellknown') {
|
|
27
|
-
if (typeof auth.key === 'string' && auth.key)
|
|
28
|
-
return auth.key;
|
|
29
|
-
if (typeof auth.token === 'string' && auth.token)
|
|
30
|
-
return auth.token;
|
|
31
|
-
}
|
|
32
|
-
if (auth.type === 'oauth' && typeof auth.access === 'string' && auth.access) {
|
|
33
|
-
return auth.access;
|
|
34
|
-
}
|
|
35
|
-
return undefined;
|
|
36
|
-
}
|
|
37
17
|
function matchesSubscriptionPrefix(providerPrefixes, availablePrefixes) {
|
|
38
18
|
if (providerPrefixes.length === 0 || availablePrefixes.length === 0) {
|
|
39
19
|
return false;
|
|
@@ -50,10 +30,10 @@ function matchesSubscriptionPrefix(providerPrefixes, availablePrefixes) {
|
|
|
50
30
|
}
|
|
51
31
|
function formatQuotaValue(value) {
|
|
52
32
|
if (!Number.isFinite(value))
|
|
53
|
-
return
|
|
33
|
+
return "0";
|
|
54
34
|
if (Math.abs(value) >= 10) {
|
|
55
35
|
const one = value.toFixed(1);
|
|
56
|
-
return one.endsWith(
|
|
36
|
+
return one.endsWith(".0") ? one.slice(0, -2) : one;
|
|
57
37
|
}
|
|
58
38
|
return value.toFixed(2);
|
|
59
39
|
}
|
|
@@ -72,7 +52,7 @@ function parseSubscription(value) {
|
|
|
72
52
|
// Intentionally not using normalizePercent(): daily ratio can exceed 100%.
|
|
73
53
|
const remainingPercent = (dailyRemaining / total) * 100;
|
|
74
54
|
return {
|
|
75
|
-
name: typeof value.name ===
|
|
55
|
+
name: typeof value.name === "string" ? value.name : "Subscription",
|
|
76
56
|
dailyTotal: total,
|
|
77
57
|
dailyRemaining,
|
|
78
58
|
remainingPercent,
|
|
@@ -83,20 +63,20 @@ function extractPrefixes(value) {
|
|
|
83
63
|
const raw = value.available_prefixes;
|
|
84
64
|
if (!Array.isArray(raw))
|
|
85
65
|
return [];
|
|
86
|
-
return raw.filter((item) => typeof item ===
|
|
66
|
+
return raw.filter((item) => typeof item === "string" && !!item);
|
|
87
67
|
}
|
|
88
68
|
async function fetchRightCodeQuota(ctx) {
|
|
89
69
|
const checkedAt = Date.now();
|
|
90
|
-
const sourceProviderID = typeof ctx.sourceProviderID ===
|
|
70
|
+
const sourceProviderID = typeof ctx.sourceProviderID === "string" && ctx.sourceProviderID
|
|
91
71
|
? ctx.sourceProviderID
|
|
92
72
|
: ctx.providerID;
|
|
93
|
-
const shortLabel = sourceProviderID.startsWith(
|
|
94
|
-
? `RC-${sourceProviderID.slice(
|
|
95
|
-
:
|
|
73
|
+
const shortLabel = sourceProviderID.startsWith("rightcode-")
|
|
74
|
+
? `RC-${sourceProviderID.slice("rightcode-".length)}`
|
|
75
|
+
: "RC";
|
|
96
76
|
const base = {
|
|
97
77
|
providerID: sourceProviderID,
|
|
98
|
-
adapterID:
|
|
99
|
-
label:
|
|
78
|
+
adapterID: "rightcode",
|
|
79
|
+
label: "RightCode",
|
|
100
80
|
shortLabel,
|
|
101
81
|
sortOrder: 5,
|
|
102
82
|
};
|
|
@@ -104,43 +84,43 @@ async function fetchRightCodeQuota(ctx) {
|
|
|
104
84
|
if (!apiKey) {
|
|
105
85
|
return {
|
|
106
86
|
...base,
|
|
107
|
-
status:
|
|
87
|
+
status: "unavailable",
|
|
108
88
|
checkedAt,
|
|
109
|
-
note:
|
|
89
|
+
note: "missing api key",
|
|
110
90
|
};
|
|
111
91
|
}
|
|
112
|
-
const response = await fetchWithTimeout(
|
|
92
|
+
const response = await fetchWithTimeout("https://www.right.codes/account/summary", {
|
|
113
93
|
headers: {
|
|
114
|
-
Accept:
|
|
94
|
+
Accept: "application/json",
|
|
115
95
|
Authorization: `Bearer ${apiKey}`,
|
|
116
|
-
|
|
96
|
+
"User-Agent": "opencode-quota-sidebar",
|
|
117
97
|
},
|
|
118
|
-
}, ctx.config.quota.requestTimeoutMs).catch(swallow(
|
|
98
|
+
}, ctx.config.quota.requestTimeoutMs).catch(swallow("fetchRightCodeQuota"));
|
|
119
99
|
if (!response) {
|
|
120
100
|
return {
|
|
121
101
|
...base,
|
|
122
|
-
status:
|
|
102
|
+
status: "error",
|
|
123
103
|
checkedAt,
|
|
124
|
-
note:
|
|
104
|
+
note: "network request failed",
|
|
125
105
|
};
|
|
126
106
|
}
|
|
127
107
|
if (!response.ok) {
|
|
128
108
|
return {
|
|
129
109
|
...base,
|
|
130
|
-
status:
|
|
110
|
+
status: "error",
|
|
131
111
|
checkedAt,
|
|
132
112
|
note: `http ${response.status}`,
|
|
133
113
|
};
|
|
134
114
|
}
|
|
135
115
|
const payload = await response
|
|
136
116
|
.json()
|
|
137
|
-
.catch(swallow(
|
|
117
|
+
.catch(swallow("fetchRightCodeQuota:json"));
|
|
138
118
|
if (!isRecord(payload)) {
|
|
139
119
|
return {
|
|
140
120
|
...base,
|
|
141
|
-
status:
|
|
121
|
+
status: "error",
|
|
142
122
|
checkedAt,
|
|
143
|
-
note:
|
|
123
|
+
note: "invalid response",
|
|
144
124
|
};
|
|
145
125
|
}
|
|
146
126
|
const balance = asNumber(payload.balance);
|
|
@@ -164,7 +144,7 @@ async function fetchRightCodeQuota(ctx) {
|
|
|
164
144
|
const dailyPercent = dailyTotal > 0 ? (dailyRemaining / dailyTotal) * 100 : undefined;
|
|
165
145
|
const parsedExpiries = matched
|
|
166
146
|
.map((subscription) => subscription.expiresAt)
|
|
167
|
-
.filter((iso) => typeof iso ===
|
|
147
|
+
.filter((iso) => typeof iso === "string" && !!iso)
|
|
168
148
|
.map((iso) => ({ iso, ts: Date.parse(iso) }))
|
|
169
149
|
.filter((item) => !Number.isNaN(item.ts));
|
|
170
150
|
const uniqueExpiryTimestamps = new Set(parsedExpiries.map((e) => e.ts));
|
|
@@ -184,10 +164,10 @@ async function fetchRightCodeQuota(ctx) {
|
|
|
184
164
|
remainingPercent: dailyPercent,
|
|
185
165
|
},
|
|
186
166
|
];
|
|
187
|
-
const names = matched.map((subscription) => subscription.name).join(
|
|
167
|
+
const names = matched.map((subscription) => subscription.name).join(", ");
|
|
188
168
|
return {
|
|
189
169
|
...base,
|
|
190
|
-
status: dailyPercent === undefined ?
|
|
170
|
+
status: dailyPercent === undefined ? "error" : "ok",
|
|
191
171
|
checkedAt,
|
|
192
172
|
remainingPercent: dailyPercent,
|
|
193
173
|
expiresAt: expiry,
|
|
@@ -195,39 +175,39 @@ async function fetchRightCodeQuota(ctx) {
|
|
|
195
175
|
? undefined
|
|
196
176
|
: {
|
|
197
177
|
amount: balance,
|
|
198
|
-
currency:
|
|
178
|
+
currency: "$",
|
|
199
179
|
},
|
|
200
180
|
windows,
|
|
201
181
|
note: dailyPercent === undefined
|
|
202
|
-
?
|
|
203
|
-
: `subscription daily quota: ${names}${expiry ? ` | exp ${expiry.slice(5, 10)}` :
|
|
182
|
+
? "matched subscription has no daily quota fields"
|
|
183
|
+
: `subscription daily quota: ${names}${expiry ? ` | exp ${expiry.slice(5, 10)}` : ""}${hasMultipleExpiries ? "+" : ""}`,
|
|
204
184
|
};
|
|
205
185
|
}
|
|
206
186
|
if (balance !== undefined) {
|
|
207
187
|
return {
|
|
208
188
|
...base,
|
|
209
|
-
status:
|
|
189
|
+
status: "ok",
|
|
210
190
|
checkedAt,
|
|
211
191
|
balance: {
|
|
212
192
|
amount: balance,
|
|
213
|
-
currency:
|
|
193
|
+
currency: "$",
|
|
214
194
|
},
|
|
215
|
-
note:
|
|
195
|
+
note: "no matching subscription for provider prefix",
|
|
216
196
|
};
|
|
217
197
|
}
|
|
218
198
|
return {
|
|
219
199
|
...base,
|
|
220
|
-
status:
|
|
200
|
+
status: "error",
|
|
221
201
|
checkedAt,
|
|
222
|
-
note:
|
|
202
|
+
note: "missing balance and subscription fields",
|
|
223
203
|
};
|
|
224
204
|
}
|
|
225
205
|
export const rightCodeAdapter = {
|
|
226
|
-
id:
|
|
227
|
-
label:
|
|
228
|
-
shortLabel:
|
|
206
|
+
id: "rightcode",
|
|
207
|
+
label: "RightCode",
|
|
208
|
+
shortLabel: "RC",
|
|
229
209
|
sortOrder: 5,
|
|
230
210
|
matchScore: ({ providerOptions }) => isRightCodeBaseURL(providerOptions?.baseURL) ? 100 : 0,
|
|
231
|
-
isEnabled: (config) => configuredProviderEnabled(config.quota,
|
|
211
|
+
isEnabled: (config) => configuredProviderEnabled(config.quota, "rightcode", true),
|
|
232
212
|
fetch: fetchRightCodeQuota,
|
|
233
213
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { debugError, isRecord, swallow } from
|
|
2
|
-
import { asNumber, configuredProviderEnabled, fetchWithTimeout, sanitizeBaseURL, toIso, } from
|
|
3
|
-
const XYAI_BASE_URL =
|
|
1
|
+
import { debugError, isRecord, swallow } from "../../helpers.js";
|
|
2
|
+
import { asNumber, configuredProviderEnabled, fetchWithTimeout, sanitizeBaseURL, toIso, } from "../common.js";
|
|
3
|
+
const XYAI_BASE_URL = "https://new.xychatai.com";
|
|
4
4
|
function resolveSiteOrigin(value) {
|
|
5
5
|
const normalized = sanitizeBaseURL(value);
|
|
6
6
|
if (!normalized)
|
|
@@ -18,7 +18,7 @@ function isXyaiBaseURL(value) {
|
|
|
18
18
|
return false;
|
|
19
19
|
try {
|
|
20
20
|
const parsed = new URL(normalized);
|
|
21
|
-
return parsed.protocol ===
|
|
21
|
+
return parsed.protocol === "https:" && parsed.host === "new.xychatai.com";
|
|
22
22
|
}
|
|
23
23
|
catch {
|
|
24
24
|
return false;
|
|
@@ -29,45 +29,62 @@ function providerConfigFor(config, providerIDs) {
|
|
|
29
29
|
if (!providerID)
|
|
30
30
|
continue;
|
|
31
31
|
const value = config.quota.providers?.[providerID];
|
|
32
|
-
if (value && typeof value ===
|
|
32
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
33
33
|
return value;
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
return undefined;
|
|
37
37
|
}
|
|
38
|
+
function xyaiConfigCandidates(runtimeProviderID, providerID) {
|
|
39
|
+
const candidates = [];
|
|
40
|
+
const push = (value) => {
|
|
41
|
+
if (!value)
|
|
42
|
+
return;
|
|
43
|
+
if (!candidates.includes(value))
|
|
44
|
+
candidates.push(value);
|
|
45
|
+
};
|
|
46
|
+
if (runtimeProviderID === "xyai" || runtimeProviderID === "xyai-vibe") {
|
|
47
|
+
push("xyai");
|
|
48
|
+
}
|
|
49
|
+
push(runtimeProviderID);
|
|
50
|
+
push(providerID);
|
|
51
|
+
push("xyai");
|
|
52
|
+
push("xyai-vibe");
|
|
53
|
+
return candidates;
|
|
54
|
+
}
|
|
38
55
|
function resolveSessionCookie(auth, providerConfig) {
|
|
39
|
-
if (typeof providerConfig?.sessionCookie ===
|
|
56
|
+
if (typeof providerConfig?.sessionCookie === "string" &&
|
|
40
57
|
providerConfig.sessionCookie) {
|
|
41
58
|
return providerConfig.sessionCookie;
|
|
42
59
|
}
|
|
43
60
|
if (!auth)
|
|
44
61
|
return undefined;
|
|
45
|
-
if (auth.type ===
|
|
46
|
-
if (typeof auth.token ===
|
|
62
|
+
if (auth.type === "wellknown") {
|
|
63
|
+
if (typeof auth.token === "string" && auth.token)
|
|
47
64
|
return auth.token;
|
|
48
65
|
}
|
|
49
66
|
return undefined;
|
|
50
67
|
}
|
|
51
68
|
function resolveServiceType(providerConfig) {
|
|
52
|
-
return providerConfig?.serviceType ===
|
|
69
|
+
return providerConfig?.serviceType === "claudecode" ? "claudecode" : "codex";
|
|
53
70
|
}
|
|
54
71
|
function resolveLogin(providerConfig) {
|
|
55
72
|
const login = providerConfig?.login;
|
|
56
|
-
if (!login || typeof login !==
|
|
73
|
+
if (!login || typeof login !== "object" || Array.isArray(login)) {
|
|
57
74
|
return undefined;
|
|
58
75
|
}
|
|
59
|
-
const username = typeof login.username ===
|
|
60
|
-
const password = typeof login.password ===
|
|
76
|
+
const username = typeof login.username === "string" ? login.username.trim() : "";
|
|
77
|
+
const password = typeof login.password === "string" ? login.password : "";
|
|
61
78
|
if (!username || !password)
|
|
62
79
|
return undefined;
|
|
63
80
|
return { username, password };
|
|
64
81
|
}
|
|
65
82
|
function headerCookies(response) {
|
|
66
83
|
const headers = response.headers;
|
|
67
|
-
if (typeof headers.getSetCookie ===
|
|
84
|
+
if (typeof headers.getSetCookie === "function") {
|
|
68
85
|
return headers.getSetCookie().filter(Boolean);
|
|
69
86
|
}
|
|
70
|
-
const joined = response.headers.get(
|
|
87
|
+
const joined = response.headers.get("set-cookie");
|
|
71
88
|
return joined ? [joined] : [];
|
|
72
89
|
}
|
|
73
90
|
function extractShareSession(response) {
|
|
@@ -80,45 +97,49 @@ function extractShareSession(response) {
|
|
|
80
97
|
}
|
|
81
98
|
async function loginAndPersistSession(siteOrigin, login, providerID, updateAuth, timeoutMs) {
|
|
82
99
|
const response = await fetchWithTimeout(`${siteOrigin}/frontend-api/login`, {
|
|
83
|
-
method:
|
|
100
|
+
method: "POST",
|
|
84
101
|
headers: {
|
|
85
|
-
Accept:
|
|
86
|
-
|
|
87
|
-
|
|
102
|
+
Accept: "application/json",
|
|
103
|
+
"Content-Type": "application/json",
|
|
104
|
+
"User-Agent": "opencode-quota-sidebar",
|
|
88
105
|
},
|
|
89
106
|
body: JSON.stringify({
|
|
90
107
|
userToken: login.username,
|
|
91
108
|
password: login.password,
|
|
92
|
-
token:
|
|
109
|
+
token: "",
|
|
93
110
|
}),
|
|
94
|
-
}, timeoutMs).catch(swallow(
|
|
111
|
+
}, timeoutMs).catch(swallow("fetchXyaiQuota:login"));
|
|
95
112
|
if (!response) {
|
|
96
|
-
return { error:
|
|
113
|
+
return { error: "login request failed" };
|
|
97
114
|
}
|
|
98
115
|
if (!response.ok) {
|
|
99
116
|
return { error: `login http ${response.status}` };
|
|
100
117
|
}
|
|
101
|
-
const payload = await response
|
|
118
|
+
const payload = await response
|
|
119
|
+
.json()
|
|
120
|
+
.catch(swallow("fetchXyaiQuota:loginJson"));
|
|
102
121
|
if (!isRecord(payload)) {
|
|
103
|
-
return { error:
|
|
122
|
+
return { error: "invalid login response" };
|
|
104
123
|
}
|
|
105
124
|
if (payload.code !== 1) {
|
|
106
|
-
const msg = typeof payload.msg ===
|
|
125
|
+
const msg = typeof payload.msg === "string" && payload.msg
|
|
126
|
+
? payload.msg
|
|
127
|
+
: "login failed";
|
|
107
128
|
return { error: msg };
|
|
108
129
|
}
|
|
109
130
|
const session = extractShareSession(response);
|
|
110
131
|
if (!session) {
|
|
111
|
-
return { error:
|
|
132
|
+
return { error: "missing share-session cookie" };
|
|
112
133
|
}
|
|
113
134
|
if (updateAuth) {
|
|
114
135
|
try {
|
|
115
|
-
await updateAuth(providerID, { type:
|
|
136
|
+
await updateAuth(providerID, { type: "wellknown", token: session });
|
|
116
137
|
}
|
|
117
138
|
catch (error) {
|
|
118
|
-
debugError(
|
|
139
|
+
debugError("updateAuth:xyai", error);
|
|
119
140
|
return {
|
|
120
141
|
session,
|
|
121
|
-
warning:
|
|
142
|
+
warning: "session refreshed but failed to persist; using in-memory session",
|
|
122
143
|
};
|
|
123
144
|
}
|
|
124
145
|
}
|
|
@@ -127,35 +148,39 @@ async function loginAndPersistSession(siteOrigin, login, providerID, updateAuth,
|
|
|
127
148
|
async function fetchQuotaPayload(siteOrigin, session, timeoutMs) {
|
|
128
149
|
const response = await fetchWithTimeout(`${siteOrigin}/frontend-api/vibe-code/quota`, {
|
|
129
150
|
headers: {
|
|
130
|
-
Accept:
|
|
151
|
+
Accept: "application/json",
|
|
131
152
|
Cookie: `share-session=${session}`,
|
|
132
|
-
|
|
153
|
+
"User-Agent": "opencode-quota-sidebar",
|
|
133
154
|
},
|
|
134
|
-
}, timeoutMs).catch(swallow(
|
|
155
|
+
}, timeoutMs).catch(swallow("fetchXyaiQuota:quota"));
|
|
135
156
|
if (!response)
|
|
136
|
-
return { error:
|
|
157
|
+
return { error: "network request failed" };
|
|
137
158
|
if (!response.ok)
|
|
138
159
|
return { error: `http ${response.status}` };
|
|
139
|
-
const payload = await response
|
|
160
|
+
const payload = await response
|
|
161
|
+
.json()
|
|
162
|
+
.catch(swallow("fetchXyaiQuota:quotaJson"));
|
|
140
163
|
if (!isRecord(payload))
|
|
141
|
-
return { error:
|
|
164
|
+
return { error: "invalid response" };
|
|
142
165
|
return { payload };
|
|
143
166
|
}
|
|
144
167
|
function isAuthFailure(payload) {
|
|
145
|
-
return payload.code === -1 || payload.msg ===
|
|
168
|
+
return payload.code === -1 || payload.msg === "认证失败,请重新登录";
|
|
146
169
|
}
|
|
147
170
|
function formatAmount(value) {
|
|
148
171
|
if (!Number.isFinite(value))
|
|
149
|
-
return
|
|
172
|
+
return "0";
|
|
150
173
|
if (Math.abs(value) >= 10) {
|
|
151
174
|
const one = value.toFixed(1);
|
|
152
|
-
return one.endsWith(
|
|
175
|
+
return one.endsWith(".0") ? one.slice(0, -2) : one;
|
|
153
176
|
}
|
|
154
177
|
return value.toFixed(2);
|
|
155
178
|
}
|
|
156
179
|
function pickServicePayload(payload, preferred) {
|
|
157
180
|
const source = isRecord(payload.data) ? payload.data : payload;
|
|
158
|
-
const ordered = preferred ===
|
|
181
|
+
const ordered = preferred === "claudecode"
|
|
182
|
+
? ["claudecode", "codex"]
|
|
183
|
+
: ["codex", "claudecode"];
|
|
159
184
|
for (const key of ordered) {
|
|
160
185
|
const value = source[key];
|
|
161
186
|
if (isRecord(value))
|
|
@@ -166,15 +191,17 @@ function pickServicePayload(payload, preferred) {
|
|
|
166
191
|
function parseQuotaSnapshot(args) {
|
|
167
192
|
const base = {
|
|
168
193
|
providerID: args.providerID,
|
|
169
|
-
adapterID:
|
|
170
|
-
label:
|
|
171
|
-
shortLabel:
|
|
194
|
+
adapterID: "xyai",
|
|
195
|
+
label: "XYAI",
|
|
196
|
+
shortLabel: "XYAI",
|
|
172
197
|
sortOrder: 7,
|
|
173
198
|
};
|
|
174
199
|
const subscriptions = isRecord(args.payload.subscriptions)
|
|
175
200
|
? args.payload.subscriptions
|
|
176
201
|
: undefined;
|
|
177
|
-
const usage = isRecord(args.payload.currentUsage)
|
|
202
|
+
const usage = isRecord(args.payload.currentUsage)
|
|
203
|
+
? args.payload.currentUsage
|
|
204
|
+
: undefined;
|
|
178
205
|
const amountLimit = asNumber(subscriptions?.amountLimit);
|
|
179
206
|
const remainingAmount = asNumber(subscriptions?.remainingAmount);
|
|
180
207
|
const periodResetTime = toIso(subscriptions?.periodResetTime);
|
|
@@ -182,54 +209,52 @@ function parseQuotaSnapshot(args) {
|
|
|
182
209
|
if (amountLimit === undefined || remainingAmount === undefined) {
|
|
183
210
|
return {
|
|
184
211
|
...base,
|
|
185
|
-
status:
|
|
212
|
+
status: "error",
|
|
186
213
|
checkedAt: args.checkedAt,
|
|
187
|
-
note:
|
|
214
|
+
note: "missing quota fields",
|
|
188
215
|
};
|
|
189
216
|
}
|
|
190
|
-
const remainingPercent = amountLimit > 0
|
|
217
|
+
const remainingPercent = amountLimit > 0
|
|
218
|
+
? Math.max(0, Math.min(100, (remainingAmount / amountLimit) * 100))
|
|
219
|
+
: undefined;
|
|
191
220
|
const windows = [
|
|
192
221
|
{
|
|
193
222
|
label: `Daily $${formatAmount(remainingAmount)}/$${formatAmount(amountLimit)}`,
|
|
194
223
|
showPercent: false,
|
|
195
224
|
remainingPercent,
|
|
196
225
|
resetAt: periodResetTime,
|
|
197
|
-
resetLabel:
|
|
226
|
+
resetLabel: "Rst",
|
|
198
227
|
},
|
|
199
228
|
];
|
|
200
229
|
const noteParts = [
|
|
201
230
|
expireTime ? `exp ${expireTime.slice(5, 10)}` : undefined,
|
|
202
|
-
args.serviceType ===
|
|
231
|
+
args.serviceType === "claudecode" ? "service=claudecode" : undefined,
|
|
203
232
|
args.warning,
|
|
204
233
|
].filter((value) => Boolean(value));
|
|
205
234
|
return {
|
|
206
235
|
...base,
|
|
207
|
-
status:
|
|
236
|
+
status: "ok",
|
|
208
237
|
checkedAt: args.checkedAt,
|
|
209
238
|
remainingPercent,
|
|
210
239
|
resetAt: periodResetTime,
|
|
211
240
|
expiresAt: expireTime,
|
|
212
|
-
note: noteParts.join(
|
|
241
|
+
note: noteParts.join(" | ") || undefined,
|
|
213
242
|
windows,
|
|
214
243
|
};
|
|
215
244
|
}
|
|
216
|
-
async function
|
|
245
|
+
async function fetchXyaiQuota(ctx) {
|
|
217
246
|
const checkedAt = Date.now();
|
|
218
|
-
const runtimeProviderID = typeof ctx.sourceProviderID ===
|
|
247
|
+
const runtimeProviderID = typeof ctx.sourceProviderID === "string" && ctx.sourceProviderID
|
|
219
248
|
? ctx.sourceProviderID
|
|
220
249
|
: ctx.providerID;
|
|
221
|
-
const providerConfig = providerConfigFor(ctx.config,
|
|
222
|
-
runtimeProviderID,
|
|
223
|
-
ctx.providerID,
|
|
224
|
-
'xyai-vibe',
|
|
225
|
-
]);
|
|
250
|
+
const providerConfig = providerConfigFor(ctx.config, xyaiConfigCandidates(runtimeProviderID, ctx.providerID));
|
|
226
251
|
const siteOrigin = resolveSiteOrigin(providerConfig?.baseURL ?? ctx.providerOptions?.baseURL);
|
|
227
252
|
const serviceType = resolveServiceType(providerConfig);
|
|
228
253
|
const base = {
|
|
229
254
|
providerID: runtimeProviderID,
|
|
230
|
-
adapterID:
|
|
231
|
-
label:
|
|
232
|
-
shortLabel:
|
|
255
|
+
adapterID: "xyai",
|
|
256
|
+
label: "XYAI",
|
|
257
|
+
shortLabel: "XYAI",
|
|
233
258
|
sortOrder: 7,
|
|
234
259
|
};
|
|
235
260
|
let session = resolveSessionCookie(ctx.auth, providerConfig);
|
|
@@ -237,10 +262,10 @@ async function fetchXyaiVibeQuota(ctx) {
|
|
|
237
262
|
let warning;
|
|
238
263
|
if (!session && login) {
|
|
239
264
|
const loginResult = await loginAndPersistSession(siteOrigin, login, ctx.providerID, ctx.updateAuth, ctx.config.quota.requestTimeoutMs);
|
|
240
|
-
if (
|
|
265
|
+
if ("error" in loginResult) {
|
|
241
266
|
return {
|
|
242
267
|
...base,
|
|
243
|
-
status:
|
|
268
|
+
status: "unavailable",
|
|
244
269
|
checkedAt,
|
|
245
270
|
note: loginResult.error,
|
|
246
271
|
};
|
|
@@ -251,24 +276,26 @@ async function fetchXyaiVibeQuota(ctx) {
|
|
|
251
276
|
if (!session) {
|
|
252
277
|
return {
|
|
253
278
|
...base,
|
|
254
|
-
status:
|
|
279
|
+
status: "unavailable",
|
|
255
280
|
checkedAt,
|
|
256
|
-
note:
|
|
281
|
+
note: "missing share-session or login credentials",
|
|
257
282
|
};
|
|
258
283
|
}
|
|
259
284
|
let quotaResult = await fetchQuotaPayload(siteOrigin, session, ctx.config.quota.requestTimeoutMs);
|
|
260
|
-
if (!(
|
|
285
|
+
if (!("error" in quotaResult) &&
|
|
286
|
+
isAuthFailure(quotaResult.payload) &&
|
|
287
|
+
login) {
|
|
261
288
|
const loginResult = await loginAndPersistSession(siteOrigin, login, ctx.providerID, ctx.updateAuth, ctx.config.quota.requestTimeoutMs);
|
|
262
|
-
if (!(
|
|
289
|
+
if (!("error" in loginResult)) {
|
|
263
290
|
session = loginResult.session;
|
|
264
291
|
warning = loginResult.warning ?? warning;
|
|
265
292
|
quotaResult = await fetchQuotaPayload(siteOrigin, session, ctx.config.quota.requestTimeoutMs);
|
|
266
293
|
}
|
|
267
294
|
}
|
|
268
|
-
if (
|
|
295
|
+
if ("error" in quotaResult) {
|
|
269
296
|
return {
|
|
270
297
|
...base,
|
|
271
|
-
status:
|
|
298
|
+
status: "error",
|
|
272
299
|
checkedAt,
|
|
273
300
|
note: quotaResult.error,
|
|
274
301
|
};
|
|
@@ -276,18 +303,18 @@ async function fetchXyaiVibeQuota(ctx) {
|
|
|
276
303
|
if (isAuthFailure(quotaResult.payload)) {
|
|
277
304
|
return {
|
|
278
305
|
...base,
|
|
279
|
-
status:
|
|
306
|
+
status: "unavailable",
|
|
280
307
|
checkedAt,
|
|
281
|
-
note:
|
|
308
|
+
note: "auth expired",
|
|
282
309
|
};
|
|
283
310
|
}
|
|
284
311
|
const service = pickServicePayload(quotaResult.payload, serviceType);
|
|
285
312
|
if (!service) {
|
|
286
313
|
return {
|
|
287
314
|
...base,
|
|
288
|
-
status:
|
|
315
|
+
status: "error",
|
|
289
316
|
checkedAt,
|
|
290
|
-
note:
|
|
317
|
+
note: "missing service payload",
|
|
291
318
|
};
|
|
292
319
|
}
|
|
293
320
|
return parseQuotaSnapshot({
|
|
@@ -298,17 +325,24 @@ async function fetchXyaiVibeQuota(ctx) {
|
|
|
298
325
|
warning,
|
|
299
326
|
});
|
|
300
327
|
}
|
|
301
|
-
export const
|
|
302
|
-
id:
|
|
303
|
-
label:
|
|
304
|
-
shortLabel:
|
|
328
|
+
export const xyaiAdapter = {
|
|
329
|
+
id: "xyai",
|
|
330
|
+
label: "XYAI",
|
|
331
|
+
shortLabel: "XYAI",
|
|
305
332
|
sortOrder: 7,
|
|
306
|
-
normalizeID: (providerID) =>
|
|
333
|
+
normalizeID: (providerID) => providerID === "xyai" || providerID === "xyai-vibe" ? "xyai" : undefined,
|
|
307
334
|
matchScore: ({ providerID, providerOptions }) => {
|
|
308
|
-
if (providerID ===
|
|
335
|
+
if (providerID === "xyai")
|
|
309
336
|
return 100;
|
|
337
|
+
if (providerID === "xyai-vibe")
|
|
338
|
+
return 99;
|
|
310
339
|
return isXyaiBaseURL(providerOptions?.baseURL) ? 95 : 0;
|
|
311
340
|
},
|
|
312
|
-
isEnabled: (config) =>
|
|
313
|
-
|
|
341
|
+
isEnabled: (config) => {
|
|
342
|
+
const renamed = config.quota.providers?.xyai?.enabled;
|
|
343
|
+
if (typeof renamed === "boolean")
|
|
344
|
+
return renamed;
|
|
345
|
+
return configuredProviderEnabled(config.quota, "xyai-vibe", false);
|
|
346
|
+
},
|
|
347
|
+
fetch: fetchXyaiQuota,
|
|
314
348
|
};
|
package/dist/quota.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { AuthUpdate, AuthValue } from
|
|
2
|
-
import type { QuotaSidebarConfig, QuotaSnapshot } from
|
|
1
|
+
import type { AuthUpdate, AuthValue } from "./providers/types.js";
|
|
2
|
+
import type { QuotaSidebarConfig, QuotaSnapshot } from "./types.js";
|
|
3
3
|
export declare function quotaSort(left: QuotaSnapshot, right: QuotaSnapshot): number;
|
|
4
4
|
export declare function listDefaultQuotaProviderIDs(): string[];
|
|
5
5
|
export declare function createQuotaRuntime(): {
|