@leo000001/opencode-quota-sidebar 3.0.10 → 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.
Files changed (53) hide show
  1. package/CHANGELOG.md +0 -1
  2. package/README.md +157 -42
  3. package/README.zh-CN.md +157 -42
  4. package/SECURITY.md +1 -1
  5. package/dist/cli.d.ts +18 -0
  6. package/dist/cli.js +354 -0
  7. package/dist/cli_render.d.ts +17 -0
  8. package/dist/cli_render.js +292 -0
  9. package/dist/events.d.ts +1 -1
  10. package/dist/events.js +2 -2
  11. package/dist/format.d.ts +4 -0
  12. package/dist/format.js +302 -41
  13. package/dist/history_messages.d.ts +8 -0
  14. package/dist/history_messages.js +157 -0
  15. package/dist/history_usage.d.ts +93 -0
  16. package/dist/history_usage.js +251 -0
  17. package/dist/index.js +29 -4
  18. package/dist/period.d.ts +29 -1
  19. package/dist/period.js +187 -9
  20. package/dist/provider_catalog.d.ts +8 -0
  21. package/dist/provider_catalog.js +68 -0
  22. package/dist/providers/core/anthropic.d.ts +1 -1
  23. package/dist/providers/core/anthropic.js +69 -45
  24. package/dist/providers/core/openai.js +38 -2
  25. package/dist/providers/index.d.ts +1 -2
  26. package/dist/providers/index.js +1 -3
  27. package/dist/quota.d.ts +4 -2
  28. package/dist/quota.js +18 -21
  29. package/dist/quota_render.d.ts +1 -1
  30. package/dist/quota_render.js +23 -24
  31. package/dist/quota_service.d.ts +1 -0
  32. package/dist/quota_service.js +151 -19
  33. package/dist/storage.d.ts +1 -1
  34. package/dist/storage.js +4 -4
  35. package/dist/storage_dates.d.ts +1 -1
  36. package/dist/storage_dates.js +8 -5
  37. package/dist/storage_parse.js +23 -1
  38. package/dist/supported_quota.d.ts +4 -0
  39. package/dist/supported_quota.js +36 -0
  40. package/dist/title.js +18 -8
  41. package/dist/tools.d.ts +14 -3
  42. package/dist/tools.js +54 -2
  43. package/dist/tui.tsx +17 -6
  44. package/dist/tui_helpers.js +11 -6
  45. package/dist/types.d.ts +8 -0
  46. package/dist/usage.d.ts +18 -0
  47. package/dist/usage.js +93 -9
  48. package/dist/usage_service.d.ts +4 -1
  49. package/dist/usage_service.js +193 -189
  50. package/package.json +4 -1
  51. package/quota-sidebar.config.example.json +36 -45
  52. package/dist/providers/third_party/xyai.d.ts +0 -2
  53. 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
- };