qwenproxy-cli 1.5.2 → 1.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/dist/clean-cache.js +205 -22
- package/dist/delete-chats.js +199 -36
- package/dist/index.js +492 -57
- package/dist/login.js +95 -24
- package/dist/reset-cooldowns.js +205 -22
- package/package.json +3 -3
- package/src/api/server.ts +1 -0
- package/src/core/daily-usage.ts +276 -0
- package/src/core/database.ts +27 -1
- package/src/core/version.ts +1 -1
- package/src/routes/chat/account.ts +43 -10
- package/src/routes/chat/index.ts +1 -1
- package/src/routes/chat/streaming.ts +31 -3
- package/src/services/captcha-coordinator.ts +12 -7
- package/src/services/playwright.ts +76 -24
- package/src/services/qwen.ts +130 -16
- package/src/tui/proxy-client.ts +11 -1
- package/src/tui/types.ts +11 -0
- package/src/tui/views/accounts-view.ts +19 -2
- package/src/tui/views/status-view.ts +37 -4
package/dist/index.js
CHANGED
|
@@ -1592,7 +1592,26 @@ function runMigrations(db2) {
|
|
|
1592
1592
|
instruction_hash TEXT NOT NULL,
|
|
1593
1593
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1594
1594
|
);
|
|
1595
|
+
|
|
1596
|
+
CREATE TABLE IF NOT EXISTS account_daily_usage (
|
|
1597
|
+
account_id TEXT NOT NULL,
|
|
1598
|
+
date_utc TEXT NOT NULL,
|
|
1599
|
+
prompt_tokens INTEGER NOT NULL DEFAULT 0,
|
|
1600
|
+
completion_tokens INTEGER NOT NULL DEFAULT 0,
|
|
1601
|
+
weighted_tokens REAL NOT NULL DEFAULT 0,
|
|
1602
|
+
total_tokens INTEGER NOT NULL DEFAULT 0,
|
|
1603
|
+
turns_count INTEGER NOT NULL DEFAULT 0,
|
|
1604
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1605
|
+
PRIMARY KEY (account_id, date_utc)
|
|
1606
|
+
);
|
|
1607
|
+
|
|
1608
|
+
CREATE INDEX IF NOT EXISTS idx_daily_usage_date ON account_daily_usage(date_utc);
|
|
1595
1609
|
`);
|
|
1610
|
+
try {
|
|
1611
|
+
db2.exec(`ALTER TABLE account_daily_usage ADD COLUMN weighted_tokens REAL DEFAULT 0;`);
|
|
1612
|
+
} catch (err) {
|
|
1613
|
+
if (!isDuplicateColumnError(err)) throw err;
|
|
1614
|
+
}
|
|
1596
1615
|
try {
|
|
1597
1616
|
db2.exec(
|
|
1598
1617
|
`ALTER TABLE accounts ADD COLUMN cooldown_until INTEGER DEFAULT 0;`
|
|
@@ -1693,7 +1712,11 @@ function saveAuthSession(accountId, session) {
|
|
|
1693
1712
|
) VALUES (
|
|
1694
1713
|
?, ?, ?, ?, ?, ?,
|
|
1695
1714
|
?, ?, ?, ?,
|
|
1696
|
-
?,
|
|
1715
|
+
COALESCE(?, (SELECT user_id FROM qwen_auth_sessions WHERE account_id = ?)),
|
|
1716
|
+
?,
|
|
1717
|
+
COALESCE(?, (SELECT refresh_token FROM qwen_auth_sessions WHERE account_id = ?)),
|
|
1718
|
+
?,
|
|
1719
|
+
datetime('now')
|
|
1697
1720
|
)
|
|
1698
1721
|
`);
|
|
1699
1722
|
stmt.run(
|
|
@@ -1708,8 +1731,10 @@ function saveAuthSession(accountId, session) {
|
|
|
1708
1731
|
session.secChUaPlatform || null,
|
|
1709
1732
|
session.version || null,
|
|
1710
1733
|
session.userId || null,
|
|
1734
|
+
accountId,
|
|
1711
1735
|
session.tokenExpiresAt || null,
|
|
1712
1736
|
session.refreshToken || null,
|
|
1737
|
+
accountId,
|
|
1713
1738
|
session.capturedAt ?? Date.now()
|
|
1714
1739
|
);
|
|
1715
1740
|
}
|
|
@@ -5194,6 +5219,11 @@ async function getBasicHeaders(accountId) {
|
|
|
5194
5219
|
if (!page) {
|
|
5195
5220
|
throw new Error(`Playwright not initialized for account: ${accountId}`);
|
|
5196
5221
|
}
|
|
5222
|
+
const inFlightInit = inFlightAccountInits.get(accountId);
|
|
5223
|
+
if (inFlightInit) {
|
|
5224
|
+
await inFlightInit.catch(() => {
|
|
5225
|
+
});
|
|
5226
|
+
}
|
|
5197
5227
|
const release = await acquireAccountMutex(
|
|
5198
5228
|
accountId,
|
|
5199
5229
|
`headers:${accountId.substring(0, 12)}`
|
|
@@ -6668,6 +6698,11 @@ async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.ti
|
|
|
6668
6698
|
const { saveAuthSession: saveAuthSession2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
6669
6699
|
const { parseJwtExpiry: parseJwtExpiry2 } = await Promise.resolve().then(() => (init_jwt(), jwt_exports));
|
|
6670
6700
|
const tokenExpiry = parseJwtExpiry2(capturedHeaders.cookie);
|
|
6701
|
+
let rTok;
|
|
6702
|
+
try {
|
|
6703
|
+
rTok = (await page.evaluate(() => localStorage.getItem("refresh_token") || "")).trim() || void 0;
|
|
6704
|
+
} catch {
|
|
6705
|
+
}
|
|
6671
6706
|
saveAuthSession2(accountId, {
|
|
6672
6707
|
cookie: capturedHeaders.cookie,
|
|
6673
6708
|
userAgent: capturedHeaders["user-agent"],
|
|
@@ -6679,6 +6714,7 @@ async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.ti
|
|
|
6679
6714
|
secChUaPlatform: capturedHeaders["sec-ch-ua-platform"],
|
|
6680
6715
|
version: capturedHeaders["version"],
|
|
6681
6716
|
tokenExpiresAt: tokenExpiry || void 0,
|
|
6717
|
+
refreshToken: rTok,
|
|
6682
6718
|
capturedAt: Date.now()
|
|
6683
6719
|
});
|
|
6684
6720
|
} catch {
|
|
@@ -6710,38 +6746,57 @@ async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.ti
|
|
|
6710
6746
|
const isAuthUrl = currentUrl.includes("/auth") || currentUrl.includes("/login");
|
|
6711
6747
|
const isSuspectedGuest = isAuthUrl || attempt >= 2 && !await isPageLoggedIn(page, 5e3);
|
|
6712
6748
|
if (isSuspectedGuest) {
|
|
6713
|
-
const
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
`\u26A0\uFE0F [Playwright] Session expired or guest state detected during header capture for ${accountId}; re-authenticating...`
|
|
6749
|
+
const silentRefresh = await refreshAccountToken(accountId);
|
|
6750
|
+
if (silentRefresh.success) {
|
|
6751
|
+
console.log(
|
|
6752
|
+
`\u26A1 [Playwright] Token refreshed silently during header capture for ${accountId}`
|
|
6718
6753
|
);
|
|
6719
|
-
|
|
6720
|
-
const ok = await loginToQwen(accountId, creds.email, creds.password);
|
|
6721
|
-
if (ok) {
|
|
6754
|
+
if (isAuthUrl) {
|
|
6722
6755
|
await page.goto(qwenUrl("/"), {
|
|
6723
6756
|
waitUntil: "domcontentloaded",
|
|
6724
6757
|
timeout: Math.min(config.timeouts.navigation, timeoutMs)
|
|
6725
6758
|
});
|
|
6726
|
-
await sleep2(
|
|
6759
|
+
await sleep2(1500);
|
|
6727
6760
|
}
|
|
6728
|
-
if (
|
|
6761
|
+
if (await isPageLoggedIn(page, 5e3)) {
|
|
6762
|
+
armOverallDeadline();
|
|
6763
|
+
}
|
|
6764
|
+
}
|
|
6765
|
+
const stillSuspected = !silentRefresh.success || !await isPageLoggedIn(page, 2e3);
|
|
6766
|
+
if (stillSuspected) {
|
|
6767
|
+
const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
6768
|
+
const creds = getAccountCredentials2(accountId);
|
|
6769
|
+
if (creds && creds.email && creds.password) {
|
|
6770
|
+
console.warn(
|
|
6771
|
+
`\u26A0\uFE0F [Playwright] Session expired or guest state detected during header capture for ${accountId}; re-authenticating...`
|
|
6772
|
+
);
|
|
6773
|
+
armOverallDeadline();
|
|
6774
|
+
const ok = await loginToQwen(accountId, creds.email, creds.password);
|
|
6775
|
+
if (ok) {
|
|
6776
|
+
await page.goto(qwenUrl("/"), {
|
|
6777
|
+
waitUntil: "domcontentloaded",
|
|
6778
|
+
timeout: Math.min(config.timeouts.navigation, timeoutMs)
|
|
6779
|
+
});
|
|
6780
|
+
await sleep2(2e3);
|
|
6781
|
+
}
|
|
6782
|
+
if (!ok || !await isPageLoggedIn(page, 6e3)) {
|
|
6783
|
+
settle(
|
|
6784
|
+
new Error(
|
|
6785
|
+
`Header capture failed for ${accountId}: re-login after session expiry did not succeed`
|
|
6786
|
+
)
|
|
6787
|
+
);
|
|
6788
|
+
return;
|
|
6789
|
+
}
|
|
6790
|
+
armOverallDeadline();
|
|
6791
|
+
if (settled || page.isClosed()) return;
|
|
6792
|
+
} else {
|
|
6729
6793
|
settle(
|
|
6730
6794
|
new Error(
|
|
6731
|
-
`Header capture failed for ${accountId}: re-login
|
|
6795
|
+
`Header capture failed for ${accountId}: session expired and no credentials available for re-login (run 'qpx login')`
|
|
6732
6796
|
)
|
|
6733
6797
|
);
|
|
6734
6798
|
return;
|
|
6735
6799
|
}
|
|
6736
|
-
armOverallDeadline();
|
|
6737
|
-
if (settled || page.isClosed()) return;
|
|
6738
|
-
} else {
|
|
6739
|
-
settle(
|
|
6740
|
-
new Error(
|
|
6741
|
-
`Header capture failed for ${accountId}: session expired and no credentials available for re-login (run 'qpx login')`
|
|
6742
|
-
)
|
|
6743
|
-
);
|
|
6744
|
-
return;
|
|
6745
6800
|
}
|
|
6746
6801
|
}
|
|
6747
6802
|
if (settled || page.isClosed()) return;
|
|
@@ -7057,7 +7112,13 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
|
|
|
7057
7112
|
`[Playwright] Navigation check failed during refresh for ${accountId}:`,
|
|
7058
7113
|
navErr.message
|
|
7059
7114
|
);
|
|
7060
|
-
await
|
|
7115
|
+
if (await isPageLoggedIn(page, 3e3)) {
|
|
7116
|
+
} else {
|
|
7117
|
+
const refreshed = await refreshAccountToken(accountId);
|
|
7118
|
+
if (!refreshed.success) {
|
|
7119
|
+
await executeReauth();
|
|
7120
|
+
}
|
|
7121
|
+
}
|
|
7061
7122
|
}
|
|
7062
7123
|
}
|
|
7063
7124
|
if (hasRequiredQwenHeaders(cache3.headers)) {
|
|
@@ -7071,6 +7132,11 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
|
|
|
7071
7132
|
const { saveAuthSession: saveAuthSession2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
7072
7133
|
const tokenCookie = liveCookies.find((c) => c.name === "token");
|
|
7073
7134
|
const exp = tokenCookie ? parseJwtExpiry(tokenCookie.value) : void 0;
|
|
7135
|
+
let rTok;
|
|
7136
|
+
try {
|
|
7137
|
+
rTok = (await page.evaluate(() => localStorage.getItem("refresh_token") || "")).trim() || void 0;
|
|
7138
|
+
} catch {
|
|
7139
|
+
}
|
|
7074
7140
|
saveAuthSession2(accountId, {
|
|
7075
7141
|
cookie: cookieStr,
|
|
7076
7142
|
userAgent: cache3.headers["user-agent"] || "",
|
|
@@ -7082,6 +7148,7 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
|
|
|
7082
7148
|
secChUaPlatform: cache3.headers["sec-ch-ua-platform"] || void 0,
|
|
7083
7149
|
version: cache3.headers["version"] || void 0,
|
|
7084
7150
|
tokenExpiresAt: exp || void 0,
|
|
7151
|
+
refreshToken: rTok,
|
|
7085
7152
|
capturedAt: Date.now()
|
|
7086
7153
|
});
|
|
7087
7154
|
} catch {
|
|
@@ -7468,8 +7535,10 @@ async function keepAlivePlaywrightAccount(accountId) {
|
|
|
7468
7535
|
if (!page || page.isClosed()) return false;
|
|
7469
7536
|
const now = Date.now();
|
|
7470
7537
|
const currentUrl = page.url();
|
|
7538
|
+
const lastRefreshAttempt = lastProactiveTokenRefresh.get(accountId) ?? 0;
|
|
7471
7539
|
const cookie = await getCookies(accountId);
|
|
7472
|
-
if (cookie && isTokenExpiringSoon(cookie, 5)) {
|
|
7540
|
+
if (cookie && isTokenExpiringSoon(cookie, 5) && now - lastRefreshAttempt > 6e4) {
|
|
7541
|
+
lastProactiveTokenRefresh.set(accountId, now);
|
|
7473
7542
|
console.log(
|
|
7474
7543
|
`\u{1F493} [SessionKeeper] Account ${accountId} access token expiring soon (<5m); silently refreshing...`
|
|
7475
7544
|
);
|
|
@@ -7513,6 +7582,7 @@ function cleanupPlaywrightAccountState(accountId) {
|
|
|
7513
7582
|
cookieCaches.delete(accountId);
|
|
7514
7583
|
lastAccountActivity.delete(accountId);
|
|
7515
7584
|
lastKeepAliveNavigation.delete(accountId);
|
|
7585
|
+
lastProactiveTokenRefresh.delete(accountId);
|
|
7516
7586
|
clearFingerprintCache(accountId);
|
|
7517
7587
|
unmarkAccountHeadersReady(accountId);
|
|
7518
7588
|
}
|
|
@@ -7696,7 +7766,7 @@ async function getTokenDiagnostics(accountId) {
|
|
|
7696
7766
|
}
|
|
7697
7767
|
};
|
|
7698
7768
|
}
|
|
7699
|
-
var requireLocal, contextInitHooks, ACCOUNT_MUTEX_MAX_HOLD_MS, accountMutexes, accountContexts, accountPages, cachedUserAgents, inFlightAccountInits, sharedBrowser, sharedBrowserPromise, headerCaches, HEADER_CACHE_TTL, HEADER_REFRESH_THRESHOLD, COOKIE_CACHE_TTL, cookieCaches, lastAccountActivity, lastKeepAliveNavigation, profileResetQueue, profileResetChain, closingAllPlaywright, sleep2, HEADER_CAPTURE_SETTLE_MS, PLAYWRIGHT_MUTEX_WAIT_MS, ACCOUNT_PAGE_OPERATION_TIMEOUT_MS, SESSION_PROBE_NAVIGATION_TIMEOUT_MS, HEADER_CAPTURE_TRIGGER_GRACE_MS, FIRST_TRIGGER_GRACE_MS, HEADER_CAPTURE_TRIGGER_ATTEMPTS, CHAT_INPUT_APPEAR_TIMEOUT_MS, CHAT_INPUT_ACTION_TIMEOUT_MS, PROFILE_RESET_TIMEOUT_MS;
|
|
7769
|
+
var requireLocal, contextInitHooks, ACCOUNT_MUTEX_MAX_HOLD_MS, accountMutexes, accountContexts, accountPages, cachedUserAgents, inFlightAccountInits, sharedBrowser, sharedBrowserPromise, headerCaches, HEADER_CACHE_TTL, HEADER_REFRESH_THRESHOLD, COOKIE_CACHE_TTL, cookieCaches, lastAccountActivity, lastKeepAliveNavigation, lastProactiveTokenRefresh, profileResetQueue, profileResetChain, closingAllPlaywright, sleep2, HEADER_CAPTURE_SETTLE_MS, PLAYWRIGHT_MUTEX_WAIT_MS, ACCOUNT_PAGE_OPERATION_TIMEOUT_MS, SESSION_PROBE_NAVIGATION_TIMEOUT_MS, HEADER_CAPTURE_TRIGGER_GRACE_MS, FIRST_TRIGGER_GRACE_MS, HEADER_CAPTURE_TRIGGER_ATTEMPTS, CHAT_INPUT_APPEAR_TIMEOUT_MS, CHAT_INPUT_ACTION_TIMEOUT_MS, PROFILE_RESET_TIMEOUT_MS;
|
|
7700
7770
|
var init_playwright = __esm({
|
|
7701
7771
|
"src/services/playwright.ts"() {
|
|
7702
7772
|
"use strict";
|
|
@@ -7732,6 +7802,7 @@ var init_playwright = __esm({
|
|
|
7732
7802
|
cookieCaches = /* @__PURE__ */ new Map();
|
|
7733
7803
|
lastAccountActivity = /* @__PURE__ */ new Map();
|
|
7734
7804
|
lastKeepAliveNavigation = /* @__PURE__ */ new Map();
|
|
7805
|
+
lastProactiveTokenRefresh = /* @__PURE__ */ new Map();
|
|
7735
7806
|
profileResetQueue = /* @__PURE__ */ new Map();
|
|
7736
7807
|
profileResetChain = Promise.resolve();
|
|
7737
7808
|
closingAllPlaywright = false;
|
|
@@ -8547,9 +8618,13 @@ var init_model_registry = __esm({
|
|
|
8547
8618
|
// src/services/captcha-coordinator.ts
|
|
8548
8619
|
var captcha_coordinator_exports = {};
|
|
8549
8620
|
__export(captcha_coordinator_exports, {
|
|
8621
|
+
isCaptchaRecoveryActive: () => isCaptchaRecoveryActive,
|
|
8550
8622
|
recoverBaxiaCaptcha: () => recoverBaxiaCaptcha,
|
|
8551
8623
|
solveChallengeOnPage: () => solveChallengeOnPage
|
|
8552
8624
|
});
|
|
8625
|
+
function isCaptchaRecoveryActive(accountId) {
|
|
8626
|
+
return activeCaptchaRecoveries.has(accountId);
|
|
8627
|
+
}
|
|
8553
8628
|
async function gotoBestEffort(page, url) {
|
|
8554
8629
|
await page.goto(url, {
|
|
8555
8630
|
waitUntil: "domcontentloaded",
|
|
@@ -8592,11 +8667,12 @@ async function recoverBaxiaCaptcha(accountId, label, options = {}) {
|
|
|
8592
8667
|
return false;
|
|
8593
8668
|
}
|
|
8594
8669
|
metrics.increment("captcha.challenges.detected", 1, { solver });
|
|
8595
|
-
const challengeUrl = options.challengeBody ? extractBaxiaChallengeUrl(options.challengeBody, config.qwen.baseUrl) : null;
|
|
8596
8670
|
const solverOperationTimeoutMs = Math.max(
|
|
8597
8671
|
15e3,
|
|
8598
8672
|
config.captcha.timeoutMs + config.captcha.maxAttempts * (3e3 + config.captcha.retryDelayMs + config.captcha.settleMs) + 2 * CHALLENGE_NAVIGATION_TIMEOUT_MS
|
|
8599
8673
|
);
|
|
8674
|
+
const challengeUrl = options.challengeBody ? extractBaxiaChallengeUrl(options.challengeBody, config.qwen.baseUrl) : null;
|
|
8675
|
+
activeCaptchaRecoveries.add(accountId);
|
|
8600
8676
|
try {
|
|
8601
8677
|
const solved = await withAccountPage(
|
|
8602
8678
|
accountId,
|
|
@@ -8629,9 +8705,11 @@ async function recoverBaxiaCaptcha(accountId, label, options = {}) {
|
|
|
8629
8705
|
detail
|
|
8630
8706
|
});
|
|
8631
8707
|
return false;
|
|
8708
|
+
} finally {
|
|
8709
|
+
activeCaptchaRecoveries.delete(accountId);
|
|
8632
8710
|
}
|
|
8633
8711
|
}
|
|
8634
|
-
var CHALLENGE_NAVIGATION_TIMEOUT_MS, CHALLENGE_PATH_MARKER, FAILED_RECOVERY_BACKOFF_MS, lastFailedRecoveryAt;
|
|
8712
|
+
var CHALLENGE_NAVIGATION_TIMEOUT_MS, CHALLENGE_PATH_MARKER, FAILED_RECOVERY_BACKOFF_MS, lastFailedRecoveryAt, activeCaptchaRecoveries;
|
|
8635
8713
|
var init_captcha_coordinator = __esm({
|
|
8636
8714
|
"src/services/captcha-coordinator.ts"() {
|
|
8637
8715
|
"use strict";
|
|
@@ -8644,6 +8722,7 @@ var init_captcha_coordinator = __esm({
|
|
|
8644
8722
|
CHALLENGE_PATH_MARKER = "_____tmd_____";
|
|
8645
8723
|
FAILED_RECOVERY_BACKOFF_MS = 3e4;
|
|
8646
8724
|
lastFailedRecoveryAt = /* @__PURE__ */ new Map();
|
|
8725
|
+
activeCaptchaRecoveries = /* @__PURE__ */ new Set();
|
|
8647
8726
|
}
|
|
8648
8727
|
});
|
|
8649
8728
|
|
|
@@ -11110,6 +11189,7 @@ __export(qwen_exports, {
|
|
|
11110
11189
|
fetchRemoteQwenChats: () => fetchRemoteQwenChats,
|
|
11111
11190
|
flushLogicalThreadState: () => flushLogicalThreadState,
|
|
11112
11191
|
getAnyCachedQwenModels: () => getAnyCachedQwenModels,
|
|
11192
|
+
getInFlightPersonalizationCount: () => getInFlightPersonalizationCount,
|
|
11113
11193
|
getLogicalThreadState: () => getLogicalThreadState,
|
|
11114
11194
|
getQwenErrorCode: () => getQwenErrorCode,
|
|
11115
11195
|
getSessionParent: () => getSessionParent,
|
|
@@ -12042,12 +12122,51 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
|
|
|
12042
12122
|
captchaWatcher?.stop();
|
|
12043
12123
|
}
|
|
12044
12124
|
}
|
|
12125
|
+
function getInFlightPersonalizationCount() {
|
|
12126
|
+
return inFlightPersonalizationSyncs.size;
|
|
12127
|
+
}
|
|
12045
12128
|
async function syncQwenRequestPersonalization(instruction, accountId, metadata = {}) {
|
|
12046
|
-
if (isAuthMockEnabled()) {
|
|
12047
|
-
if (process.env.TEST_PERSONALIZATION_SYNC_FAIL === "true") return false;
|
|
12048
|
-
return true;
|
|
12049
|
-
}
|
|
12050
12129
|
const cacheKey = accountId || "global";
|
|
12130
|
+
const sent = textSize(instruction);
|
|
12131
|
+
const syncHash = sent.hash ? `${sent.hash}:${QWEN_SAFE_SETTINGS_HASH}` : null;
|
|
12132
|
+
const inFlightKey = `${cacheKey}:${syncHash || "empty"}`;
|
|
12133
|
+
const existingInFlight = inFlightPersonalizationSyncs.get(inFlightKey);
|
|
12134
|
+
if (existingInFlight) {
|
|
12135
|
+
logger.debug("[Qwen] Personalization sync already in flight, sharing promise", {
|
|
12136
|
+
accountId: cacheKey
|
|
12137
|
+
});
|
|
12138
|
+
return existingInFlight;
|
|
12139
|
+
}
|
|
12140
|
+
let syncPromise;
|
|
12141
|
+
syncPromise = (async () => {
|
|
12142
|
+
await Promise.resolve();
|
|
12143
|
+
try {
|
|
12144
|
+
if (isAuthMockEnabled()) {
|
|
12145
|
+
const delay = parseInt(process.env.TEST_PERSONALIZATION_DELAY_MS || "0", 10);
|
|
12146
|
+
if (delay > 0) {
|
|
12147
|
+
await sleep5(delay);
|
|
12148
|
+
}
|
|
12149
|
+
if (process.env.TEST_PERSONALIZATION_SYNC_FAIL === "true") return false;
|
|
12150
|
+
return true;
|
|
12151
|
+
}
|
|
12152
|
+
return await syncQwenRequestPersonalizationInternal(
|
|
12153
|
+
instruction,
|
|
12154
|
+
accountId,
|
|
12155
|
+
metadata,
|
|
12156
|
+
cacheKey,
|
|
12157
|
+
sent,
|
|
12158
|
+
syncHash
|
|
12159
|
+
);
|
|
12160
|
+
} finally {
|
|
12161
|
+
if (inFlightPersonalizationSyncs.get(inFlightKey) === syncPromise) {
|
|
12162
|
+
inFlightPersonalizationSyncs.delete(inFlightKey);
|
|
12163
|
+
}
|
|
12164
|
+
}
|
|
12165
|
+
})();
|
|
12166
|
+
inFlightPersonalizationSyncs.set(inFlightKey, syncPromise);
|
|
12167
|
+
return syncPromise;
|
|
12168
|
+
}
|
|
12169
|
+
async function syncQwenRequestPersonalizationInternal(instruction, accountId, metadata, cacheKey, sent, syncHash) {
|
|
12051
12170
|
let forceRefresh = false;
|
|
12052
12171
|
try {
|
|
12053
12172
|
const basic = await getBasicHeaders2(accountId);
|
|
@@ -12065,8 +12184,6 @@ async function syncQwenRequestPersonalization(instruction, accountId, metadata =
|
|
|
12065
12184
|
});
|
|
12066
12185
|
let currentSettings = null;
|
|
12067
12186
|
let payload = buildQwenSettingsUpdatePayload(currentSettings, instruction);
|
|
12068
|
-
const sent = textSize(instruction);
|
|
12069
|
-
const syncHash = sent.hash ? `${sent.hash}:${QWEN_SAFE_SETTINGS_HASH}` : null;
|
|
12070
12187
|
const bypassCache = metadata.forceSync === true;
|
|
12071
12188
|
const cachedHash = lastSyncedPersonalizationHashes.get(cacheKey);
|
|
12072
12189
|
if (!bypassCache && syncHash && cachedHash === syncHash) {
|
|
@@ -12736,9 +12853,13 @@ function parseQwenJsonError(raw, status, accountId) {
|
|
|
12736
12853
|
}
|
|
12737
12854
|
if (errorJson?.success === false) {
|
|
12738
12855
|
const code = errorJson.data?.code || errorJson.code || "UpstreamError";
|
|
12739
|
-
|
|
12856
|
+
const detailsStr = typeof details === "string" ? details : "";
|
|
12857
|
+
const codeStr = String(code).toLowerCase();
|
|
12858
|
+
const detailsLower = detailsStr.toLowerCase();
|
|
12859
|
+
const isAuthExpired = status === 401 || codeStr.includes("unauthorized") || detailsLower.includes("token has expired") || detailsLower.includes("log in") || detailsLower.includes("login") || detailsLower.includes("session") || detailsLower.includes("n\xE3o autorizado") || detailsLower.includes("unauthorized");
|
|
12860
|
+
if (isAuthExpired) {
|
|
12740
12861
|
return new QwenSessionExpiredError(
|
|
12741
|
-
`Session expired: ${
|
|
12862
|
+
`Session expired: ${detailsStr || codeStr}`,
|
|
12742
12863
|
accountId || "global"
|
|
12743
12864
|
);
|
|
12744
12865
|
}
|
|
@@ -13277,8 +13398,24 @@ async function createQwenStreamInternal(prompt, enableThinking, modelId, forcedP
|
|
|
13277
13398
|
)
|
|
13278
13399
|
);
|
|
13279
13400
|
}
|
|
13401
|
+
const parsedJsonErr = parseQwenJsonError(errText, response.status, accountId);
|
|
13402
|
+
if (parsedJsonErr instanceof QwenSessionExpiredError && accountId && !retriedNonSseResponse) {
|
|
13403
|
+
retriedNonSseResponse = true;
|
|
13404
|
+
logger.warn(
|
|
13405
|
+
"[Qwen] Token expired on completion fetch (200 JSON); refreshing session and retrying",
|
|
13406
|
+
{ accountId, chatId: chatSessionId ?? "new" }
|
|
13407
|
+
);
|
|
13408
|
+
const { refreshAccountToken: refreshAccountToken2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
|
|
13409
|
+
const refreshed = await refreshAccountToken2(accountId);
|
|
13410
|
+
if (refreshed.success) {
|
|
13411
|
+
const fresh = await getQwenHeaders(false, accountId);
|
|
13412
|
+
activeHeaders = fresh.headers;
|
|
13413
|
+
response = await fetchCompletion(activeHeaders);
|
|
13414
|
+
continue;
|
|
13415
|
+
}
|
|
13416
|
+
}
|
|
13280
13417
|
throw withCreatedChatMetadata(
|
|
13281
|
-
|
|
13418
|
+
parsedJsonErr ?? new QwenUpstreamError(
|
|
13282
13419
|
`Qwen returned non-stream JSON response: ${errText.substring(0, 300)}`,
|
|
13283
13420
|
"NonStreamJsonResponse",
|
|
13284
13421
|
502
|
|
@@ -13329,6 +13466,31 @@ async function createQwenStreamInternal(prompt, enableThinking, modelId, forcedP
|
|
|
13329
13466
|
response.status,
|
|
13330
13467
|
accountId
|
|
13331
13468
|
);
|
|
13469
|
+
if (parsedError instanceof QwenSessionExpiredError && accountId && !retriedNonSseResponse) {
|
|
13470
|
+
retriedNonSseResponse = true;
|
|
13471
|
+
logger.warn(
|
|
13472
|
+
"[Qwen] Token expired on completion fetch (!response.ok); refreshing session and retrying",
|
|
13473
|
+
{ accountId, chatId: chatSessionId ?? "new", status: response.status }
|
|
13474
|
+
);
|
|
13475
|
+
const { refreshAccountToken: refreshAccountToken2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
|
|
13476
|
+
const refreshed = await refreshAccountToken2(accountId);
|
|
13477
|
+
if (refreshed.success) {
|
|
13478
|
+
const fresh = await getQwenHeaders(false, accountId);
|
|
13479
|
+
activeHeaders = fresh.headers;
|
|
13480
|
+
response = await fetchCompletion(activeHeaders);
|
|
13481
|
+
if (response.ok && response.body) {
|
|
13482
|
+
return {
|
|
13483
|
+
stream: wrapUpstreamStream(response.body, controller),
|
|
13484
|
+
headers: activeHeaders,
|
|
13485
|
+
uiSessionId: chatSessionId || "",
|
|
13486
|
+
controller,
|
|
13487
|
+
accountId: accountId ?? "global",
|
|
13488
|
+
createdNewChat,
|
|
13489
|
+
tokenEstimationContext
|
|
13490
|
+
};
|
|
13491
|
+
}
|
|
13492
|
+
}
|
|
13493
|
+
}
|
|
13332
13494
|
if (parsedError) {
|
|
13333
13495
|
throw withCreatedChatMetadata(parsedError);
|
|
13334
13496
|
}
|
|
@@ -13366,7 +13528,7 @@ async function createQwenStreamInternal(prompt, enableThinking, modelId, forcedP
|
|
|
13366
13528
|
clearTimeout(timeoutId);
|
|
13367
13529
|
}
|
|
13368
13530
|
}
|
|
13369
|
-
var sleep5, BROWSER_STREAM_BINDING, BROWSER_ABORTERS_KEY, BROWSER_STREAM_FLUSH_BYTES, BROWSER_STREAM_FLUSH_MS, METADATA_TIMEOUT_PER_PAYLOAD_MB_MS, POST_CAPTCHA_METADATA_GRACE_MS, browserStreamStates, browserStreamBindingPages, browserStreamBindingContexts, MODEL_CACHE_TTL_MS, modelsCache, nativeToolsDisabled, disablingNativeToolsInProgress, lastSyncedPersonalizationHashes, directSettingsFetchConsecutiveFailures, DIRECT_SETTINGS_FETCH_BLOCK_THRESHOLD, directSettingsFetchBlocked, DIRECT_SETTINGS_FETCH_TIMEOUT_MS, activePersonalizationByAccount, accountStreamMutexes, QWEN_SAFE_SETTINGS_PATCH, QWEN_SAFE_SETTINGS_HASH, DISABLE_TOOLS_MAX_RETRIES, DISABLE_TOOLS_BACKOFF_MS, UPSTREAM_RESPONSE_PREVIEW_BYTES;
|
|
13531
|
+
var sleep5, BROWSER_STREAM_BINDING, BROWSER_ABORTERS_KEY, BROWSER_STREAM_FLUSH_BYTES, BROWSER_STREAM_FLUSH_MS, METADATA_TIMEOUT_PER_PAYLOAD_MB_MS, POST_CAPTCHA_METADATA_GRACE_MS, browserStreamStates, browserStreamBindingPages, browserStreamBindingContexts, MODEL_CACHE_TTL_MS, modelsCache, nativeToolsDisabled, disablingNativeToolsInProgress, lastSyncedPersonalizationHashes, directSettingsFetchConsecutiveFailures, DIRECT_SETTINGS_FETCH_BLOCK_THRESHOLD, directSettingsFetchBlocked, DIRECT_SETTINGS_FETCH_TIMEOUT_MS, activePersonalizationByAccount, accountStreamMutexes, QWEN_SAFE_SETTINGS_PATCH, QWEN_SAFE_SETTINGS_HASH, inFlightPersonalizationSyncs, DISABLE_TOOLS_MAX_RETRIES, DISABLE_TOOLS_BACKOFF_MS, UPSTREAM_RESPONSE_PREVIEW_BYTES;
|
|
13370
13532
|
var init_qwen = __esm({
|
|
13371
13533
|
"src/services/qwen.ts"() {
|
|
13372
13534
|
"use strict";
|
|
@@ -13453,6 +13615,7 @@ var init_qwen = __esm({
|
|
|
13453
13615
|
}
|
|
13454
13616
|
};
|
|
13455
13617
|
QWEN_SAFE_SETTINGS_HASH = crypto7.createHash("sha256").update(JSON.stringify(QWEN_SAFE_SETTINGS_PATCH)).digest("hex").slice(0, 12);
|
|
13618
|
+
inFlightPersonalizationSyncs = /* @__PURE__ */ new Map();
|
|
13456
13619
|
DISABLE_TOOLS_MAX_RETRIES = 3;
|
|
13457
13620
|
DISABLE_TOOLS_BACKOFF_MS = 2e3;
|
|
13458
13621
|
UPSTREAM_RESPONSE_PREVIEW_BYTES = 8 * 1024;
|
|
@@ -16385,8 +16548,9 @@ function resolveInitialAccount(preferredAccountId, excludeAccountIds) {
|
|
|
16385
16548
|
syncCooldownsFromDb(configuredAccounts);
|
|
16386
16549
|
const excluded = new Set(excludeAccountIds ?? []);
|
|
16387
16550
|
if (typeof preferredAccountId === "string" && preferredAccountId) {
|
|
16551
|
+
const needle = preferredAccountId.toLowerCase();
|
|
16388
16552
|
const preferred = configuredAccounts.find(
|
|
16389
|
-
(candidate) => candidate.id === preferredAccountId
|
|
16553
|
+
(candidate) => candidate.id === preferredAccountId || candidate.id.toLowerCase().startsWith(needle) || candidate.email.toLowerCase() === needle || candidate.email.toLowerCase().includes(needle)
|
|
16390
16554
|
);
|
|
16391
16555
|
if (preferred && !getAccountCooldownInfo(preferred.id)) {
|
|
16392
16556
|
return { account: preferred, configuredAccounts };
|
|
@@ -16741,7 +16905,12 @@ function shouldWaitQueueForever(isThreadOwnerWaiting, heldByOtherSession, hasFre
|
|
|
16741
16905
|
}
|
|
16742
16906
|
async function tryCreateStreamWithRetry(params, accountId, accountEmail) {
|
|
16743
16907
|
const maxAttempts = Math.max(1, config.retry.maxAttempts);
|
|
16744
|
-
const
|
|
16908
|
+
const accounts = loadAccounts();
|
|
16909
|
+
const configuredAccountCount = accounts.length;
|
|
16910
|
+
const maxAccountSwitches = Math.max(
|
|
16911
|
+
config.retry.maxAccountSwitches,
|
|
16912
|
+
Math.min(configuredAccountCount - 1, 6)
|
|
16913
|
+
);
|
|
16745
16914
|
let attemptsLeft = maxAttempts;
|
|
16746
16915
|
let retryDelay = config.retry.baseDelayMs;
|
|
16747
16916
|
let attempt = 0;
|
|
@@ -16753,7 +16922,6 @@ async function tryCreateStreamWithRetry(params, accountId, accountEmail) {
|
|
|
16753
16922
|
let chatInProgressOriginAccountEmail = null;
|
|
16754
16923
|
let lastAttemptError = null;
|
|
16755
16924
|
let invalidInputSameAccountRetried = false;
|
|
16756
|
-
const accounts = loadAccounts();
|
|
16757
16925
|
const isSingleAccount = accounts.length <= 1;
|
|
16758
16926
|
let currentAccountId = accountId;
|
|
16759
16927
|
let currentAccountEmail = accountEmail;
|
|
@@ -16966,14 +17134,24 @@ ${loopReminder}` : truncation.prompt;
|
|
|
16966
17134
|
const acquireDeadlineMs = config.concurrency.acquireDeadlineMs;
|
|
16967
17135
|
let acquireDeadlineTimer;
|
|
16968
17136
|
const acquireDeadline = new Promise((_, reject) => {
|
|
16969
|
-
|
|
17137
|
+
const checkDeadline = async () => {
|
|
17138
|
+
try {
|
|
17139
|
+
const { isCaptchaRecoveryActive: isCaptchaRecoveryActive2 } = await Promise.resolve().then(() => (init_captcha_coordinator(), captcha_coordinator_exports));
|
|
17140
|
+
if (currentAccountId && isCaptchaRecoveryActive2(currentAccountId)) {
|
|
17141
|
+
acquireDeadlineTimer = setTimeout(checkDeadline, 3e4);
|
|
17142
|
+
acquireDeadlineTimer.unref?.();
|
|
17143
|
+
return;
|
|
17144
|
+
}
|
|
17145
|
+
} catch {
|
|
17146
|
+
}
|
|
16970
17147
|
acquireAbort.abort();
|
|
16971
17148
|
const err2 = new Error(
|
|
16972
17149
|
`Acquire deadline (${acquireDeadlineMs}ms) exceeded creating stream on ${currentAccountEmail}`
|
|
16973
17150
|
);
|
|
16974
17151
|
err2.code = "acquire_deadline";
|
|
16975
17152
|
reject(err2);
|
|
16976
|
-
}
|
|
17153
|
+
};
|
|
17154
|
+
acquireDeadlineTimer = setTimeout(checkDeadline, acquireDeadlineMs);
|
|
16977
17155
|
acquireDeadlineTimer.unref?.();
|
|
16978
17156
|
});
|
|
16979
17157
|
result = await Promise.race([
|
|
@@ -17106,16 +17284,16 @@ ${loopReminder}` : truncation.prompt;
|
|
|
17106
17284
|
error: new Error("Failed to create Qwen stream")
|
|
17107
17285
|
};
|
|
17108
17286
|
}
|
|
17109
|
-
|
|
17287
|
+
const isSessionExpired = err instanceof QwenSessionExpiredError || err?.name === "QwenSessionExpiredError" || err instanceof Error && (err.message.toLowerCase().includes("token has expired") || err.message.toLowerCase().includes("session expired") || err.message.toLowerCase().includes("401 n\xE3o autorizado") || err.message.toLowerCase().includes("unauthorized"));
|
|
17288
|
+
if (isSessionExpired) {
|
|
17110
17289
|
console.warn(
|
|
17111
|
-
`\u{1F504} [Chat] Session expired for ${currentAccountEmail} (${currentAccountId}). Attempting re-login...`
|
|
17290
|
+
`\u{1F504} [Chat] Session expired for ${currentAccountEmail} (${currentAccountId}). Attempting silent token refresh/re-login...`
|
|
17112
17291
|
);
|
|
17113
17292
|
const reLoginOk = await attemptRelogin(
|
|
17114
17293
|
currentAccountId,
|
|
17115
17294
|
currentAccountEmail
|
|
17116
17295
|
);
|
|
17117
17296
|
if (reLoginOk) continue;
|
|
17118
|
-
return { success: false, error: err };
|
|
17119
17297
|
}
|
|
17120
17298
|
if (isAccountUnavailableError(err)) {
|
|
17121
17299
|
const quotaMsg = err.message || "Unknown quota error";
|
|
@@ -17255,6 +17433,10 @@ ${loopReminder}` : truncation.prompt;
|
|
|
17255
17433
|
currentAccountId = nextAccount.id;
|
|
17256
17434
|
currentAccountEmail = maskEmail(nextAccount.email);
|
|
17257
17435
|
accountSwitches++;
|
|
17436
|
+
const isRealQuotaError = policy.reason === "quota_or_rate_limit" && (policy.accountCooldownReason === "RateLimited" || policy.accountCooldownReason === "QuotaExceeded");
|
|
17437
|
+
if (isRealQuotaError) {
|
|
17438
|
+
attemptsLeft = Math.max(attemptsLeft, 1);
|
|
17439
|
+
}
|
|
17258
17440
|
if (params.useThreadNative) {
|
|
17259
17441
|
const abandonedChatId = params.existingThread?.chatSessionId;
|
|
17260
17442
|
if (abandonedChatId) {
|
|
@@ -19875,6 +20057,199 @@ var init_token_estimation_metrics = __esm({
|
|
|
19875
20057
|
}
|
|
19876
20058
|
});
|
|
19877
20059
|
|
|
20060
|
+
// src/core/daily-usage.ts
|
|
20061
|
+
var daily_usage_exports = {};
|
|
20062
|
+
__export(daily_usage_exports, {
|
|
20063
|
+
DAILY_ACCOUNT_TOKEN_BASELINE: () => DAILY_ACCOUNT_TOKEN_BASELINE,
|
|
20064
|
+
MODEL_DAILY_OUTPUT_BASELINES: () => MODEL_DAILY_OUTPUT_BASELINES,
|
|
20065
|
+
STANDARD_DAILY_TOKEN_BASELINE: () => STANDARD_DAILY_TOKEN_BASELINE,
|
|
20066
|
+
getAccountDailyUsage: () => getAccountDailyUsage,
|
|
20067
|
+
getAllAccountsDailyUsage: () => getAllAccountsDailyUsage,
|
|
20068
|
+
getModelQuotaWeight: () => getModelQuotaWeight,
|
|
20069
|
+
getPoolDailyUsageSummary: () => getPoolDailyUsageSummary,
|
|
20070
|
+
getUtcDateString: () => getUtcDateString,
|
|
20071
|
+
recordTurnUsage: () => recordTurnUsage
|
|
20072
|
+
});
|
|
20073
|
+
function getModelQuotaWeight(model) {
|
|
20074
|
+
if (!model) return 1;
|
|
20075
|
+
const resolved = mapClientModelToQwen(model).toLowerCase();
|
|
20076
|
+
if (resolved.includes("max")) {
|
|
20077
|
+
return STANDARD_DAILY_TOKEN_BASELINE / MODEL_DAILY_OUTPUT_BASELINES.max;
|
|
20078
|
+
}
|
|
20079
|
+
if (resolved.includes("omni") || resolved.includes("flash")) {
|
|
20080
|
+
return STANDARD_DAILY_TOKEN_BASELINE / MODEL_DAILY_OUTPUT_BASELINES.omni;
|
|
20081
|
+
}
|
|
20082
|
+
return 1;
|
|
20083
|
+
}
|
|
20084
|
+
function getUtcDateString(timestampMs = Date.now()) {
|
|
20085
|
+
return new Date(timestampMs).toISOString().slice(0, 10);
|
|
20086
|
+
}
|
|
20087
|
+
function recordTurnUsage(accountId, usage, modelOrDate, maybeDateUtc) {
|
|
20088
|
+
if (!accountId || accountId === "global") return;
|
|
20089
|
+
let model;
|
|
20090
|
+
let dateUtc;
|
|
20091
|
+
if (modelOrDate && /^\d{4}-\d{2}-\d{2}$/.test(modelOrDate)) {
|
|
20092
|
+
dateUtc = modelOrDate;
|
|
20093
|
+
model = void 0;
|
|
20094
|
+
} else {
|
|
20095
|
+
model = modelOrDate;
|
|
20096
|
+
dateUtc = maybeDateUtc || getUtcDateString();
|
|
20097
|
+
}
|
|
20098
|
+
const prompt = Math.max(0, Number(usage.prompt_tokens) || 0);
|
|
20099
|
+
const completion = Math.max(0, Number(usage.completion_tokens) || 0);
|
|
20100
|
+
const total = Math.max(
|
|
20101
|
+
prompt + completion,
|
|
20102
|
+
Number(usage.total_tokens) || prompt + completion
|
|
20103
|
+
);
|
|
20104
|
+
const weight = getModelQuotaWeight(model);
|
|
20105
|
+
const weighted = completion * weight;
|
|
20106
|
+
try {
|
|
20107
|
+
const db2 = getDatabase();
|
|
20108
|
+
db2.prepare(
|
|
20109
|
+
`
|
|
20110
|
+
INSERT INTO account_daily_usage (
|
|
20111
|
+
account_id, date_utc, prompt_tokens, completion_tokens, weighted_tokens, total_tokens, turns_count, updated_at
|
|
20112
|
+
) VALUES (
|
|
20113
|
+
?, ?, ?, ?, ?, ?, 1, datetime('now')
|
|
20114
|
+
)
|
|
20115
|
+
ON CONFLICT(account_id, date_utc) DO UPDATE SET
|
|
20116
|
+
prompt_tokens = prompt_tokens + excluded.prompt_tokens,
|
|
20117
|
+
completion_tokens = completion_tokens + excluded.completion_tokens,
|
|
20118
|
+
weighted_tokens = weighted_tokens + excluded.weighted_tokens,
|
|
20119
|
+
total_tokens = total_tokens + excluded.total_tokens,
|
|
20120
|
+
turns_count = turns_count + 1,
|
|
20121
|
+
updated_at = datetime('now')
|
|
20122
|
+
`
|
|
20123
|
+
).run(accountId, dateUtc, prompt, completion, weighted, total);
|
|
20124
|
+
} catch (err) {
|
|
20125
|
+
}
|
|
20126
|
+
}
|
|
20127
|
+
function getAccountDailyUsage(accountId, dateUtc = getUtcDateString()) {
|
|
20128
|
+
try {
|
|
20129
|
+
const db2 = getDatabase();
|
|
20130
|
+
const row = db2.prepare(
|
|
20131
|
+
`SELECT prompt_tokens, completion_tokens, weighted_tokens, total_tokens, turns_count
|
|
20132
|
+
FROM account_daily_usage
|
|
20133
|
+
WHERE account_id = ? AND date_utc = ?`
|
|
20134
|
+
).get(accountId, dateUtc);
|
|
20135
|
+
const promptTokens = Number(row?.prompt_tokens) || 0;
|
|
20136
|
+
const completionTokens = Number(row?.completion_tokens) || 0;
|
|
20137
|
+
const weightedTokens = Number(row?.weighted_tokens) || completionTokens;
|
|
20138
|
+
const totalTokens = Number(row?.total_tokens) || 0;
|
|
20139
|
+
const turnsCount = Number(row?.turns_count) || 0;
|
|
20140
|
+
const usagePercent = Math.min(
|
|
20141
|
+
100,
|
|
20142
|
+
Math.round(weightedTokens / STANDARD_DAILY_TOKEN_BASELINE * 100)
|
|
20143
|
+
);
|
|
20144
|
+
return {
|
|
20145
|
+
accountId,
|
|
20146
|
+
dateUtc,
|
|
20147
|
+
promptTokens,
|
|
20148
|
+
completionTokens,
|
|
20149
|
+
weightedTokens,
|
|
20150
|
+
totalTokens,
|
|
20151
|
+
turnsCount,
|
|
20152
|
+
usagePercent
|
|
20153
|
+
};
|
|
20154
|
+
} catch {
|
|
20155
|
+
return {
|
|
20156
|
+
accountId,
|
|
20157
|
+
dateUtc,
|
|
20158
|
+
promptTokens: 0,
|
|
20159
|
+
completionTokens: 0,
|
|
20160
|
+
weightedTokens: 0,
|
|
20161
|
+
totalTokens: 0,
|
|
20162
|
+
turnsCount: 0,
|
|
20163
|
+
usagePercent: 0
|
|
20164
|
+
};
|
|
20165
|
+
}
|
|
20166
|
+
}
|
|
20167
|
+
function getAllAccountsDailyUsage(dateUtc = getUtcDateString()) {
|
|
20168
|
+
const result = /* @__PURE__ */ new Map();
|
|
20169
|
+
try {
|
|
20170
|
+
const db2 = getDatabase();
|
|
20171
|
+
const rows = db2.prepare(
|
|
20172
|
+
`SELECT account_id, prompt_tokens, completion_tokens, weighted_tokens, total_tokens, turns_count
|
|
20173
|
+
FROM account_daily_usage
|
|
20174
|
+
WHERE date_utc = ?`
|
|
20175
|
+
).all(dateUtc);
|
|
20176
|
+
for (const row of rows) {
|
|
20177
|
+
const promptTokens = Number(row.prompt_tokens) || 0;
|
|
20178
|
+
const completionTokens = Number(row.completion_tokens) || 0;
|
|
20179
|
+
const weightedTokens = Number(row.weighted_tokens) || completionTokens;
|
|
20180
|
+
const totalTokens = Number(row.total_tokens) || 0;
|
|
20181
|
+
const turnsCount = Number(row.turns_count) || 0;
|
|
20182
|
+
const usagePercent = Math.min(
|
|
20183
|
+
100,
|
|
20184
|
+
Math.round(weightedTokens / STANDARD_DAILY_TOKEN_BASELINE * 100)
|
|
20185
|
+
);
|
|
20186
|
+
result.set(row.account_id, {
|
|
20187
|
+
accountId: row.account_id,
|
|
20188
|
+
dateUtc,
|
|
20189
|
+
promptTokens,
|
|
20190
|
+
completionTokens,
|
|
20191
|
+
weightedTokens,
|
|
20192
|
+
totalTokens,
|
|
20193
|
+
turnsCount,
|
|
20194
|
+
usagePercent
|
|
20195
|
+
});
|
|
20196
|
+
}
|
|
20197
|
+
} catch {
|
|
20198
|
+
}
|
|
20199
|
+
return result;
|
|
20200
|
+
}
|
|
20201
|
+
function getPoolDailyUsageSummary(targetAccountIds, dateUtc = getUtcDateString()) {
|
|
20202
|
+
const accounts = targetAccountIds && targetAccountIds.length > 0 ? targetAccountIds : loadAccounts().map((a) => a.id);
|
|
20203
|
+
const accountCount = Math.max(1, accounts.length);
|
|
20204
|
+
const poolCapacityTokens = accountCount * STANDARD_DAILY_TOKEN_BASELINE;
|
|
20205
|
+
const usageMap = getAllAccountsDailyUsage(dateUtc);
|
|
20206
|
+
let totalPromptTokens = 0;
|
|
20207
|
+
let totalCompletionTokens = 0;
|
|
20208
|
+
let totalWeightedTokens = 0;
|
|
20209
|
+
let totalTokens = 0;
|
|
20210
|
+
let totalTurns = 0;
|
|
20211
|
+
for (const id of accounts) {
|
|
20212
|
+
const usage = usageMap.get(id);
|
|
20213
|
+
if (usage) {
|
|
20214
|
+
totalPromptTokens += usage.promptTokens;
|
|
20215
|
+
totalCompletionTokens += usage.completionTokens;
|
|
20216
|
+
totalWeightedTokens += usage.weightedTokens;
|
|
20217
|
+
totalTokens += usage.totalTokens;
|
|
20218
|
+
totalTurns += usage.turnsCount;
|
|
20219
|
+
}
|
|
20220
|
+
}
|
|
20221
|
+
const poolUsagePercent = Math.min(
|
|
20222
|
+
100,
|
|
20223
|
+
Math.round(totalWeightedTokens / poolCapacityTokens * 100)
|
|
20224
|
+
);
|
|
20225
|
+
return {
|
|
20226
|
+
dateUtc,
|
|
20227
|
+
totalPromptTokens,
|
|
20228
|
+
totalCompletionTokens,
|
|
20229
|
+
totalWeightedTokens,
|
|
20230
|
+
totalTokens,
|
|
20231
|
+
totalTurns,
|
|
20232
|
+
poolCapacityTokens,
|
|
20233
|
+
poolUsagePercent
|
|
20234
|
+
};
|
|
20235
|
+
}
|
|
20236
|
+
var STANDARD_DAILY_TOKEN_BASELINE, DAILY_ACCOUNT_TOKEN_BASELINE, MODEL_DAILY_OUTPUT_BASELINES;
|
|
20237
|
+
var init_daily_usage = __esm({
|
|
20238
|
+
"src/core/daily-usage.ts"() {
|
|
20239
|
+
"use strict";
|
|
20240
|
+
init_database();
|
|
20241
|
+
init_accounts();
|
|
20242
|
+
init_model_alias();
|
|
20243
|
+
STANDARD_DAILY_TOKEN_BASELINE = 5e5;
|
|
20244
|
+
DAILY_ACCOUNT_TOKEN_BASELINE = STANDARD_DAILY_TOKEN_BASELINE;
|
|
20245
|
+
MODEL_DAILY_OUTPUT_BASELINES = {
|
|
20246
|
+
max: 28e4,
|
|
20247
|
+
plus: 5e5,
|
|
20248
|
+
omni: 62e4
|
|
20249
|
+
};
|
|
20250
|
+
}
|
|
20251
|
+
});
|
|
20252
|
+
|
|
19878
20253
|
// src/routes/chat/streaming.ts
|
|
19879
20254
|
import { stream as honoStream } from "hono/streaming";
|
|
19880
20255
|
function firstString(...values) {
|
|
@@ -20380,6 +20755,9 @@ Please retry your tool call(s) with correct JSON and valid tool names from the a
|
|
|
20380
20755
|
mode: "non-stream",
|
|
20381
20756
|
context: currentTokenEstimationContext
|
|
20382
20757
|
});
|
|
20758
|
+
if (activeAccountId && usage) {
|
|
20759
|
+
recordTurnUsage(activeAccountId, usage, body.model);
|
|
20760
|
+
}
|
|
20383
20761
|
if (pendingParentId) {
|
|
20384
20762
|
rememberParent(pendingParentId);
|
|
20385
20763
|
}
|
|
@@ -20475,6 +20853,8 @@ async function processStreamingResponse(params) {
|
|
|
20475
20853
|
let activeReader = null;
|
|
20476
20854
|
let invalidInputSameAccountRetries = 0;
|
|
20477
20855
|
let chatInProgressSameAccountRetries = 0;
|
|
20856
|
+
const totalConfiguredAccounts = loadAccounts().length;
|
|
20857
|
+
let quotaRotationsLeft = Math.min(Math.max(1, totalConfiguredAccounts - 1), 6);
|
|
20478
20858
|
let streamCompletedOk = false;
|
|
20479
20859
|
let stoppedByToolCap = false;
|
|
20480
20860
|
const runDisconnectTeardown = () => {
|
|
@@ -20832,13 +21212,18 @@ async function processStreamingResponse(params) {
|
|
|
20832
21212
|
reason: "network"
|
|
20833
21213
|
}
|
|
20834
21214
|
) : null;
|
|
20835
|
-
if (!normalizedError || clientDisconnected && !gracePending || c.req.raw.signal.aborted ||
|
|
21215
|
+
if (!normalizedError || clientDisconnected && !gracePending || c.req.raw.signal.aborted || !midStreamRetry || emittedModelOutput) {
|
|
20836
21216
|
return false;
|
|
20837
21217
|
}
|
|
20838
21218
|
const policy = classifyRetryAction(normalizedError, {
|
|
20839
21219
|
requestAborted: c.req.raw.signal.aborted
|
|
20840
21220
|
});
|
|
20841
21221
|
if (!policy.retryable) return false;
|
|
21222
|
+
const isRealQuotaError = policy.reason === "quota_or_rate_limit" && (policy.accountCooldownReason === "RateLimited" || policy.accountCooldownReason === "QuotaExceeded");
|
|
21223
|
+
const hasBudget = isRealQuotaError ? quotaRotationsLeft > 0 : retryContext.retriesLeft > 0;
|
|
21224
|
+
if (!hasBudget) {
|
|
21225
|
+
return false;
|
|
21226
|
+
}
|
|
20842
21227
|
if (logger.isLevelEnabled("info")) {
|
|
20843
21228
|
console.log(
|
|
20844
21229
|
`\u{1F9ED} [Chat] Stream recovery policy | account=${currentAccountId} | reason=${policy.reason} | retryable=${policy.retryable} | switch=${policy.switchAccount} | newChat=${policy.forceNewChat} | fullPrompt=${policy.retryWithFullPrompt} | retryAfter=${policy.retryAfterMs}ms`
|
|
@@ -20847,9 +21232,13 @@ async function processStreamingResponse(params) {
|
|
|
20847
21232
|
if (policy.reason === "corrupted_chat_history") {
|
|
20848
21233
|
invalidateLogicalThreadParent(midStreamRetry.sessionId);
|
|
20849
21234
|
}
|
|
20850
|
-
|
|
21235
|
+
if (isRealQuotaError) {
|
|
21236
|
+
quotaRotationsLeft--;
|
|
21237
|
+
} else {
|
|
21238
|
+
retryContext.retriesLeft--;
|
|
21239
|
+
}
|
|
20851
21240
|
console.warn(
|
|
20852
|
-
`\u{1F504} [Chat] Stream recovery | account=${currentAccountId} | reason=${policy.reason} | error=${normalizedError.message.substring(0, 150)} | retries_left=${retryContext.retriesLeft}`
|
|
21241
|
+
`\u{1F504} [Chat] Stream recovery | account=${currentAccountId} | reason=${policy.reason} | error=${normalizedError.message.substring(0, 150)} | retries_left=${isRealQuotaError ? quotaRotationsLeft : retryContext.retriesLeft}`
|
|
20853
21242
|
);
|
|
20854
21243
|
const retryInvalidInputOnSameAccount = shouldRetryInvalidInputOnSameAccount(
|
|
20855
21244
|
policy.reason,
|
|
@@ -21730,6 +22119,9 @@ Please retry your tool call(s) with correct JSON and valid tool names from the a
|
|
|
21730
22119
|
mode: "stream",
|
|
21731
22120
|
context: currentTokenEstimationContext
|
|
21732
22121
|
});
|
|
22122
|
+
if (activeAccountId && usage) {
|
|
22123
|
+
recordTurnUsage(activeAccountId, usage, body.model);
|
|
22124
|
+
}
|
|
21733
22125
|
} else {
|
|
21734
22126
|
if (isToolcallDebugEnabled()) {
|
|
21735
22127
|
logger.debug(
|
|
@@ -21935,6 +22327,7 @@ var init_streaming = __esm({
|
|
|
21935
22327
|
init_qwen_url();
|
|
21936
22328
|
init_qwen();
|
|
21937
22329
|
init_account();
|
|
22330
|
+
init_accounts();
|
|
21938
22331
|
init_account_manager();
|
|
21939
22332
|
init_account_concurrency();
|
|
21940
22333
|
init_retry_policy();
|
|
@@ -21950,6 +22343,7 @@ var init_streaming = __esm({
|
|
|
21950
22343
|
init_errors2();
|
|
21951
22344
|
init_retry_policy();
|
|
21952
22345
|
init_token_estimation_metrics();
|
|
22346
|
+
init_daily_usage();
|
|
21953
22347
|
init_context_meter();
|
|
21954
22348
|
init_helpers();
|
|
21955
22349
|
MAX_INITIAL_PROTOCOL_BYTES = 64 * 1024;
|
|
@@ -22389,7 +22783,7 @@ async function chatCompletions(c) {
|
|
|
22389
22783
|
updateLogicalThread: parallelEscape ? false : ctx.updateLogicalThread,
|
|
22390
22784
|
allowThreadReuse: ctx.allowThreadReuse,
|
|
22391
22785
|
forceNewChat: parallelEscape,
|
|
22392
|
-
preferredAccountId: void 0,
|
|
22786
|
+
preferredAccountId: c.req.header("x-qwenproxy-account") || void 0,
|
|
22393
22787
|
messageCount: msgCount,
|
|
22394
22788
|
fullMessageCount: parsed.messageCount,
|
|
22395
22789
|
toolsCount: declaredTools.length || void 0,
|
|
@@ -25891,7 +26285,7 @@ function getAppVersion() {
|
|
|
25891
26285
|
}
|
|
25892
26286
|
} catch {
|
|
25893
26287
|
}
|
|
25894
|
-
cachedVersion = "v1.
|
|
26288
|
+
cachedVersion = "v1.6.0";
|
|
25895
26289
|
return cachedVersion;
|
|
25896
26290
|
}
|
|
25897
26291
|
var cachedVersion;
|
|
@@ -26600,7 +26994,8 @@ var init_server = __esm({
|
|
|
26600
26994
|
captchasDetected: Number(metrics.get("captcha.challenges.detected")?.value ?? 0),
|
|
26601
26995
|
captchasSolved: Number(metrics.get("captcha.solves.succeeded")?.value ?? 0),
|
|
26602
26996
|
chatsCleaned: Number(metrics.get("chats.cleaned")?.value ?? 0)
|
|
26603
|
-
}
|
|
26997
|
+
},
|
|
26998
|
+
dailyUsage: (await Promise.resolve().then(() => (init_daily_usage(), daily_usage_exports))).getPoolDailyUsageSummary()
|
|
26604
26999
|
});
|
|
26605
27000
|
});
|
|
26606
27001
|
app5.get("/diagnostics/tokens", async (c) => {
|
|
@@ -27362,12 +27757,17 @@ async function fetchProxyStatus() {
|
|
|
27362
27757
|
}
|
|
27363
27758
|
} catch {
|
|
27364
27759
|
}
|
|
27760
|
+
const dailyUsageMap = getAllAccountsDailyUsage();
|
|
27365
27761
|
const accounts = cachedAccounts.map((acc) => {
|
|
27366
27762
|
const concurrency = concurrencyMap.get(acc.id);
|
|
27763
|
+
const daily = dailyUsageMap.get(acc.id);
|
|
27367
27764
|
return {
|
|
27368
27765
|
...acc,
|
|
27369
27766
|
activeStreams: concurrency?.active ?? 0,
|
|
27370
|
-
streamLimit: concurrency?.limit ?? config.concurrency.maxStreamsPerAccount
|
|
27767
|
+
streamLimit: concurrency?.limit ?? config.concurrency.maxStreamsPerAccount,
|
|
27768
|
+
dailyTokens: daily?.completionTokens ?? 0,
|
|
27769
|
+
dailyUsagePercent: daily?.usagePercent ?? 0,
|
|
27770
|
+
dailyTurns: daily?.turnsCount ?? 0
|
|
27371
27771
|
};
|
|
27372
27772
|
});
|
|
27373
27773
|
let rssMb = 0;
|
|
@@ -27426,6 +27826,7 @@ async function fetchProxyStatus() {
|
|
|
27426
27826
|
cacheHitRatio,
|
|
27427
27827
|
cacheBytesSaved
|
|
27428
27828
|
},
|
|
27829
|
+
dailyUsage: getPoolDailyUsageSummary(cachedAccounts.map((a) => a.id)),
|
|
27429
27830
|
accounts
|
|
27430
27831
|
};
|
|
27431
27832
|
}
|
|
@@ -27566,6 +27967,7 @@ var init_proxy_client = __esm({
|
|
|
27566
27967
|
init_account_concurrency();
|
|
27567
27968
|
init_memory_usage();
|
|
27568
27969
|
init_metrics();
|
|
27970
|
+
init_daily_usage();
|
|
27569
27971
|
init_model_alias();
|
|
27570
27972
|
init_model_alias();
|
|
27571
27973
|
cachedAccounts = [];
|
|
@@ -28296,12 +28698,24 @@ var init_status_view = __esm({
|
|
|
28296
28698
|
const poolPct = accounts.length > 0 ? Math.round(availableCount / accounts.length * 100) : 0;
|
|
28297
28699
|
const poolColor = poolPct >= 70 ? theme.green : poolPct >= 40 ? theme.yellow : theme.red;
|
|
28298
28700
|
const poolBar = renderProgressBar(poolPct, 8, poolColor);
|
|
28701
|
+
const poolDailyUsage = data?.dailyUsage;
|
|
28702
|
+
const poolUsagePct = poolDailyUsage?.poolUsagePercent ?? 0;
|
|
28703
|
+
const poolUsageColor = poolUsagePct >= 80 ? theme.red : poolUsagePct >= 40 ? theme.yellow : theme.cyan;
|
|
28704
|
+
const poolUsageBar = renderProgressBar(poolUsagePct, 8, poolUsageColor);
|
|
28299
28705
|
const innerRightW = Math.max(30, rightW - 2);
|
|
28300
|
-
const
|
|
28706
|
+
const showUsageCol = innerRightW >= 52;
|
|
28707
|
+
const emailWidth = showUsageCol ? Math.max(14, Math.min(18, innerRightW - 33)) : Math.max(16, Math.min(20, innerRightW - 27));
|
|
28301
28708
|
const rightContent = [
|
|
28302
28709
|
` ${theme.bold("Disponibilidade:")} [${poolBar}] ${poolColor(`${availableCount}/${accounts.length} (${poolPct}%)`)}`,
|
|
28710
|
+
...poolDailyUsage ? [
|
|
28711
|
+
` ${theme.bold("Cota Pool Hoje:")} [${poolUsageBar}] ${poolUsageColor(
|
|
28712
|
+
`${poolUsagePct}%`
|
|
28713
|
+
)} ${theme.dim(
|
|
28714
|
+
`(~${Math.round(poolDailyUsage.totalTokens / 1e3)}k tok)`
|
|
28715
|
+
)}`
|
|
28716
|
+
] : [],
|
|
28303
28717
|
` ${theme.dim("\u2500".repeat(Math.max(32, innerRightW - 2)))}`,
|
|
28304
|
-
` ${theme.dim(`# ${pad("Conta", emailWidth)} Carga Status`)}`,
|
|
28718
|
+
` ${theme.dim(`# ${pad("Conta", emailWidth)} Carga ${showUsageCol ? "Uso% " : ""}Status`)}`,
|
|
28305
28719
|
` ${theme.dim("\u2500".repeat(Math.max(32, innerRightW - 2)))}`
|
|
28306
28720
|
];
|
|
28307
28721
|
if (accounts.length === 0) {
|
|
@@ -28313,7 +28727,17 @@ var init_status_view = __esm({
|
|
|
28313
28727
|
const name = pad(truncate(acc.emailOrName, emailWidth - 1), emailWidth);
|
|
28314
28728
|
const active = acc.activeStreams || 0;
|
|
28315
28729
|
const limit = acc.streamLimit || 1;
|
|
28316
|
-
const
|
|
28730
|
+
const usagePct = acc.dailyUsagePercent ?? 0;
|
|
28731
|
+
let loadBadge;
|
|
28732
|
+
if (active > 0) {
|
|
28733
|
+
loadBadge = theme.yellow(`[${active}/${limit}]`);
|
|
28734
|
+
} else if (!showUsageCol && usagePct > 0) {
|
|
28735
|
+
const pctColor = usagePct >= 80 ? theme.red : usagePct >= 40 ? theme.yellow : theme.cyan;
|
|
28736
|
+
loadBadge = pctColor(pad(`[${usagePct}%]`, 5));
|
|
28737
|
+
} else {
|
|
28738
|
+
loadBadge = theme.dim(`[0/${limit}]`);
|
|
28739
|
+
}
|
|
28740
|
+
const usageCol = showUsageCol ? (usagePct > 0 ? usagePct >= 80 ? theme.red(pad(`${usagePct}%`, 5)) : usagePct >= 40 ? theme.yellow(pad(`${usagePct}%`, 5)) : theme.cyan(pad(`${usagePct}%`, 5)) : theme.dim(pad("0%", 5))) + " " : "";
|
|
28317
28741
|
let status = theme.green(`${glyphs.bullet} Pronto`);
|
|
28318
28742
|
if (active > 0 && !acc.onCooldown && acc.headersReady) {
|
|
28319
28743
|
status = theme.yellow(`\u25CF Gerando `);
|
|
@@ -28330,7 +28754,7 @@ var init_status_view = __esm({
|
|
|
28330
28754
|
} else if (!acc.headersReady) {
|
|
28331
28755
|
status = acc.isInitialized ? theme.yellow(`\u25D0 Aquecendo...`) : theme.muted(`\u25CB Standby`);
|
|
28332
28756
|
}
|
|
28333
|
-
rightContent.push(` ${num}${name} ${loadBadge} ${status}`);
|
|
28757
|
+
rightContent.push(` ${num}${name} ${loadBadge} ${usageCol}${status}`);
|
|
28334
28758
|
});
|
|
28335
28759
|
}
|
|
28336
28760
|
const summaryParts = [];
|
|
@@ -32712,6 +33136,7 @@ var init_accounts_view = __esm({
|
|
|
32712
33136
|
init_accounts();
|
|
32713
33137
|
init_server_manager();
|
|
32714
33138
|
init_config();
|
|
33139
|
+
init_status_view();
|
|
32715
33140
|
AccountsView = class {
|
|
32716
33141
|
id = "accounts";
|
|
32717
33142
|
title = "Contas";
|
|
@@ -33477,9 +33902,11 @@ var init_accounts_view = __esm({
|
|
|
33477
33902
|
if (this.selectedIndex >= accounts.length && accounts.length > 0) {
|
|
33478
33903
|
this.selectedIndex = accounts.length - 1;
|
|
33479
33904
|
}
|
|
33905
|
+
const showUsageCol = leftW >= 48;
|
|
33906
|
+
const headerUsage = showUsageCol ? "Uso% " : "";
|
|
33480
33907
|
const leftContent = [
|
|
33481
33908
|
"",
|
|
33482
|
-
` ${theme.dim(
|
|
33909
|
+
` ${theme.dim(`# Conta ${headerUsage}Status`)}`,
|
|
33483
33910
|
` ${theme.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}`
|
|
33484
33911
|
];
|
|
33485
33912
|
if (accounts.length === 0) {
|
|
@@ -33524,7 +33951,9 @@ var init_accounts_view = __esm({
|
|
|
33524
33951
|
} else if (!acc.headersReady) {
|
|
33525
33952
|
status = acc.isInitialized ? theme.yellow(`\u25D0 Aquecendo...`) : theme.muted(`\u25CB Standby `);
|
|
33526
33953
|
}
|
|
33527
|
-
const
|
|
33954
|
+
const usagePct = acc.dailyUsagePercent ?? 0;
|
|
33955
|
+
const usagePctStr = showUsageCol ? (usagePct > 0 ? usagePct >= 80 ? theme.red(pad(`${usagePct}%`, 5)) : usagePct >= 40 ? theme.yellow(pad(`${usagePct}%`, 5)) : theme.cyan(pad(`${usagePct}%`, 5)) : theme.dim(pad("0%", 5))) + " " : "";
|
|
33956
|
+
const line = `${pointer}${num}${name}${usagePctStr}${status}`;
|
|
33528
33957
|
if (isHovered) {
|
|
33529
33958
|
leftContent.push(theme.bgHover(line));
|
|
33530
33959
|
} else if (isFocused) {
|
|
@@ -33565,6 +33994,12 @@ var init_accounts_view = __esm({
|
|
|
33565
33994
|
rightContent.push(` ${theme.bold("Conta:")} ${theme.cyan(email)}`);
|
|
33566
33995
|
rightContent.push(` ${theme.bold("Sistema ID:")} ${theme.muted(selected.id.slice(0, 14))}`);
|
|
33567
33996
|
rightContent.push(` ${theme.bold("N\xEDvel:")} ${selected.priority}`);
|
|
33997
|
+
const usagePct = selected.dailyUsagePercent ?? 0;
|
|
33998
|
+
const usageTokens = selected.dailyTokens ?? 0;
|
|
33999
|
+
const usageTurns = selected.dailyTurns ?? 0;
|
|
34000
|
+
const usageColor = usagePct >= 80 ? theme.red : usagePct >= 40 ? theme.yellow : theme.cyan;
|
|
34001
|
+
const usageBar = renderProgressBar(usagePct, 6, usageColor);
|
|
34002
|
+
rightContent.push(` ${theme.bold("Cota Hoje:")} [${usageBar}] ${usageColor(`${usagePct}%`)} ${theme.dim(`(~${Math.round(usageTokens / 1e3)}k / ${usageTurns} reqs)`)}`);
|
|
33568
34003
|
const cdStatus = selected.onCooldown ? theme.yellow(`[!] Cooldown ${Math.round(selected.remainingCooldownMs / 6e4)}m`) : theme.green(`${glyphs.check} Dispon\xEDvel`);
|
|
33569
34004
|
rightContent.push(` ${theme.bold("Estado:")} ${cdStatus}`);
|
|
33570
34005
|
const hStatus = selected.headersReady ? theme.green(`${glyphs.check} Capturados`) : selected.isInitialized ? theme.yellow(`\u25D0 Aquecendo...`) : theme.muted(`${glyphs.circle} Standby (Sob Demanda)`);
|