@leo000001/opencode-quota-sidebar 3.0.9 → 4.0.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/CHANGELOG.md +0 -1
- package/README.md +157 -42
- package/README.zh-CN.md +157 -42
- package/SECURITY.md +1 -1
- package/dist/cli.d.ts +18 -0
- package/dist/cli.js +354 -0
- package/dist/cli_render.d.ts +17 -0
- package/dist/cli_render.js +292 -0
- package/dist/events.d.ts +1 -1
- package/dist/events.js +2 -2
- package/dist/format.d.ts +4 -0
- package/dist/format.js +302 -41
- package/dist/history_messages.d.ts +8 -0
- package/dist/history_messages.js +157 -0
- package/dist/history_usage.d.ts +93 -0
- package/dist/history_usage.js +251 -0
- package/dist/index.js +29 -4
- package/dist/period.d.ts +29 -1
- package/dist/period.js +187 -9
- package/dist/provider_catalog.d.ts +8 -0
- package/dist/provider_catalog.js +68 -0
- package/dist/providers/core/anthropic.d.ts +1 -1
- package/dist/providers/core/anthropic.js +69 -45
- package/dist/providers/core/openai.js +101 -8
- package/dist/providers/index.d.ts +1 -2
- package/dist/providers/index.js +1 -3
- package/dist/quota.d.ts +4 -2
- package/dist/quota.js +18 -21
- package/dist/quota_render.d.ts +1 -1
- package/dist/quota_render.js +23 -24
- package/dist/quota_service.d.ts +1 -0
- package/dist/quota_service.js +151 -19
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +4 -4
- package/dist/storage_dates.d.ts +1 -1
- package/dist/storage_dates.js +8 -5
- package/dist/storage_parse.js +23 -1
- package/dist/supported_quota.d.ts +4 -0
- package/dist/supported_quota.js +36 -0
- package/dist/title.js +18 -8
- package/dist/tools.d.ts +14 -3
- package/dist/tools.js +54 -2
- package/dist/tui.tsx +17 -6
- package/dist/tui_helpers.js +11 -6
- package/dist/types.d.ts +8 -0
- package/dist/usage.d.ts +18 -0
- package/dist/usage.js +93 -9
- package/dist/usage_service.d.ts +4 -1
- package/dist/usage_service.js +193 -189
- package/package.json +4 -1
- package/quota-sidebar.config.example.json +36 -45
- package/dist/providers/third_party/xyai.d.ts +0 -2
- package/dist/providers/third_party/xyai.js +0 -348
|
@@ -1,348 +0,0 @@
|
|
|
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
|
-
function resolveSiteOrigin(value) {
|
|
5
|
-
const normalized = sanitizeBaseURL(value);
|
|
6
|
-
if (!normalized)
|
|
7
|
-
return XYAI_BASE_URL;
|
|
8
|
-
try {
|
|
9
|
-
return new URL(normalized).origin;
|
|
10
|
-
}
|
|
11
|
-
catch {
|
|
12
|
-
return XYAI_BASE_URL;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
function isXyaiBaseURL(value) {
|
|
16
|
-
const normalized = sanitizeBaseURL(value);
|
|
17
|
-
if (!normalized)
|
|
18
|
-
return false;
|
|
19
|
-
try {
|
|
20
|
-
const parsed = new URL(normalized);
|
|
21
|
-
return parsed.protocol === "https:" && parsed.host === "new.xychatai.com";
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
function providerConfigFor(config, providerIDs) {
|
|
28
|
-
for (const providerID of providerIDs) {
|
|
29
|
-
if (!providerID)
|
|
30
|
-
continue;
|
|
31
|
-
const value = config.quota.providers?.[providerID];
|
|
32
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
33
|
-
return value;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return undefined;
|
|
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
|
-
}
|
|
55
|
-
function resolveSessionCookie(auth, providerConfig) {
|
|
56
|
-
if (typeof providerConfig?.sessionCookie === "string" &&
|
|
57
|
-
providerConfig.sessionCookie) {
|
|
58
|
-
return providerConfig.sessionCookie;
|
|
59
|
-
}
|
|
60
|
-
if (!auth)
|
|
61
|
-
return undefined;
|
|
62
|
-
if (auth.type === "wellknown") {
|
|
63
|
-
if (typeof auth.token === "string" && auth.token)
|
|
64
|
-
return auth.token;
|
|
65
|
-
}
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
|
-
function resolveServiceType(providerConfig) {
|
|
69
|
-
return providerConfig?.serviceType === "claudecode" ? "claudecode" : "codex";
|
|
70
|
-
}
|
|
71
|
-
function resolveLogin(providerConfig) {
|
|
72
|
-
const login = providerConfig?.login;
|
|
73
|
-
if (!login || typeof login !== "object" || Array.isArray(login)) {
|
|
74
|
-
return undefined;
|
|
75
|
-
}
|
|
76
|
-
const username = typeof login.username === "string" ? login.username.trim() : "";
|
|
77
|
-
const password = typeof login.password === "string" ? login.password : "";
|
|
78
|
-
if (!username || !password)
|
|
79
|
-
return undefined;
|
|
80
|
-
return { username, password };
|
|
81
|
-
}
|
|
82
|
-
function headerCookies(response) {
|
|
83
|
-
const headers = response.headers;
|
|
84
|
-
if (typeof headers.getSetCookie === "function") {
|
|
85
|
-
return headers.getSetCookie().filter(Boolean);
|
|
86
|
-
}
|
|
87
|
-
const joined = response.headers.get("set-cookie");
|
|
88
|
-
return joined ? [joined] : [];
|
|
89
|
-
}
|
|
90
|
-
function extractShareSession(response) {
|
|
91
|
-
for (const value of headerCookies(response)) {
|
|
92
|
-
const match = value.match(/share-session=("?)([^;"]+)\1/);
|
|
93
|
-
if (match?.[2])
|
|
94
|
-
return match[2];
|
|
95
|
-
}
|
|
96
|
-
return undefined;
|
|
97
|
-
}
|
|
98
|
-
async function loginAndPersistSession(siteOrigin, login, providerID, updateAuth, timeoutMs) {
|
|
99
|
-
const response = await fetchWithTimeout(`${siteOrigin}/frontend-api/login`, {
|
|
100
|
-
method: "POST",
|
|
101
|
-
headers: {
|
|
102
|
-
Accept: "application/json",
|
|
103
|
-
"Content-Type": "application/json",
|
|
104
|
-
"User-Agent": "opencode-quota-sidebar",
|
|
105
|
-
},
|
|
106
|
-
body: JSON.stringify({
|
|
107
|
-
userToken: login.username,
|
|
108
|
-
password: login.password,
|
|
109
|
-
token: "",
|
|
110
|
-
}),
|
|
111
|
-
}, timeoutMs).catch(swallow("fetchXyaiQuota:login"));
|
|
112
|
-
if (!response) {
|
|
113
|
-
return { error: "login request failed" };
|
|
114
|
-
}
|
|
115
|
-
if (!response.ok) {
|
|
116
|
-
return { error: `login http ${response.status}` };
|
|
117
|
-
}
|
|
118
|
-
const payload = await response
|
|
119
|
-
.json()
|
|
120
|
-
.catch(swallow("fetchXyaiQuota:loginJson"));
|
|
121
|
-
if (!isRecord(payload)) {
|
|
122
|
-
return { error: "invalid login response" };
|
|
123
|
-
}
|
|
124
|
-
if (payload.code !== 1) {
|
|
125
|
-
const msg = typeof payload.msg === "string" && payload.msg
|
|
126
|
-
? payload.msg
|
|
127
|
-
: "login failed";
|
|
128
|
-
return { error: msg };
|
|
129
|
-
}
|
|
130
|
-
const session = extractShareSession(response);
|
|
131
|
-
if (!session) {
|
|
132
|
-
return { error: "missing share-session cookie" };
|
|
133
|
-
}
|
|
134
|
-
if (updateAuth) {
|
|
135
|
-
try {
|
|
136
|
-
await updateAuth(providerID, { type: "wellknown", token: session });
|
|
137
|
-
}
|
|
138
|
-
catch (error) {
|
|
139
|
-
debugError("updateAuth:xyai", error);
|
|
140
|
-
return {
|
|
141
|
-
session,
|
|
142
|
-
warning: "session refreshed but failed to persist; using in-memory session",
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return { session };
|
|
147
|
-
}
|
|
148
|
-
async function fetchQuotaPayload(siteOrigin, session, timeoutMs) {
|
|
149
|
-
const response = await fetchWithTimeout(`${siteOrigin}/frontend-api/vibe-code/quota`, {
|
|
150
|
-
headers: {
|
|
151
|
-
Accept: "application/json",
|
|
152
|
-
Cookie: `share-session=${session}`,
|
|
153
|
-
"User-Agent": "opencode-quota-sidebar",
|
|
154
|
-
},
|
|
155
|
-
}, timeoutMs).catch(swallow("fetchXyaiQuota:quota"));
|
|
156
|
-
if (!response)
|
|
157
|
-
return { error: "network request failed" };
|
|
158
|
-
if (!response.ok)
|
|
159
|
-
return { error: `http ${response.status}` };
|
|
160
|
-
const payload = await response
|
|
161
|
-
.json()
|
|
162
|
-
.catch(swallow("fetchXyaiQuota:quotaJson"));
|
|
163
|
-
if (!isRecord(payload))
|
|
164
|
-
return { error: "invalid response" };
|
|
165
|
-
return { payload };
|
|
166
|
-
}
|
|
167
|
-
function isAuthFailure(payload) {
|
|
168
|
-
return payload.code === -1 || payload.msg === "认证失败,请重新登录";
|
|
169
|
-
}
|
|
170
|
-
function formatAmount(value) {
|
|
171
|
-
if (!Number.isFinite(value))
|
|
172
|
-
return "0";
|
|
173
|
-
if (Math.abs(value) >= 10) {
|
|
174
|
-
const one = value.toFixed(1);
|
|
175
|
-
return one.endsWith(".0") ? one.slice(0, -2) : one;
|
|
176
|
-
}
|
|
177
|
-
return value.toFixed(2);
|
|
178
|
-
}
|
|
179
|
-
function pickServicePayload(payload, preferred) {
|
|
180
|
-
const source = isRecord(payload.data) ? payload.data : payload;
|
|
181
|
-
const ordered = preferred === "claudecode"
|
|
182
|
-
? ["claudecode", "codex"]
|
|
183
|
-
: ["codex", "claudecode"];
|
|
184
|
-
for (const key of ordered) {
|
|
185
|
-
const value = source[key];
|
|
186
|
-
if (isRecord(value))
|
|
187
|
-
return { serviceType: key, value };
|
|
188
|
-
}
|
|
189
|
-
return undefined;
|
|
190
|
-
}
|
|
191
|
-
function parseQuotaSnapshot(args) {
|
|
192
|
-
const base = {
|
|
193
|
-
providerID: args.providerID,
|
|
194
|
-
adapterID: "xyai",
|
|
195
|
-
label: "XYAI",
|
|
196
|
-
shortLabel: "XYAI",
|
|
197
|
-
sortOrder: 7,
|
|
198
|
-
};
|
|
199
|
-
const subscriptions = isRecord(args.payload.subscriptions)
|
|
200
|
-
? args.payload.subscriptions
|
|
201
|
-
: undefined;
|
|
202
|
-
const usage = isRecord(args.payload.currentUsage)
|
|
203
|
-
? args.payload.currentUsage
|
|
204
|
-
: undefined;
|
|
205
|
-
const amountLimit = asNumber(subscriptions?.amountLimit);
|
|
206
|
-
const remainingAmount = asNumber(subscriptions?.remainingAmount);
|
|
207
|
-
const periodResetTime = toIso(subscriptions?.periodResetTime);
|
|
208
|
-
const expireTime = toIso(subscriptions?.expireTime);
|
|
209
|
-
if (amountLimit === undefined || remainingAmount === undefined) {
|
|
210
|
-
return {
|
|
211
|
-
...base,
|
|
212
|
-
status: "error",
|
|
213
|
-
checkedAt: args.checkedAt,
|
|
214
|
-
note: "missing quota fields",
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
const remainingPercent = amountLimit > 0
|
|
218
|
-
? Math.max(0, Math.min(100, (remainingAmount / amountLimit) * 100))
|
|
219
|
-
: undefined;
|
|
220
|
-
const windows = [
|
|
221
|
-
{
|
|
222
|
-
label: `Daily $${formatAmount(remainingAmount)}/$${formatAmount(amountLimit)}`,
|
|
223
|
-
showPercent: false,
|
|
224
|
-
remainingPercent,
|
|
225
|
-
resetAt: periodResetTime,
|
|
226
|
-
resetLabel: "Rst",
|
|
227
|
-
},
|
|
228
|
-
];
|
|
229
|
-
const noteParts = [
|
|
230
|
-
expireTime ? `exp ${expireTime.slice(5, 10)}` : undefined,
|
|
231
|
-
args.serviceType === "claudecode" ? "service=claudecode" : undefined,
|
|
232
|
-
args.warning,
|
|
233
|
-
].filter((value) => Boolean(value));
|
|
234
|
-
return {
|
|
235
|
-
...base,
|
|
236
|
-
status: "ok",
|
|
237
|
-
checkedAt: args.checkedAt,
|
|
238
|
-
remainingPercent,
|
|
239
|
-
resetAt: periodResetTime,
|
|
240
|
-
expiresAt: expireTime,
|
|
241
|
-
note: noteParts.join(" | ") || undefined,
|
|
242
|
-
windows,
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
async function fetchXyaiQuota(ctx) {
|
|
246
|
-
const checkedAt = Date.now();
|
|
247
|
-
const runtimeProviderID = typeof ctx.sourceProviderID === "string" && ctx.sourceProviderID
|
|
248
|
-
? ctx.sourceProviderID
|
|
249
|
-
: ctx.providerID;
|
|
250
|
-
const providerConfig = providerConfigFor(ctx.config, xyaiConfigCandidates(runtimeProviderID, ctx.providerID));
|
|
251
|
-
const siteOrigin = resolveSiteOrigin(providerConfig?.baseURL ?? ctx.providerOptions?.baseURL);
|
|
252
|
-
const serviceType = resolveServiceType(providerConfig);
|
|
253
|
-
const base = {
|
|
254
|
-
providerID: runtimeProviderID,
|
|
255
|
-
adapterID: "xyai",
|
|
256
|
-
label: "XYAI",
|
|
257
|
-
shortLabel: "XYAI",
|
|
258
|
-
sortOrder: 7,
|
|
259
|
-
};
|
|
260
|
-
let session = resolveSessionCookie(ctx.auth, providerConfig);
|
|
261
|
-
const login = resolveLogin(providerConfig);
|
|
262
|
-
let warning;
|
|
263
|
-
if (!session && login) {
|
|
264
|
-
const loginResult = await loginAndPersistSession(siteOrigin, login, ctx.providerID, ctx.updateAuth, ctx.config.quota.requestTimeoutMs);
|
|
265
|
-
if ("error" in loginResult) {
|
|
266
|
-
return {
|
|
267
|
-
...base,
|
|
268
|
-
status: "unavailable",
|
|
269
|
-
checkedAt,
|
|
270
|
-
note: loginResult.error,
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
session = loginResult.session;
|
|
274
|
-
warning = loginResult.warning;
|
|
275
|
-
}
|
|
276
|
-
if (!session) {
|
|
277
|
-
return {
|
|
278
|
-
...base,
|
|
279
|
-
status: "unavailable",
|
|
280
|
-
checkedAt,
|
|
281
|
-
note: "missing share-session or login credentials",
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
let quotaResult = await fetchQuotaPayload(siteOrigin, session, ctx.config.quota.requestTimeoutMs);
|
|
285
|
-
if (!("error" in quotaResult) &&
|
|
286
|
-
isAuthFailure(quotaResult.payload) &&
|
|
287
|
-
login) {
|
|
288
|
-
const loginResult = await loginAndPersistSession(siteOrigin, login, ctx.providerID, ctx.updateAuth, ctx.config.quota.requestTimeoutMs);
|
|
289
|
-
if (!("error" in loginResult)) {
|
|
290
|
-
session = loginResult.session;
|
|
291
|
-
warning = loginResult.warning ?? warning;
|
|
292
|
-
quotaResult = await fetchQuotaPayload(siteOrigin, session, ctx.config.quota.requestTimeoutMs);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
if ("error" in quotaResult) {
|
|
296
|
-
return {
|
|
297
|
-
...base,
|
|
298
|
-
status: "error",
|
|
299
|
-
checkedAt,
|
|
300
|
-
note: quotaResult.error,
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
if (isAuthFailure(quotaResult.payload)) {
|
|
304
|
-
return {
|
|
305
|
-
...base,
|
|
306
|
-
status: "unavailable",
|
|
307
|
-
checkedAt,
|
|
308
|
-
note: "auth expired",
|
|
309
|
-
};
|
|
310
|
-
}
|
|
311
|
-
const service = pickServicePayload(quotaResult.payload, serviceType);
|
|
312
|
-
if (!service) {
|
|
313
|
-
return {
|
|
314
|
-
...base,
|
|
315
|
-
status: "error",
|
|
316
|
-
checkedAt,
|
|
317
|
-
note: "missing service payload",
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
return parseQuotaSnapshot({
|
|
321
|
-
providerID: runtimeProviderID,
|
|
322
|
-
serviceType: service.serviceType,
|
|
323
|
-
payload: service.value,
|
|
324
|
-
checkedAt,
|
|
325
|
-
warning,
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
export const xyaiAdapter = {
|
|
329
|
-
id: "xyai",
|
|
330
|
-
label: "XYAI",
|
|
331
|
-
shortLabel: "XYAI",
|
|
332
|
-
sortOrder: 7,
|
|
333
|
-
normalizeID: (providerID) => providerID === "xyai" || providerID === "xyai-vibe" ? "xyai" : undefined,
|
|
334
|
-
matchScore: ({ providerID, providerOptions }) => {
|
|
335
|
-
if (providerID === "xyai")
|
|
336
|
-
return 100;
|
|
337
|
-
if (providerID === "xyai-vibe")
|
|
338
|
-
return 99;
|
|
339
|
-
return isXyaiBaseURL(providerOptions?.baseURL) ? 95 : 0;
|
|
340
|
-
},
|
|
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,
|
|
348
|
-
};
|