qwenproxy-cli 1.5.3 → 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/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
- ?, ?, ?, ?, datetime('now')
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 { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
6714
- const creds = getAccountCredentials2(accountId);
6715
- if (creds && creds.email && creds.password) {
6716
- console.warn(
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
- armOverallDeadline();
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(2e3);
6759
+ await sleep2(1500);
6727
6760
  }
6728
- if (!ok || !await isPageLoggedIn(page, 6e3)) {
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 after session expiry did not succeed`
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 executeReauth();
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) {
@@ -13411,7 +13528,7 @@ async function createQwenStreamInternal(prompt, enableThinking, modelId, forcedP
13411
13528
  clearTimeout(timeoutId);
13412
13529
  }
13413
13530
  }
13414
- 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;
13415
13532
  var init_qwen = __esm({
13416
13533
  "src/services/qwen.ts"() {
13417
13534
  "use strict";
@@ -13498,6 +13615,7 @@ var init_qwen = __esm({
13498
13615
  }
13499
13616
  };
13500
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();
13501
13619
  DISABLE_TOOLS_MAX_RETRIES = 3;
13502
13620
  DISABLE_TOOLS_BACKOFF_MS = 2e3;
13503
13621
  UPSTREAM_RESPONSE_PREVIEW_BYTES = 8 * 1024;
@@ -16430,8 +16548,9 @@ function resolveInitialAccount(preferredAccountId, excludeAccountIds) {
16430
16548
  syncCooldownsFromDb(configuredAccounts);
16431
16549
  const excluded = new Set(excludeAccountIds ?? []);
16432
16550
  if (typeof preferredAccountId === "string" && preferredAccountId) {
16551
+ const needle = preferredAccountId.toLowerCase();
16433
16552
  const preferred = configuredAccounts.find(
16434
- (candidate) => candidate.id === preferredAccountId
16553
+ (candidate) => candidate.id === preferredAccountId || candidate.id.toLowerCase().startsWith(needle) || candidate.email.toLowerCase() === needle || candidate.email.toLowerCase().includes(needle)
16435
16554
  );
16436
16555
  if (preferred && !getAccountCooldownInfo(preferred.id)) {
16437
16556
  return { account: preferred, configuredAccounts };
@@ -16786,7 +16905,12 @@ function shouldWaitQueueForever(isThreadOwnerWaiting, heldByOtherSession, hasFre
16786
16905
  }
16787
16906
  async function tryCreateStreamWithRetry(params, accountId, accountEmail) {
16788
16907
  const maxAttempts = Math.max(1, config.retry.maxAttempts);
16789
- const maxAccountSwitches = Math.max(0, config.retry.maxAccountSwitches);
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
+ );
16790
16914
  let attemptsLeft = maxAttempts;
16791
16915
  let retryDelay = config.retry.baseDelayMs;
16792
16916
  let attempt = 0;
@@ -16798,7 +16922,6 @@ async function tryCreateStreamWithRetry(params, accountId, accountEmail) {
16798
16922
  let chatInProgressOriginAccountEmail = null;
16799
16923
  let lastAttemptError = null;
16800
16924
  let invalidInputSameAccountRetried = false;
16801
- const accounts = loadAccounts();
16802
16925
  const isSingleAccount = accounts.length <= 1;
16803
16926
  let currentAccountId = accountId;
16804
16927
  let currentAccountEmail = accountEmail;
@@ -17011,14 +17134,24 @@ ${loopReminder}` : truncation.prompt;
17011
17134
  const acquireDeadlineMs = config.concurrency.acquireDeadlineMs;
17012
17135
  let acquireDeadlineTimer;
17013
17136
  const acquireDeadline = new Promise((_, reject) => {
17014
- acquireDeadlineTimer = setTimeout(() => {
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
+ }
17015
17147
  acquireAbort.abort();
17016
17148
  const err2 = new Error(
17017
17149
  `Acquire deadline (${acquireDeadlineMs}ms) exceeded creating stream on ${currentAccountEmail}`
17018
17150
  );
17019
17151
  err2.code = "acquire_deadline";
17020
17152
  reject(err2);
17021
- }, acquireDeadlineMs);
17153
+ };
17154
+ acquireDeadlineTimer = setTimeout(checkDeadline, acquireDeadlineMs);
17022
17155
  acquireDeadlineTimer.unref?.();
17023
17156
  });
17024
17157
  result = await Promise.race([
@@ -17300,6 +17433,10 @@ ${loopReminder}` : truncation.prompt;
17300
17433
  currentAccountId = nextAccount.id;
17301
17434
  currentAccountEmail = maskEmail(nextAccount.email);
17302
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
+ }
17303
17440
  if (params.useThreadNative) {
17304
17441
  const abandonedChatId = params.existingThread?.chatSessionId;
17305
17442
  if (abandonedChatId) {
@@ -19920,6 +20057,199 @@ var init_token_estimation_metrics = __esm({
19920
20057
  }
19921
20058
  });
19922
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
+
19923
20253
  // src/routes/chat/streaming.ts
19924
20254
  import { stream as honoStream } from "hono/streaming";
19925
20255
  function firstString(...values) {
@@ -20425,6 +20755,9 @@ Please retry your tool call(s) with correct JSON and valid tool names from the a
20425
20755
  mode: "non-stream",
20426
20756
  context: currentTokenEstimationContext
20427
20757
  });
20758
+ if (activeAccountId && usage) {
20759
+ recordTurnUsage(activeAccountId, usage, body.model);
20760
+ }
20428
20761
  if (pendingParentId) {
20429
20762
  rememberParent(pendingParentId);
20430
20763
  }
@@ -20520,6 +20853,8 @@ async function processStreamingResponse(params) {
20520
20853
  let activeReader = null;
20521
20854
  let invalidInputSameAccountRetries = 0;
20522
20855
  let chatInProgressSameAccountRetries = 0;
20856
+ const totalConfiguredAccounts = loadAccounts().length;
20857
+ let quotaRotationsLeft = Math.min(Math.max(1, totalConfiguredAccounts - 1), 6);
20523
20858
  let streamCompletedOk = false;
20524
20859
  let stoppedByToolCap = false;
20525
20860
  const runDisconnectTeardown = () => {
@@ -20877,13 +21212,18 @@ async function processStreamingResponse(params) {
20877
21212
  reason: "network"
20878
21213
  }
20879
21214
  ) : null;
20880
- if (!normalizedError || clientDisconnected && !gracePending || c.req.raw.signal.aborted || retryContext.retriesLeft <= 0 || !midStreamRetry || emittedModelOutput) {
21215
+ if (!normalizedError || clientDisconnected && !gracePending || c.req.raw.signal.aborted || !midStreamRetry || emittedModelOutput) {
20881
21216
  return false;
20882
21217
  }
20883
21218
  const policy = classifyRetryAction(normalizedError, {
20884
21219
  requestAborted: c.req.raw.signal.aborted
20885
21220
  });
20886
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
+ }
20887
21227
  if (logger.isLevelEnabled("info")) {
20888
21228
  console.log(
20889
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`
@@ -20892,9 +21232,13 @@ async function processStreamingResponse(params) {
20892
21232
  if (policy.reason === "corrupted_chat_history") {
20893
21233
  invalidateLogicalThreadParent(midStreamRetry.sessionId);
20894
21234
  }
20895
- retryContext.retriesLeft--;
21235
+ if (isRealQuotaError) {
21236
+ quotaRotationsLeft--;
21237
+ } else {
21238
+ retryContext.retriesLeft--;
21239
+ }
20896
21240
  console.warn(
20897
- `\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}`
20898
21242
  );
20899
21243
  const retryInvalidInputOnSameAccount = shouldRetryInvalidInputOnSameAccount(
20900
21244
  policy.reason,
@@ -21775,6 +22119,9 @@ Please retry your tool call(s) with correct JSON and valid tool names from the a
21775
22119
  mode: "stream",
21776
22120
  context: currentTokenEstimationContext
21777
22121
  });
22122
+ if (activeAccountId && usage) {
22123
+ recordTurnUsage(activeAccountId, usage, body.model);
22124
+ }
21778
22125
  } else {
21779
22126
  if (isToolcallDebugEnabled()) {
21780
22127
  logger.debug(
@@ -21980,6 +22327,7 @@ var init_streaming = __esm({
21980
22327
  init_qwen_url();
21981
22328
  init_qwen();
21982
22329
  init_account();
22330
+ init_accounts();
21983
22331
  init_account_manager();
21984
22332
  init_account_concurrency();
21985
22333
  init_retry_policy();
@@ -21995,6 +22343,7 @@ var init_streaming = __esm({
21995
22343
  init_errors2();
21996
22344
  init_retry_policy();
21997
22345
  init_token_estimation_metrics();
22346
+ init_daily_usage();
21998
22347
  init_context_meter();
21999
22348
  init_helpers();
22000
22349
  MAX_INITIAL_PROTOCOL_BYTES = 64 * 1024;
@@ -22434,7 +22783,7 @@ async function chatCompletions(c) {
22434
22783
  updateLogicalThread: parallelEscape ? false : ctx.updateLogicalThread,
22435
22784
  allowThreadReuse: ctx.allowThreadReuse,
22436
22785
  forceNewChat: parallelEscape,
22437
- preferredAccountId: void 0,
22786
+ preferredAccountId: c.req.header("x-qwenproxy-account") || void 0,
22438
22787
  messageCount: msgCount,
22439
22788
  fullMessageCount: parsed.messageCount,
22440
22789
  toolsCount: declaredTools.length || void 0,
@@ -25936,7 +26285,7 @@ function getAppVersion() {
25936
26285
  }
25937
26286
  } catch {
25938
26287
  }
25939
- cachedVersion = "v1.5.3";
26288
+ cachedVersion = "v1.6.0";
25940
26289
  return cachedVersion;
25941
26290
  }
25942
26291
  var cachedVersion;
@@ -26645,7 +26994,8 @@ var init_server = __esm({
26645
26994
  captchasDetected: Number(metrics.get("captcha.challenges.detected")?.value ?? 0),
26646
26995
  captchasSolved: Number(metrics.get("captcha.solves.succeeded")?.value ?? 0),
26647
26996
  chatsCleaned: Number(metrics.get("chats.cleaned")?.value ?? 0)
26648
- }
26997
+ },
26998
+ dailyUsage: (await Promise.resolve().then(() => (init_daily_usage(), daily_usage_exports))).getPoolDailyUsageSummary()
26649
26999
  });
26650
27000
  });
26651
27001
  app5.get("/diagnostics/tokens", async (c) => {
@@ -27407,12 +27757,17 @@ async function fetchProxyStatus() {
27407
27757
  }
27408
27758
  } catch {
27409
27759
  }
27760
+ const dailyUsageMap = getAllAccountsDailyUsage();
27410
27761
  const accounts = cachedAccounts.map((acc) => {
27411
27762
  const concurrency = concurrencyMap.get(acc.id);
27763
+ const daily = dailyUsageMap.get(acc.id);
27412
27764
  return {
27413
27765
  ...acc,
27414
27766
  activeStreams: concurrency?.active ?? 0,
27415
- 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
27416
27771
  };
27417
27772
  });
27418
27773
  let rssMb = 0;
@@ -27471,6 +27826,7 @@ async function fetchProxyStatus() {
27471
27826
  cacheHitRatio,
27472
27827
  cacheBytesSaved
27473
27828
  },
27829
+ dailyUsage: getPoolDailyUsageSummary(cachedAccounts.map((a) => a.id)),
27474
27830
  accounts
27475
27831
  };
27476
27832
  }
@@ -27611,6 +27967,7 @@ var init_proxy_client = __esm({
27611
27967
  init_account_concurrency();
27612
27968
  init_memory_usage();
27613
27969
  init_metrics();
27970
+ init_daily_usage();
27614
27971
  init_model_alias();
27615
27972
  init_model_alias();
27616
27973
  cachedAccounts = [];
@@ -28341,12 +28698,24 @@ var init_status_view = __esm({
28341
28698
  const poolPct = accounts.length > 0 ? Math.round(availableCount / accounts.length * 100) : 0;
28342
28699
  const poolColor = poolPct >= 70 ? theme.green : poolPct >= 40 ? theme.yellow : theme.red;
28343
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);
28344
28705
  const innerRightW = Math.max(30, rightW - 2);
28345
- const emailWidth = Math.max(16, Math.min(20, innerRightW - 27));
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));
28346
28708
  const rightContent = [
28347
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
+ ] : [],
28348
28717
  ` ${theme.dim("\u2500".repeat(Math.max(32, innerRightW - 2)))}`,
28349
- ` ${theme.dim(`# ${pad("Conta", emailWidth)} Carga Status`)}`,
28718
+ ` ${theme.dim(`# ${pad("Conta", emailWidth)} Carga ${showUsageCol ? "Uso% " : ""}Status`)}`,
28350
28719
  ` ${theme.dim("\u2500".repeat(Math.max(32, innerRightW - 2)))}`
28351
28720
  ];
28352
28721
  if (accounts.length === 0) {
@@ -28358,7 +28727,17 @@ var init_status_view = __esm({
28358
28727
  const name = pad(truncate(acc.emailOrName, emailWidth - 1), emailWidth);
28359
28728
  const active = acc.activeStreams || 0;
28360
28729
  const limit = acc.streamLimit || 1;
28361
- const loadBadge = active > 0 ? theme.yellow(`[${active}/${limit}]`) : theme.dim(`[0/${limit}]`);
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))) + " " : "";
28362
28741
  let status = theme.green(`${glyphs.bullet} Pronto`);
28363
28742
  if (active > 0 && !acc.onCooldown && acc.headersReady) {
28364
28743
  status = theme.yellow(`\u25CF Gerando `);
@@ -28375,7 +28754,7 @@ var init_status_view = __esm({
28375
28754
  } else if (!acc.headersReady) {
28376
28755
  status = acc.isInitialized ? theme.yellow(`\u25D0 Aquecendo...`) : theme.muted(`\u25CB Standby`);
28377
28756
  }
28378
- rightContent.push(` ${num}${name} ${loadBadge} ${status}`);
28757
+ rightContent.push(` ${num}${name} ${loadBadge} ${usageCol}${status}`);
28379
28758
  });
28380
28759
  }
28381
28760
  const summaryParts = [];
@@ -32757,6 +33136,7 @@ var init_accounts_view = __esm({
32757
33136
  init_accounts();
32758
33137
  init_server_manager();
32759
33138
  init_config();
33139
+ init_status_view();
32760
33140
  AccountsView = class {
32761
33141
  id = "accounts";
32762
33142
  title = "Contas";
@@ -33522,9 +33902,11 @@ var init_accounts_view = __esm({
33522
33902
  if (this.selectedIndex >= accounts.length && accounts.length > 0) {
33523
33903
  this.selectedIndex = accounts.length - 1;
33524
33904
  }
33905
+ const showUsageCol = leftW >= 48;
33906
+ const headerUsage = showUsageCol ? "Uso% " : "";
33525
33907
  const leftContent = [
33526
33908
  "",
33527
- ` ${theme.dim("# Conta Status")}`,
33909
+ ` ${theme.dim(`# Conta ${headerUsage}Status`)}`,
33528
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")}`
33529
33911
  ];
33530
33912
  if (accounts.length === 0) {
@@ -33569,7 +33951,9 @@ var init_accounts_view = __esm({
33569
33951
  } else if (!acc.headersReady) {
33570
33952
  status = acc.isInitialized ? theme.yellow(`\u25D0 Aquecendo...`) : theme.muted(`\u25CB Standby `);
33571
33953
  }
33572
- const line = `${pointer}${num}${name}${status}`;
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}`;
33573
33957
  if (isHovered) {
33574
33958
  leftContent.push(theme.bgHover(line));
33575
33959
  } else if (isFocused) {
@@ -33610,6 +33994,12 @@ var init_accounts_view = __esm({
33610
33994
  rightContent.push(` ${theme.bold("Conta:")} ${theme.cyan(email)}`);
33611
33995
  rightContent.push(` ${theme.bold("Sistema ID:")} ${theme.muted(selected.id.slice(0, 14))}`);
33612
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)`)}`);
33613
34003
  const cdStatus = selected.onCooldown ? theme.yellow(`[!] Cooldown ${Math.round(selected.remainingCooldownMs / 6e4)}m`) : theme.green(`${glyphs.check} Dispon\xEDvel`);
33614
34004
  rightContent.push(` ${theme.bold("Estado:")} ${cdStatus}`);
33615
34005
  const hStatus = selected.headersReady ? theme.green(`${glyphs.check} Capturados`) : selected.isInitialized ? theme.yellow(`\u25D0 Aquecendo...`) : theme.muted(`${glyphs.circle} Standby (Sob Demanda)`);