qwenproxy-cli 1.6.3 → 1.6.5

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
@@ -2816,6 +2816,7 @@ var init_mutex = __esm({
2816
2816
  locked = false;
2817
2817
  lockedAt = 0;
2818
2818
  lockedByKey = "";
2819
+ epoch = 0;
2819
2820
  async acquire(timeoutMs = 3e5, key = "") {
2820
2821
  const holdLimitMs = this.maxHoldMs;
2821
2822
  if (this.locked && Date.now() - this.lockedAt > holdLimitMs) {
@@ -2826,6 +2827,7 @@ var init_mutex = __esm({
2826
2827
  this.locked = false;
2827
2828
  this.lockedAt = 0;
2828
2829
  this.lockedByKey = "";
2830
+ this.epoch++;
2829
2831
  }
2830
2832
  if (!this.locked) {
2831
2833
  this.locked = true;
@@ -2864,10 +2866,12 @@ var init_mutex = __esm({
2864
2866
  }
2865
2867
  }
2866
2868
  createRelease() {
2869
+ const epoch = this.epoch;
2867
2870
  let released = false;
2868
2871
  return () => {
2869
2872
  if (released) return;
2870
2873
  released = true;
2874
+ if (epoch !== this.epoch) return;
2871
2875
  this.release();
2872
2876
  };
2873
2877
  }
@@ -4016,6 +4020,49 @@ var init_waf_isolation = __esm({
4016
4020
  }
4017
4021
  });
4018
4022
 
4023
+ // src/utils/timezone.ts
4024
+ function formatAsciiTimezone(date = /* @__PURE__ */ new Date()) {
4025
+ const dayName = DAYS[date.getDay()];
4026
+ const monthName = MONTHS[date.getMonth()];
4027
+ const day = String(date.getDate()).padStart(2, "0");
4028
+ const year = date.getFullYear();
4029
+ const hours = String(date.getHours()).padStart(2, "0");
4030
+ const minutes = String(date.getMinutes()).padStart(2, "0");
4031
+ const seconds = String(date.getSeconds()).padStart(2, "0");
4032
+ const offsetMinutes = -date.getTimezoneOffset();
4033
+ const sign = offsetMinutes >= 0 ? "+" : "-";
4034
+ const absOffset = Math.abs(offsetMinutes);
4035
+ const offsetHours = String(Math.floor(absOffset / 60)).padStart(2, "0");
4036
+ const offsetMins = String(absOffset % 60).padStart(2, "0");
4037
+ const tzOffset = `GMT${sign}${offsetHours}${offsetMins}`;
4038
+ return `${dayName} ${monthName} ${day} ${year} ${hours}:${minutes}:${seconds} ${tzOffset}`;
4039
+ }
4040
+ function sanitizeHeaderForFetch(val) {
4041
+ if (typeof val !== "string") return "";
4042
+ return val.replace(/[^\x00-\xFF]/g, "");
4043
+ }
4044
+ var DAYS, MONTHS;
4045
+ var init_timezone = __esm({
4046
+ "src/utils/timezone.ts"() {
4047
+ "use strict";
4048
+ DAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
4049
+ MONTHS = [
4050
+ "Jan",
4051
+ "Feb",
4052
+ "Mar",
4053
+ "Apr",
4054
+ "May",
4055
+ "Jun",
4056
+ "Jul",
4057
+ "Aug",
4058
+ "Sep",
4059
+ "Oct",
4060
+ "Nov",
4061
+ "Dec"
4062
+ ];
4063
+ }
4064
+ });
4065
+
4019
4066
  // src/services/qwen-headers.ts
4020
4067
  import { v4 as uuidv4 } from "uuid";
4021
4068
  function getSystemAcceptLanguage(customLocale) {
@@ -4059,8 +4106,8 @@ function buildQwenRequestHeaders(opts) {
4059
4106
  "bx-v": opts.bxV || "2.5.37",
4060
4107
  source: "web",
4061
4108
  version: opts.version || getQwenWebVersion(),
4062
- timezone: opts.extra?.timezone || opts.extra?.Timezone || (/* @__PURE__ */ new Date()).toString().split(" (")[0],
4063
- Timezone: opts.extra?.Timezone || opts.extra?.timezone || (/* @__PURE__ */ new Date()).toString().split(" (")[0],
4109
+ timezone: opts.extra?.timezone || opts.extra?.Timezone || formatAsciiTimezone(),
4110
+ Timezone: opts.extra?.Timezone || opts.extra?.timezone || formatAsciiTimezone(),
4064
4111
  // Use the real browser client-hints when captured (anti-hardcoded); fall
4065
4112
  // back to the static fingerprint otherwise.
4066
4113
  "sec-ch-ua": opts.secChUa || `"Google Chrome";v="${getChromeMajor()}", "Chromium";v="${getChromeMajor()}", "Not.A/Brand";v="99"`,
@@ -4087,6 +4134,7 @@ var init_qwen_headers = __esm({
4087
4134
  init_qwen_url();
4088
4135
  init_config();
4089
4136
  init_fingerprint();
4137
+ init_timezone();
4090
4138
  dynamicWebVersion = null;
4091
4139
  QWEN_WEB_VERSION = config.qwen.webVersion;
4092
4140
  DEFAULT_QWEN_USER_AGENT = getDefaultQwenUserAgent(151);
@@ -4323,6 +4371,7 @@ __export(playwright_exports, {
4323
4371
  closeIdlePlaywrightAccounts: () => closeIdlePlaywrightAccounts,
4324
4372
  closePlaywrightForAccount: () => closePlaywrightForAccount,
4325
4373
  evictIdlePlaywrightContextsToLimit: () => evictIdlePlaywrightContextsToLimit,
4374
+ getAccountMutex: () => getAccountMutex,
4326
4375
  getActivePlaywrightAccountIds: () => getActivePlaywrightAccountIds,
4327
4376
  getBasicHeaders: () => getBasicHeaders,
4328
4377
  getCookies: () => getCookies,
@@ -4472,9 +4521,10 @@ function getAccountMutex(accountId) {
4472
4521
  async function recoverStuckAccountMutex(accountId, mutex, key) {
4473
4522
  if (accountMutexes.get(accountId) !== mutex) return;
4474
4523
  const lockState = mutex.state();
4475
- if (lockState.heldForMs < PLAYWRIGHT_MUTEX_WAIT_MS || lockState.heldBy.startsWith("init:") || isAccountServingStream(accountId)) {
4524
+ const minLockTimeout = Math.max(PLAYWRIGHT_MUTEX_WAIT_MS, config.timeouts.headers);
4525
+ if (lockState.heldForMs < minLockTimeout || lockState.heldBy.startsWith("init:") || lockState.heldBy.startsWith("headers:") || lockState.heldBy.startsWith("refresh:") || lockState.heldBy.startsWith("profile-reset:") || isAccountServingStream(accountId)) {
4476
4526
  logger.warn(
4477
- `[Playwright] Skipping destructive mutex recovery | account=${accountId} | heldBy=${lockState.heldBy} | heldFor=${lockState.heldForMs}ms | limit=${PLAYWRIGHT_MUTEX_WAIT_MS}ms | servingStream=${isAccountServingStream(accountId)}`
4527
+ `[Playwright] Skipping destructive mutex recovery | account=${accountId} | heldBy=${lockState.heldBy} | heldFor=${lockState.heldForMs}ms | limit=${minLockTimeout}ms | servingStream=${isAccountServingStream(accountId)}`
4478
4528
  );
4479
4529
  return;
4480
4530
  }
@@ -5276,7 +5326,8 @@ async function getBasicHeaders(accountId) {
5276
5326
  }
5277
5327
  const release = await acquireAccountMutex(
5278
5328
  accountId,
5279
- `headers:${accountId.substring(0, 12)}`
5329
+ `headers:${accountId.substring(0, 12)}`,
5330
+ Math.max(PLAYWRIGHT_MUTEX_WAIT_MS, config.timeouts.headers + 3e4)
5280
5331
  );
5281
5332
  try {
5282
5333
  touchAccountActivity(accountId);
@@ -5369,8 +5420,10 @@ async function getBasicHeaders(accountId) {
5369
5420
  return { cookie: cookie2, userAgent, bxV: bxV2, bxUa: bxUa2, bxUmidtoken: bxUmidtoken2, ...clientHints };
5370
5421
  }
5371
5422
  }
5423
+ let refreshedInThisCall = false;
5372
5424
  if (headersAge > HEADER_CACHE_TTL && !cache3.refreshInProgress) {
5373
5425
  try {
5426
+ refreshedInThisCall = true;
5374
5427
  await refreshHeadersInternal(accountId);
5375
5428
  } catch (error) {
5376
5429
  if (!hadUsableHeaders) throw error;
@@ -5439,7 +5492,7 @@ async function getBasicHeaders(accountId) {
5439
5492
  } catch {
5440
5493
  }
5441
5494
  }
5442
- if (!hasRequiredQwenHeaders(cache3.headers)) {
5495
+ if (!hasRequiredQwenHeaders(cache3.headers) && !refreshedInThisCall) {
5443
5496
  console.log(
5444
5497
  `\u{1F504} [Playwright] Missing required anti-bot headers for ${accountId}, triggering header interception...`
5445
5498
  );
@@ -5718,6 +5771,22 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
5718
5771
  }
5719
5772
  }
5720
5773
  }
5774
+ const currentCache = getHeaderCache(account.id);
5775
+ const currentCookie = currentCache?.headers?.cookie || "";
5776
+ if (currentCookie && isTokenExpiringSoon(currentCookie, 5)) {
5777
+ try {
5778
+ const refreshRes = await refreshAccountToken(account.id);
5779
+ if (refreshRes.success) {
5780
+ console.log(
5781
+ `\u26A1 [Playwright] Silently refreshed expired token restored from DB for ${maskEmail(account.email)}`
5782
+ );
5783
+ }
5784
+ } catch (err) {
5785
+ console.warn(
5786
+ `\u26A0\uFE0F [Playwright] Failed to proactively refresh expired token from DB for ${maskEmail(account.email)}: ${err?.message || err}`
5787
+ );
5788
+ }
5789
+ }
5721
5790
  return;
5722
5791
  }
5723
5792
  const cookies = await acctContext.cookies();
@@ -6005,7 +6074,7 @@ async function startEmailOtpLogin(email, options = {}) {
6005
6074
  email: normalizedEmail,
6006
6075
  requestId,
6007
6076
  version: getQwenWebVersion() || "0.3.12",
6008
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0]
6077
+ timezone: formatAsciiTimezone()
6009
6078
  }
6010
6079
  ).catch((err) => ({ ok: false, error: err?.message || String(err) }));
6011
6080
  if (!evalRes.ok || evalRes.data?.success !== true) {
@@ -6100,7 +6169,7 @@ async function verifyEmailOtpLogin(sessionId, code) {
6100
6169
  code: cleanCode,
6101
6170
  requestId,
6102
6171
  version: getQwenWebVersion() || "0.3.12",
6103
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0]
6172
+ timezone: formatAsciiTimezone()
6104
6173
  }
6105
6174
  ).catch((err) => ({ ok: false, error: err?.message || String(err) }));
6106
6175
  if (!evalRes.ok || evalRes.data?.success !== true || !evalRes.data?.data) {
@@ -6381,7 +6450,7 @@ async function loginViaApi(page, email, password) {
6381
6450
  const signinUrl = "https://auth.qwen.ai/api/v2/auths/signin";
6382
6451
  const requestId = crypto5.randomUUID();
6383
6452
  const evalRes = await page.evaluate(
6384
- async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2 }) => {
6453
+ async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2, tz }) => {
6385
6454
  try {
6386
6455
  const response = await fetch(signinUrl2, {
6387
6456
  method: "POST",
@@ -6393,7 +6462,7 @@ async function loginViaApi(page, email, password) {
6393
6462
  source: "web",
6394
6463
  version: "0.3.11",
6395
6464
  "x-request-origin": "https://chat.qwen.ai",
6396
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
6465
+ timezone: tz,
6397
6466
  "x-request-id": requestId2
6398
6467
  },
6399
6468
  body: JSON.stringify({ email: email2, password: password2 })
@@ -6404,7 +6473,7 @@ async function loginViaApi(page, email, password) {
6404
6473
  return { ok: false, error: e.message };
6405
6474
  }
6406
6475
  },
6407
- { email, password: hashedPassword, signinUrl, requestId }
6476
+ { email, password: hashedPassword, signinUrl, requestId, tz: formatAsciiTimezone() }
6408
6477
  ).catch(() => null);
6409
6478
  let data = evalRes?.data;
6410
6479
  let signinSuccess = Boolean(
@@ -6413,7 +6482,7 @@ async function loginViaApi(page, email, password) {
6413
6482
  if (!signinSuccess && !data?.data?.code) {
6414
6483
  const fallbackUrl = qwenUrl("/api/v2/auths/signin");
6415
6484
  const fallbackRes = await page.evaluate(
6416
- async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2 }) => {
6485
+ async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2, tz }) => {
6417
6486
  try {
6418
6487
  const response = await fetch(fallbackUrl2, {
6419
6488
  method: "POST",
@@ -6424,7 +6493,7 @@ async function loginViaApi(page, email, password) {
6424
6493
  "content-type": "application/json",
6425
6494
  source: "web",
6426
6495
  version: "0.3.11",
6427
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
6496
+ timezone: tz,
6428
6497
  "x-request-id": requestId2
6429
6498
  },
6430
6499
  body: JSON.stringify({ email: email2, password: password2 })
@@ -6435,7 +6504,7 @@ async function loginViaApi(page, email, password) {
6435
6504
  return { ok: false, error: e.message };
6436
6505
  }
6437
6506
  },
6438
- { email, password: hashedPassword, fallbackUrl, requestId }
6507
+ { email, password: hashedPassword, fallbackUrl, requestId, tz: formatAsciiTimezone() }
6439
6508
  ).catch(() => null);
6440
6509
  if (fallbackRes?.data) {
6441
6510
  data = fallbackRes.data;
@@ -6767,7 +6836,7 @@ async function refreshAccountToken(accountId) {
6767
6836
  if (refreshToken) {
6768
6837
  try {
6769
6838
  const refreshResult = await page.evaluate(
6770
- async ({ rTok }) => {
6839
+ async ({ rTok, tz }) => {
6771
6840
  try {
6772
6841
  const response = await fetch("https://auth.qwen.ai/api/v2/auths/refresh", {
6773
6842
  method: "GET",
@@ -6777,7 +6846,7 @@ async function refreshAccountToken(accountId) {
6777
6846
  source: "web",
6778
6847
  version: "0.3.11",
6779
6848
  "x-request-origin": "https://chat.qwen.ai",
6780
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
6849
+ timezone: tz,
6781
6850
  authorization: `Bearer ${rTok}`
6782
6851
  }
6783
6852
  });
@@ -6787,7 +6856,7 @@ async function refreshAccountToken(accountId) {
6787
6856
  return { ok: false, error: e.message };
6788
6857
  }
6789
6858
  },
6790
- { rTok: refreshToken }
6859
+ { rTok: refreshToken, tz: formatAsciiTimezone() }
6791
6860
  );
6792
6861
  const json = refreshResult?.data;
6793
6862
  if (json && json.success === true && json.data) {
@@ -6825,6 +6894,7 @@ async function refreshAccountToken(accountId) {
6825
6894
  const cache3 = getHeaderCache(accountId);
6826
6895
  cache3.headers.cookie = cookieStr;
6827
6896
  cache3.lastRefresh = Date.now();
6897
+ cookieCaches.set(accountId, { cookie: cookieStr, timestamp: Date.now() });
6828
6898
  markAccountHeadersReady(accountId);
6829
6899
  saveAuthSession2(accountId, {
6830
6900
  cookie: cookieStr,
@@ -6859,6 +6929,7 @@ async function refreshAccountToken(accountId) {
6859
6929
  const cache3 = getHeaderCache(accountId);
6860
6930
  cache3.headers.cookie = cookieStr;
6861
6931
  cache3.lastRefresh = Date.now();
6932
+ cookieCaches.set(accountId, { cookie: cookieStr, timestamp: Date.now() });
6862
6933
  markAccountHeadersReady(accountId);
6863
6934
  saveAuthSession2(accountId, {
6864
6935
  cookie: cookieStr,
@@ -7913,11 +7984,11 @@ async function keepAlivePlaywrightAccount(accountId) {
7913
7984
  const now = Date.now();
7914
7985
  const currentUrl = page.url();
7915
7986
  const lastRefreshAttempt = lastProactiveTokenRefresh.get(accountId) ?? 0;
7916
- const cookie = await getCookies(accountId);
7987
+ const cookie = await getCookies(accountId, true);
7917
7988
  if (cookie && isTokenExpiringSoon(cookie, 5) && now - lastRefreshAttempt > 6e4) {
7918
7989
  lastProactiveTokenRefresh.set(accountId, now);
7919
- console.log(
7920
- `\u{1F493} [SessionKeeper] Account ${accountId} access token expiring soon (<5m); silently refreshing...`
7990
+ logger.debug(
7991
+ `[SessionKeeper] Account ${accountId} access token expiring soon (<5m); silently refreshing...`
7921
7992
  );
7922
7993
  const res = await refreshAccountToken(accountId);
7923
7994
  if (res.success) {
@@ -7925,6 +7996,9 @@ async function keepAlivePlaywrightAccount(accountId) {
7925
7996
  touchAccountActivity(accountId);
7926
7997
  return true;
7927
7998
  }
7999
+ logger.warn(
8000
+ `[SessionKeeper] Failed to silently refresh expiring token for ${accountId}: ${res.error || "unknown error"}`
8001
+ );
7928
8002
  }
7929
8003
  const lastNavigation = lastKeepAliveNavigation.get(accountId) ?? 0;
7930
8004
  const shouldNavigate = !currentUrl.startsWith(qwenOrigin()) || now - lastNavigation > config.sessionKeeper.navigationIntervalMs;
@@ -8162,6 +8236,7 @@ var init_playwright = __esm({
8162
8236
  init_qwen_headers();
8163
8237
  init_paths();
8164
8238
  init_jwt();
8239
+ init_timezone();
8165
8240
  requireLocal = createRequire(import.meta.url);
8166
8241
  contextInitHooks = [];
8167
8242
  ACCOUNT_MUTEX_MAX_HOLD_MS = 18e4;
@@ -8175,7 +8250,7 @@ var init_playwright = __esm({
8175
8250
  headerCaches = /* @__PURE__ */ new Map();
8176
8251
  HEADER_CACHE_TTL = 24 * 60 * 60 * 1e3;
8177
8252
  HEADER_REFRESH_THRESHOLD = 0.8;
8178
- COOKIE_CACHE_TTL = 15 * 60 * 1e3;
8253
+ COOKIE_CACHE_TTL = 60 * 1e3;
8179
8254
  cookieCaches = /* @__PURE__ */ new Map();
8180
8255
  lastAccountActivity = /* @__PURE__ */ new Map();
8181
8256
  lastKeepAliveNavigation = /* @__PURE__ */ new Map();
@@ -9594,8 +9669,8 @@ function createUsageAccumulator(estimatedPromptTokens) {
9594
9669
  function applyUpstreamUsage(accumulator, candidate) {
9595
9670
  if (!candidate || typeof candidate !== "object") return;
9596
9671
  const usage = candidate;
9597
- const promptTokens = asFiniteNumber(usage.input_tokens);
9598
- const completionTokens = asFiniteNumber(usage.output_tokens);
9672
+ const promptTokens = asFiniteNumber(usage.input_tokens) ?? asFiniteNumber(usage.prompt_tokens);
9673
+ const completionTokens = asFiniteNumber(usage.output_tokens) ?? asFiniteNumber(usage.completion_tokens);
9599
9674
  const totalTokens = asFiniteNumber(usage.total_tokens);
9600
9675
  if (promptTokens !== null) {
9601
9676
  accumulator.promptTokens = promptTokens;
@@ -9825,7 +9900,7 @@ function isModelNotFoundError(err) {
9825
9900
  function isNetworkLikeError(err) {
9826
9901
  if (err instanceof QwenNetworkError) return true;
9827
9902
  const message = errMessage(err).toLowerCase();
9828
- return message === "network error" || message.includes("failed to fetch") || message.includes("fetch failed") || message.includes("network connection was lost") || message.includes("connection reset") || message.includes("connection closed") || message.includes("socket hang up") || message.includes("econnreset") || message.includes("econnrefused") || message.includes("etimedout");
9903
+ return message === "network error" || message.includes("failed to fetch") || message.includes("fetch failed") || message.includes("network connection was lost") || message.includes("connection reset") || message.includes("connection closed") || message.includes("socket hang up") || message.includes("econnreset") || message.includes("econnrefused") || message.includes("etimedout") || message.includes("stream ended before response headers") || message.includes("timed out waiting for response headers");
9829
9904
  }
9830
9905
  function isCorruptedChatHistoryError(err) {
9831
9906
  const message = errMessage(err).toLowerCase();
@@ -11627,6 +11702,8 @@ __export(qwen_exports, {
11627
11702
  QwenUpstreamError: () => QwenUpstreamError,
11628
11703
  QwenUpstreamUnavailableError: () => QwenUpstreamUnavailableError,
11629
11704
  RetryableQwenStreamError: () => RetryableQwenStreamError,
11705
+ STREAM_SLOT_MAX_HOLD_MS: () => STREAM_SLOT_MAX_HOLD_MS,
11706
+ acquireAccountStreamLock: () => acquireAccountStreamLock,
11630
11707
  acquireNewQwenChatSession: () => acquireNewQwenChatSession,
11631
11708
  buildCapturedQwenHeaders: () => buildCapturedQwenHeaders,
11632
11709
  buildChatNewBody: () => buildChatNewBody,
@@ -11640,10 +11717,13 @@ __export(qwen_exports, {
11640
11717
  deleteAllQwenChats: () => deleteAllQwenChats,
11641
11718
  deleteSingleQwenChat: () => deleteSingleQwenChat,
11642
11719
  disableNativeTools: () => disableNativeTools,
11720
+ ensureStaleSlotSweeperStarted: () => ensureStaleSlotSweeperStarted,
11643
11721
  fetchQwenModels: () => fetchQwenModels,
11644
11722
  fetchRemoteQwenChats: () => fetchRemoteQwenChats,
11645
11723
  flushLogicalThreadState: () => flushLogicalThreadState,
11724
+ getAccountStreamMutex: () => getAccountStreamMutex,
11646
11725
  getAnyCachedQwenModels: () => getAnyCachedQwenModels,
11726
+ getBrowserFetchHeaders: () => getBrowserFetchHeaders,
11647
11727
  getInFlightPersonalizationCount: () => getInFlightPersonalizationCount,
11648
11728
  getLogicalThreadState: () => getLogicalThreadState,
11649
11729
  getQwenErrorCode: () => getQwenErrorCode,
@@ -11659,6 +11739,8 @@ __export(qwen_exports, {
11659
11739
  requestQwenSettingsDirectFetch: () => requestQwenSettingsDirectFetch,
11660
11740
  requestQwenTextInBrowser: () => requestQwenTextInBrowser,
11661
11741
  setToolCapNotice: () => setToolCapNotice,
11742
+ stopStaleSlotSweeperForTests: () => stopStaleSlotSweeperForTests,
11743
+ sweepStaleAccountStreamSlots: () => sweepStaleAccountStreamSlots,
11662
11744
  syncQwenRequestPersonalization: () => syncQwenRequestPersonalization,
11663
11745
  updateLogicalThreadParent: () => updateLogicalThreadParent,
11664
11746
  updateLogicalThreadState: () => updateLogicalThreadState,
@@ -11945,7 +12027,7 @@ function buildCapturedQwenHeaders(headers, options = {}) {
11945
12027
  function getAccountStreamMutex(accountId) {
11946
12028
  let slots2 = accountStreamMutexes.get(accountId);
11947
12029
  if (!slots2) {
11948
- slots2 = { active: 0, queue: [] };
12030
+ slots2 = { activeSlots: /* @__PURE__ */ new Map(), queue: [] };
11949
12031
  accountStreamMutexes.set(accountId, slots2);
11950
12032
  }
11951
12033
  return slots2;
@@ -11953,27 +12035,71 @@ function getAccountStreamMutex(accountId) {
11953
12035
  function streamSlotCapacity() {
11954
12036
  return Math.max(1, config.concurrency.maxStreamsPerAccount);
11955
12037
  }
11956
- function createStreamSlotRelease(slots2) {
12038
+ function createStreamSlotRelease(slots2, slotId) {
11957
12039
  let released = false;
11958
12040
  return () => {
11959
12041
  if (released) return;
11960
12042
  released = true;
11961
- slots2.active -= 1;
12043
+ slots2.activeSlots.delete(slotId);
11962
12044
  const next = slots2.queue.shift();
11963
12045
  if (next) {
11964
- slots2.active += 1;
11965
- next();
12046
+ const nextId = nextStreamSlotId++;
12047
+ slots2.activeSlots.set(nextId, Date.now());
12048
+ next(createStreamSlotRelease(slots2, nextId));
11966
12049
  }
11967
12050
  };
11968
12051
  }
12052
+ function sweepStaleAccountStreamSlots(maxHoldMs = STREAM_SLOT_MAX_HOLD_MS) {
12053
+ const now = Date.now();
12054
+ let sweptCount = 0;
12055
+ for (const [accountId, slots2] of accountStreamMutexes.entries()) {
12056
+ for (const [slotId, startedAt] of Array.from(slots2.activeSlots.entries())) {
12057
+ if (now - startedAt >= maxHoldMs) {
12058
+ logger.warn(
12059
+ `[Qwen] Force-releasing stale stream slot | account=${accountId} | slotId=${slotId} | heldFor=${now - startedAt}ms | maxHold=${maxHoldMs}ms`
12060
+ );
12061
+ slots2.activeSlots.delete(slotId);
12062
+ sweptCount++;
12063
+ const next = slots2.queue.shift();
12064
+ if (next) {
12065
+ const nextId = nextStreamSlotId++;
12066
+ slots2.activeSlots.set(nextId, now);
12067
+ next(createStreamSlotRelease(slots2, nextId));
12068
+ }
12069
+ }
12070
+ }
12071
+ }
12072
+ return sweptCount;
12073
+ }
12074
+ function ensureStaleSlotSweeperStarted() {
12075
+ if (staleSlotSweeperTimer) return;
12076
+ staleSlotSweeperTimer = setInterval(() => {
12077
+ try {
12078
+ sweepStaleAccountStreamSlots();
12079
+ } catch (err) {
12080
+ logger.error("[Qwen] Error in stale stream slot sweeper", {
12081
+ error: err instanceof Error ? err.message : String(err)
12082
+ });
12083
+ }
12084
+ }, 6e4);
12085
+ staleSlotSweeperTimer.unref();
12086
+ }
12087
+ function stopStaleSlotSweeperForTests() {
12088
+ if (staleSlotSweeperTimer) {
12089
+ clearInterval(staleSlotSweeperTimer);
12090
+ staleSlotSweeperTimer = null;
12091
+ }
12092
+ }
11969
12093
  async function acquireAccountStreamLock(accountId) {
12094
+ ensureStaleSlotSweeperStarted();
11970
12095
  const slots2 = getAccountStreamMutex(accountId);
11971
- if (slots2.active < streamSlotCapacity()) {
11972
- slots2.active += 1;
11973
- return Promise.resolve(createStreamSlotRelease(slots2));
12096
+ if (slots2.activeSlots.size < streamSlotCapacity()) {
12097
+ const slotId = nextStreamSlotId++;
12098
+ slots2.activeSlots.set(slotId, Date.now());
12099
+ return Promise.resolve(createStreamSlotRelease(slots2, slotId));
11974
12100
  }
11975
12101
  return new Promise((resolve) => {
11976
- slots2.queue.push(() => resolve(createStreamSlotRelease(slots2)));
12102
+ slots2.queue.push((releaseFn) => resolve(releaseFn));
11977
12103
  });
11978
12104
  }
11979
12105
  function buildQwenSettingsUpdatePayload(currentSettings, instruction) {
@@ -12047,7 +12173,7 @@ async function withQwenBrowserPage(accountId, fn, targetPath, operationTimeoutMs
12047
12173
  return fn(page);
12048
12174
  },
12049
12175
  operationTimeoutMs,
12050
- Math.min(config.timeouts.page, 3e4),
12176
+ Math.max(6e4, operationTimeoutMs),
12051
12177
  recoverOnTimeout
12052
12178
  );
12053
12179
  }
@@ -12065,11 +12191,13 @@ function getBrowserFetchHeaders(headers) {
12065
12191
  "x-request-id",
12066
12192
  "x-accel-buffering"
12067
12193
  ]);
12068
- return Object.fromEntries(
12069
- Object.entries(headers).filter(
12070
- ([name]) => browserAllowedHeaders.has(name.toLowerCase())
12071
- )
12072
- );
12194
+ const result = {};
12195
+ for (const [name, value] of Object.entries(headers)) {
12196
+ if (browserAllowedHeaders.has(name.toLowerCase())) {
12197
+ result[name] = sanitizeHeaderForFetch(String(value ?? ""));
12198
+ }
12199
+ }
12200
+ return result;
12073
12201
  }
12074
12202
  async function requestQwenTextInBrowser(accountId, method, path21, headers, body, options = {}) {
12075
12203
  const url = qwenUrl(path21);
@@ -12090,7 +12218,7 @@ async function requestQwenTextInBrowser(accountId, method, path21, headers, body
12090
12218
  browserHeaders["Content-Type"] = "application/json";
12091
12219
  }
12092
12220
  const evaluateRequest = (page) => page.evaluate(
12093
- async ({ url: url2, method: method2, headers: headers2, body: body2, referrer, timeoutMs }) => {
12221
+ async ({ url: url2, method: method2, headers: headers2, body: body2, referrer, timeoutMs, tz }) => {
12094
12222
  const controller = new AbortController();
12095
12223
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
12096
12224
  try {
@@ -12114,7 +12242,7 @@ async function requestQwenTextInBrowser(accountId, method, path21, headers, body
12114
12242
  source: "web",
12115
12243
  version: "0.3.11",
12116
12244
  "x-request-origin": "https://chat.qwen.ai",
12117
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
12245
+ timezone: tz,
12118
12246
  authorization: `Bearer ${rTok}`
12119
12247
  },
12120
12248
  signal: AbortSignal.timeout(5e3)
@@ -12159,7 +12287,8 @@ async function requestQwenTextInBrowser(accountId, method, path21, headers, body
12159
12287
  headers: browserHeaders,
12160
12288
  body,
12161
12289
  referrer: options.referrer,
12162
- timeoutMs: options.timeoutMs ?? Math.min(config.timeouts.page, 2e4)
12290
+ timeoutMs: options.timeoutMs ?? Math.min(config.timeouts.page, 2e4),
12291
+ tz: formatAsciiTimezone()
12163
12292
  }
12164
12293
  );
12165
12294
  const recoverOnTimeout = !options.noMutexRecovery;
@@ -12837,7 +12966,7 @@ async function disableNativeTools(accountId) {
12837
12966
  async (page) => {
12838
12967
  const requestId = crypto7.randomUUID();
12839
12968
  const response = await page.evaluate(
12840
- async ({ payload: payload2, timeoutMs, requestId: requestId2 }) => {
12969
+ async ({ payload: payload2, timeoutMs, requestId: requestId2, tz }) => {
12841
12970
  const controller = new AbortController();
12842
12971
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
12843
12972
  try {
@@ -12849,7 +12978,7 @@ async function disableNativeTools(accountId) {
12849
12978
  accept: "application/json, text/plain, */*",
12850
12979
  "content-type": "application/json",
12851
12980
  "x-request-id": requestId2,
12852
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
12981
+ timezone: tz,
12853
12982
  source: "web"
12854
12983
  },
12855
12984
  body: JSON.stringify(payload2),
@@ -12861,7 +12990,7 @@ async function disableNativeTools(accountId) {
12861
12990
  clearTimeout(timeoutId);
12862
12991
  }
12863
12992
  },
12864
- { payload, timeoutMs: config.timeouts.http, requestId }
12993
+ { payload, timeoutMs: config.timeouts.http, requestId, tz: formatAsciiTimezone() }
12865
12994
  );
12866
12995
  return response;
12867
12996
  }
@@ -13078,7 +13207,7 @@ async function deleteSingleQwenChat(accountId, chatId) {
13078
13207
  const requestHeaders = {
13079
13208
  source: "web",
13080
13209
  version: "0.2.89",
13081
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
13210
+ timezone: formatAsciiTimezone(),
13082
13211
  "x-request-id": crypto7.randomUUID(),
13083
13212
  Referer: qwenUrl(`/c/${encodeURIComponent(chatId)}`)
13084
13213
  };
@@ -13122,7 +13251,7 @@ async function fetchRemoteQwenChats(accountId) {
13122
13251
  }
13123
13252
  const requestHeaders = {
13124
13253
  version: "0.2.89",
13125
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
13254
+ timezone: formatAsciiTimezone(),
13126
13255
  "x-request-id": crypto7.randomUUID(),
13127
13256
  Referer: qwenUrl("/settings/chats")
13128
13257
  };
@@ -13174,7 +13303,7 @@ async function fetchQwenModels(accountId) {
13174
13303
  async (page) => {
13175
13304
  const requestId = crypto7.randomUUID();
13176
13305
  const response2 = await page.evaluate(
13177
- async ({ timeoutMs, requestId: requestId2 }) => {
13306
+ async ({ timeoutMs, requestId: requestId2, tz }) => {
13178
13307
  const controller = new AbortController();
13179
13308
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
13180
13309
  try {
@@ -13183,7 +13312,7 @@ async function fetchQwenModels(accountId) {
13183
13312
  headers: {
13184
13313
  accept: "application/json, text/plain, */*",
13185
13314
  "x-request-id": requestId2,
13186
- timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
13315
+ timezone: tz,
13187
13316
  source: "web"
13188
13317
  },
13189
13318
  signal: controller.signal
@@ -13193,7 +13322,7 @@ async function fetchQwenModels(accountId) {
13193
13322
  clearTimeout(timeoutId);
13194
13323
  }
13195
13324
  },
13196
- { timeoutMs: config.timeouts.http, requestId }
13325
+ { timeoutMs: config.timeouts.http, requestId, tz: formatAsciiTimezone() }
13197
13326
  );
13198
13327
  return response2;
13199
13328
  }
@@ -13232,7 +13361,7 @@ async function fetchQwenModels(accountId) {
13232
13361
  bxUa,
13233
13362
  bxUmidtoken,
13234
13363
  extra: {
13235
- timezone: (/* @__PURE__ */ new Date()).toString()
13364
+ timezone: formatAsciiTimezone()
13236
13365
  }
13237
13366
  }),
13238
13367
  void 0,
@@ -13995,7 +14124,7 @@ async function createQwenStreamInternal(prompt, enableThinking, modelId, forcedP
13995
14124
  clearTimeout(timeoutId);
13996
14125
  }
13997
14126
  }
13998
- 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;
14127
+ 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, STREAM_SLOT_MAX_HOLD_MS, accountStreamMutexes, nextStreamSlotId, staleSlotSweeperTimer, QWEN_SAFE_SETTINGS_PATCH, QWEN_SAFE_SETTINGS_HASH, inFlightPersonalizationSyncs, DISABLE_TOOLS_MAX_RETRIES, DISABLE_TOOLS_BACKOFF_MS, UPSTREAM_RESPONSE_PREVIEW_BYTES;
13999
14128
  var init_qwen = __esm({
14000
14129
  "src/services/qwen.ts"() {
14001
14130
  "use strict";
@@ -14013,6 +14142,7 @@ var init_qwen = __esm({
14013
14142
  init_captcha_coordinator();
14014
14143
  init_captcha_solver();
14015
14144
  init_account_concurrency();
14145
+ init_timezone();
14016
14146
  init_qwen_errors();
14017
14147
  init_qwen_thread_state();
14018
14148
  init_qwen_chat_pool();
@@ -14042,7 +14172,10 @@ var init_qwen = __esm({
14042
14172
  directSettingsFetchBlocked = /* @__PURE__ */ new Set();
14043
14173
  DIRECT_SETTINGS_FETCH_TIMEOUT_MS = 1e4;
14044
14174
  activePersonalizationByAccount = /* @__PURE__ */ new Map();
14175
+ STREAM_SLOT_MAX_HOLD_MS = 30 * 60 * 1e3;
14045
14176
  accountStreamMutexes = /* @__PURE__ */ new Map();
14177
+ nextStreamSlotId = 1;
14178
+ staleSlotSweeperTimer = null;
14046
14179
  QWEN_SAFE_SETTINGS_PATCH = {
14047
14180
  ui: {
14048
14181
  autoTags: false,
@@ -16772,7 +16905,7 @@ function buildContextMeterSnapshot(input, options = getContextMeterOptions()) {
16772
16905
  function enrichUsageWithContextMeter(usage, snapshot, options = getContextMeterOptions()) {
16773
16906
  if (!snapshot || !options.enabled) return usage;
16774
16907
  const upstreamPromptTokens = Number.isFinite(usage.prompt_tokens) && usage.prompt_tokens > 0 ? usage.prompt_tokens : null;
16775
- const reportedPromptTokens = options.reportUsage ? upstreamPromptTokens ?? snapshot.estimatedContextTokens : usage.prompt_tokens;
16908
+ const reportedPromptTokens = options.reportUsage ? upstreamPromptTokens ?? usage.prompt_tokens : usage.prompt_tokens;
16776
16909
  const contextMeter = {
16777
16910
  ...snapshot,
16778
16911
  upstreamPromptTokens,
@@ -20400,6 +20533,35 @@ var init_parser = __esm({
20400
20533
  }
20401
20534
  });
20402
20535
 
20536
+ // src/tools/tool-call-heuristics.ts
20537
+ function isToolHesitationText(text) {
20538
+ if (!text) return false;
20539
+ return /\btools?\b[^.\n]{0,24}?\b(?:does\s*n[o']?t|do\s*n[o']?t|does\s*not|do\s*not)\b[^.\n]{0,12}?\bexist/i.test(
20540
+ text
20541
+ );
20542
+ }
20543
+ function decideMissingToolCallRetry(input) {
20544
+ const {
20545
+ declaredToolCount,
20546
+ successfulToolCallCount,
20547
+ malformedToolCallCount,
20548
+ emittedToolCallCount,
20549
+ text
20550
+ } = input;
20551
+ if (declaredToolCount <= 0) return { retry: false, reason: null };
20552
+ if (successfulToolCallCount > 0) return { retry: false, reason: null };
20553
+ if (malformedToolCallCount > 0) return { retry: true, reason: "malformed" };
20554
+ if (emittedToolCallCount === 0 && isToolHesitationText(text)) {
20555
+ return { retry: true, reason: "no_call" };
20556
+ }
20557
+ return { retry: false, reason: null };
20558
+ }
20559
+ var init_tool_call_heuristics = __esm({
20560
+ "src/tools/tool-call-heuristics.ts"() {
20561
+ "use strict";
20562
+ }
20563
+ });
20564
+
20403
20565
  // src/routes/chat/errors.ts
20404
20566
  function isWafChallenge(value) {
20405
20567
  const normalized = value.toLowerCase();
@@ -21099,10 +21261,17 @@ async function processNonStreamingResponse(params) {
21099
21261
  message.tool_calls = toolCallsOut;
21100
21262
  }
21101
21263
  const finishReason = toolCallsOut.length ? "tool_calls" : "stop";
21102
- const allToolsFailed = toolParser && toolParser.getMalformedToolCalls().length > 0 && toolCallsOut.length === 0;
21264
+ const retryDecision = toolParser ? decideMissingToolCallRetry({
21265
+ declaredToolCount: declaredTools.length,
21266
+ successfulToolCallCount: toolParser.getSuccessfulToolCallCount(),
21267
+ malformedToolCallCount: toolParser.getMalformedToolCalls().length,
21268
+ emittedToolCallCount: toolParser.getEmittedToolCallCount(),
21269
+ text: finalContent
21270
+ }) : { retry: false, reason: null };
21271
+ const allToolsFailed = retryDecision.retry;
21103
21272
  const malformedRetryCount = midStreamRetry?.malformedRetryCount ?? 0;
21104
21273
  if (allToolsFailed && config.retry.autoRetryMalformedTools !== false && midStreamRetry && malformedRetryCount < config.retry.autoRetryMalformedToolsMax) {
21105
- const malformedCalls = toolParser.getMalformedToolCalls();
21274
+ const malformedCalls = toolParser?.getMalformedToolCalls() ?? [];
21106
21275
  const malformedCount = malformedCalls.length;
21107
21276
  const undeclaredNames = malformedCalls.flatMap((mc) => mc.undeclaredNames || []).filter((name, index, self) => self.indexOf(name) === index);
21108
21277
  const availableToolNames = declaredTools.map((t) => t.type === "function" ? t.function?.name : t.name).filter((n) => !!n);
@@ -21110,7 +21279,9 @@ async function processNonStreamingResponse(params) {
21110
21279
 
21111
21280
  Available tools: ${availableToolNames.join(", ")}` : "";
21112
21281
  let errorMessage;
21113
- if (undeclaredNames.length > 0) {
21282
+ if (retryDecision.reason === "no_call") {
21283
+ errorMessage = `You did not emit any tool call. Respond with a single tool-call block using one of the declared tools.${toolsHint}`;
21284
+ } else if (undeclaredNames.length > 0) {
21114
21285
  errorMessage = `Your previous ${malformedCount} tool call(s) used undeclared tool names: ${undeclaredNames.join(", ")}. Only declared tools can be executed. Please retry with valid tool names.${toolsHint}`;
21115
21286
  } else {
21116
21287
  const previews = malformedCalls.slice(0, 3).map((mc) => mc.contentPreview?.substring(0, 100) || "(empty)").join("\n - ");
@@ -21186,7 +21357,7 @@ Please retry your tool call(s) with correct JSON and valid tool names from the a
21186
21357
  new Error("client aborted during malformed-tool retry")
21187
21358
  );
21188
21359
  }
21189
- console.log(`\u{1F504} [Chat] Auto-retry (${malformedRetryCount + 1}/${config.retry.autoRetryMalformedToolsMax}) | ${newStreamResult.activeAccountLabel} | ${body.model} | chat=${newStreamResult.uiSessionId.substring(0, 12)} | reason=malformed_tool_calls`);
21360
+ console.log(`\u{1F504} [Chat] Auto-retry (${malformedRetryCount + 1}/${config.retry.autoRetryMalformedToolsMax}) | ${newStreamResult.activeAccountLabel} | ${body.model} | chat=${newStreamResult.uiSessionId.substring(0, 12)} | reason=${retryDecision.reason === "no_call" ? "missing_tool_call" : "malformed_tool_calls"}`);
21190
21361
  return processNonStreamingResponse({
21191
21362
  ...params,
21192
21363
  stream: newStreamResult.stream,
@@ -22282,7 +22453,14 @@ async function processStreamingResponse(params) {
22282
22453
  let activeRetryStream = null;
22283
22454
  while (malformedRetryCount < maxMalformedRetries) {
22284
22455
  if (!toolParser) break;
22285
- const allToolsFailed = toolParser.getMalformedToolCalls().length > 0 && toolParser.getSuccessfulToolCallCount() === 0;
22456
+ const retryDecision = decideMissingToolCallRetry({
22457
+ declaredToolCount: declaredTools.length,
22458
+ successfulToolCallCount: toolParser.getSuccessfulToolCallCount(),
22459
+ malformedToolCallCount: toolParser.getMalformedToolCalls().length,
22460
+ emittedToolCallCount: toolParser.getEmittedToolCallCount(),
22461
+ text: finalContent
22462
+ });
22463
+ const allToolsFailed = retryDecision.retry;
22286
22464
  if (!allToolsFailed) break;
22287
22465
  const malformedCalls = toolParser.getMalformedToolCalls();
22288
22466
  const malformedCount = malformedCalls.length;
@@ -22292,7 +22470,9 @@ async function processStreamingResponse(params) {
22292
22470
 
22293
22471
  Available tools: ${availableToolNames.join(", ")}` : "";
22294
22472
  let errorMessage;
22295
- if (undeclaredNames.length > 0) {
22473
+ if (retryDecision.reason === "no_call") {
22474
+ errorMessage = `You did not emit any tool call. Respond with a single tool-call block using one of the declared tools.${toolsHint}`;
22475
+ } else if (undeclaredNames.length > 0) {
22296
22476
  errorMessage = `Your previous ${malformedCount} tool call(s) used undeclared tool names: ${undeclaredNames.join(", ")}. Only declared tools can be executed. Please retry with valid tool names.${toolsHint}`;
22297
22477
  } else {
22298
22478
  const previews = malformedCalls.slice(0, 3).map((mc) => mc.contentPreview?.substring(0, 100) || "(empty)").join("\n - ");
@@ -22369,7 +22549,7 @@ Please retry your tool call(s) with correct JSON and valid tool names from the a
22369
22549
  break;
22370
22550
  }
22371
22551
  console.log(
22372
- `\u{1F504} [Chat] Auto-retry (${malformedRetryCount + 1}/${maxMalformedRetries}) | ${newStreamResult.activeAccountLabel} | ${body.model} | chat=${newStreamResult.uiSessionId.substring(0, 12)} | reason=malformed_tool_calls`
22552
+ `\u{1F504} [Chat] Auto-retry (${malformedRetryCount + 1}/${maxMalformedRetries}) | ${newStreamResult.activeAccountLabel} | ${body.model} | chat=${newStreamResult.uiSessionId.substring(0, 12)} | reason=${retryDecision.reason === "no_call" ? "missing_tool_call" : "malformed_tool_calls"}`
22373
22553
  );
22374
22554
  const retryEntry = getStream(newStreamResult.completionId);
22375
22555
  removeStream(newStreamResult.completionId);
@@ -22870,6 +23050,7 @@ var init_streaming = __esm({
22870
23050
  init_retry_policy();
22871
23051
  init_account();
22872
23052
  init_parser();
23053
+ init_tool_call_heuristics();
22873
23054
  init_stream_registry();
22874
23055
  init_metrics();
22875
23056
  init_logger();
@@ -24278,7 +24459,7 @@ function chatCompletionsToResponses(chatRes, requestModel, originalRequest) {
24278
24459
  cached_tokens: chatRes.usage.prompt_tokens_details?.cached_tokens ?? 0
24279
24460
  },
24280
24461
  output_tokens_details: {
24281
- reasoning_tokens: chatRes.usage.completion_tokens_details?.reasoning_tokens ?? 0
24462
+ reasoning_tokens: chatRes.usage.completion_tokens_details?.reasoning_tokens ?? chatRes.usage.output_tokens_details?.reasoning_tokens ?? 0
24282
24463
  },
24283
24464
  ...chatRes.usage.context_meter ? { context_meter: chatRes.usage.context_meter } : {}
24284
24465
  };
@@ -25739,7 +25920,7 @@ function translateOpenAIToAnthropic(openaiResponse, requestModel) {
25739
25920
  content_filter: "end_turn"
25740
25921
  };
25741
25922
  const hasToolUse = content.some((b) => b.type === "tool_use");
25742
- const stopReason = hasToolUse ? "tool_use" : stopReasonMap[choice.finish_reason || "stop"] || "end_turn";
25923
+ const stopReason = choice.finish_reason === "length" ? "max_tokens" : choice.finish_reason === "content_filter" ? "end_turn" : hasToolUse ? "tool_use" : stopReasonMap[choice.finish_reason || "stop"] || "end_turn";
25743
25924
  return {
25744
25925
  id: generateMessageId2(),
25745
25926
  type: "message",
@@ -25877,7 +26058,7 @@ function translateStreamChunk(chunk, state) {
25877
26058
  state.contentBlockIndex++;
25878
26059
  state.currentBlockType = null;
25879
26060
  }
25880
- const stopReason = state.hasEmittedToolUse ? "tool_use" : choice.finish_reason === "length" ? "max_tokens" : "end_turn";
26061
+ const stopReason = choice.finish_reason === "length" ? "max_tokens" : choice.finish_reason === "content_filter" ? "end_turn" : state.hasEmittedToolUse ? "tool_use" : "end_turn";
25881
26062
  events.push(
25882
26063
  JSON.stringify({
25883
26064
  type: "message_delta",
@@ -26823,7 +27004,7 @@ function getAppVersion() {
26823
27004
  }
26824
27005
  } catch {
26825
27006
  }
26826
- cachedVersion = "v1.6.3";
27007
+ cachedVersion = "v1.6.5";
26827
27008
  return cachedVersion;
26828
27009
  }
26829
27010
  var cachedVersion;