qwenproxy-cli 1.0.6 → 1.0.8

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/bin/qwenproxy.js CHANGED
@@ -134,25 +134,53 @@ if (isBrowserCommand) {
134
134
  const patchrightEntry = require.resolve("patchright");
135
135
  cliPath = path.join(path.dirname(patchrightEntry), "cli.js");
136
136
  } catch {}
137
- const installEnv = {
138
- ...process.env,
139
- PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT:
140
- process.env.PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT || "300000",
141
- };
142
137
 
143
- if (cliPath && fs.existsSync(cliPath)) {
144
- spawnSync(process.execPath, [cliPath, "install", "chromium"], {
145
- stdio: "inherit",
146
- env: installEnv,
147
- });
148
- } else {
138
+ const runInstall = (mirrorHost) => {
139
+ const installEnv = {
140
+ ...process.env,
141
+ PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT:
142
+ process.env.PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT || "120000",
143
+ ...(mirrorHost ? { PLAYWRIGHT_DOWNLOAD_HOST: mirrorHost } : {}),
144
+ };
145
+
146
+ if (cliPath && fs.existsSync(cliPath)) {
147
+ return spawnSync(process.execPath, [cliPath, "install", "chromium"], {
148
+ stdio: "inherit",
149
+ env: installEnv,
150
+ });
151
+ }
149
152
  const cmd = process.platform === "win32" ? "npx.cmd" : "npx";
150
- spawnSync(cmd, ["--yes", "patchright", "install", "chromium"], {
153
+ return spawnSync(cmd, ["--yes", "patchright", "install", "chromium"], {
151
154
  stdio: "inherit",
152
155
  env: installEnv,
153
156
  });
157
+ };
158
+
159
+ let chosenHost = process.env.PLAYWRIGHT_DOWNLOAD_HOST;
160
+ if (!chosenHost) {
161
+ // Probe official CDN; if unresponsive within 2s, switch to global mirror immediately
162
+ const controller = new AbortController();
163
+ const probeTimer = setTimeout(() => controller.abort(), 2000);
164
+ try {
165
+ const probe = await fetch("https://cdn.playwright.dev", { method: "HEAD", signal: controller.signal });
166
+ clearTimeout(probeTimer);
167
+ if (probe.status >= 500) chosenHost = "https://npmmirror.com/mirrors/playwright";
168
+ } catch {
169
+ clearTimeout(probeTimer);
170
+ chosenHost = "https://npmmirror.com/mirrors/playwright";
171
+ console.log("🌐 [QwenProxy] CDN oficial indisponível na sua região. Usando espelho global de alta velocidade...");
172
+ }
173
+ }
174
+
175
+ let res = runInstall(chosenHost);
176
+ if (res.status !== 0 && !chosenHost) {
177
+ console.log("\n⚠️ [QwenProxy] Falha no CDN primário. Tentando espelho global de alta velocidade...");
178
+ res = runInstall("https://npmmirror.com/mirrors/playwright");
179
+ }
180
+
181
+ if (res.status === 0) {
182
+ console.log("✓ [QwenProxy] Navegador instalado com sucesso!\n");
154
183
  }
155
- console.log("✓ [QwenProxy] Navegador instalado com sucesso!\n");
156
184
  }
157
185
  } catch {}
158
186
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwenproxy-cli",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "High-performance OpenAI & Anthropic compatible API gateway for Qwen with multi-account rotation, interactive TUI, and resilient tool calling.",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
package/src/index.ts CHANGED
@@ -21,6 +21,9 @@ process.on('uncaughtException', (error: unknown) => {
21
21
  msg.includes('Target page, context or browser has been closed') ||
22
22
  msg.includes('Browser has been closed') ||
23
23
  msg.includes('Target closed') ||
24
+ msg.includes('Target crashed') ||
25
+ msg.includes('Page crashed') ||
26
+ msg.includes('Assertion error') ||
24
27
  msg.includes('Connection closed')
25
28
  ) {
26
29
  console.warn(`⚠️ [Playwright] Handled benign driver teardown exception: ${msg}`)
@@ -36,6 +39,9 @@ process.on('unhandledRejection', (reason: unknown) => {
36
39
  msg.includes('Target page, context or browser has been closed') ||
37
40
  msg.includes('Browser has been closed') ||
38
41
  msg.includes('Target closed') ||
42
+ msg.includes('Target crashed') ||
43
+ msg.includes('Page crashed') ||
44
+ msg.includes('Assertion error') ||
39
45
  msg.includes('Connection closed')
40
46
  ) {
41
47
  console.warn(`⚠️ [Playwright] Handled benign driver teardown rejection: ${msg}`)
@@ -147,27 +147,43 @@ export async function subtlePageActivity(page: Page): Promise<void> {
147
147
  const viewport = page.viewportSize();
148
148
  if (!viewport) return;
149
149
 
150
- const x = Math.floor(viewport.width * (0.25 + Math.random() * 0.5));
151
- const y = Math.floor(viewport.height * (0.25 + Math.random() * 0.5));
152
- await page.mouse.move(x, y, { steps: 6 + Math.floor(Math.random() * 8) });
150
+ try {
151
+ const x = Math.floor(viewport.width * (0.25 + Math.random() * 0.5));
152
+ const y = Math.floor(viewport.height * (0.25 + Math.random() * 0.5));
153
+ await page.mouse.move(x, y, { steps: 6 + Math.floor(Math.random() * 8) });
153
154
 
154
- if (Math.random() < 0.35) {
155
- await page.mouse.wheel(0, Math.random() < 0.5 ? 60 : -60).catch(() => {});
156
- }
155
+ if (Math.random() < 0.35) {
156
+ await page.mouse.wheel(0, Math.random() < 0.5 ? 60 : -60).catch(() => {});
157
+ }
157
158
 
158
- await page
159
- .evaluate(() => {
160
- try {
161
- const target = document.querySelector(
162
- '[data-testid="sidebar"], .sidebar, nav, aside, main',
163
- );
164
- if (target) {
165
- target.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }));
166
- target.dispatchEvent(new MouseEvent("mouseleave", { bubbles: true }));
159
+ await page
160
+ .evaluate(() => {
161
+ try {
162
+ const target = document.querySelector(
163
+ '[data-testid="sidebar"], .sidebar, nav, aside, main',
164
+ );
165
+ if (target) {
166
+ target.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }));
167
+ target.dispatchEvent(new MouseEvent("mouseleave", { bubbles: true }));
168
+ }
169
+ } catch {
170
+ // Best-effort keep-alive only.
167
171
  }
168
- } catch {
169
- // Best-effort keep-alive only.
170
- }
171
- })
172
- .catch(() => {});
172
+ })
173
+ .catch(() => {});
174
+ } catch (err: any) {
175
+ if (page.isClosed()) return;
176
+ const msg = err instanceof Error ? err.message : String(err);
177
+ if (
178
+ msg.includes("Target page, context or browser has been closed") ||
179
+ msg.includes("Browser has been closed") ||
180
+ msg.includes("Target closed") ||
181
+ msg.includes("Target crashed") ||
182
+ msg.includes("Page crashed") ||
183
+ msg.includes("Connection closed")
184
+ ) {
185
+ return;
186
+ }
187
+ throw err;
188
+ }
173
189
  }
@@ -13,30 +13,40 @@ import { createRequire } from "module";
13
13
  const requireLocal = createRequire(import.meta.url);
14
14
 
15
15
  function autoInstallPlaywrightChromium(): void {
16
- const installEnv = {
17
- ...process.env,
18
- PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT:
19
- process.env.PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT || "300000",
16
+ const tryInstall = (mirror?: string): number | null => {
17
+ const installEnv = {
18
+ ...process.env,
19
+ PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT:
20
+ process.env.PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT || "120000",
21
+ ...(mirror ? { PLAYWRIGHT_DOWNLOAD_HOST: mirror } : {}),
22
+ };
23
+ try {
24
+ const patchrightEntry = requireLocal.resolve("patchright");
25
+ const cliPath = path.join(path.dirname(patchrightEntry), "cli.js");
26
+ if (fs.existsSync(cliPath)) {
27
+ console.log("⏳ [Playwright] Navegador Chromium não encontrado. Instalando automaticamente...");
28
+ const res = spawnSync(process.execPath, [cliPath, "install", "chromium"], {
29
+ stdio: "inherit",
30
+ env: installEnv,
31
+ });
32
+ return res.status;
33
+ }
34
+ } catch {}
35
+
36
+ console.log("⏳ [Playwright] Navegador Chromium não encontrado. Instalando via npx...");
37
+ const cmd = process.platform === "win32" ? "npx.cmd" : "npx";
38
+ const res = spawnSync(cmd, ["--yes", "patchright", "install", "chromium"], {
39
+ stdio: "inherit",
40
+ env: installEnv,
41
+ });
42
+ return res.status;
20
43
  };
21
- try {
22
- const patchrightEntry = requireLocal.resolve("patchright");
23
- const cliPath = path.join(path.dirname(patchrightEntry), "cli.js");
24
- if (fs.existsSync(cliPath)) {
25
- console.log("⏳ [Playwright] Navegador Chromium não encontrado. Instalando automaticamente...");
26
- spawnSync(process.execPath, [cliPath, "install", "chromium"], {
27
- stdio: "inherit",
28
- env: installEnv,
29
- });
30
- return;
31
- }
32
- } catch {}
33
44
 
34
- console.log("⏳ [Playwright] Navegador Chromium não encontrado. Instalando via npx...");
35
- const cmd = process.platform === "win32" ? "npx.cmd" : "npx";
36
- spawnSync(cmd, ["--yes", "patchright", "install", "chromium"], {
37
- stdio: "inherit",
38
- env: installEnv,
39
- });
45
+ const status = tryInstall(process.env.PLAYWRIGHT_DOWNLOAD_HOST);
46
+ if (status !== 0 && !process.env.PLAYWRIGHT_DOWNLOAD_HOST) {
47
+ console.log("\n⚠️ [Playwright] Falha no CDN primário. Tentando espelho global de alta velocidade...");
48
+ tryInstall("https://npmmirror.com/mirrors/playwright");
49
+ }
40
50
  }
41
51
  import { loadAccounts, type QwenAccount } from "../core/accounts.ts";
42
52
  // Imported here rather than injected from session-keeper.ts: account-concurrency
@@ -1201,12 +1211,13 @@ export async function initPlaywrightForAccount(
1201
1211
  // If a context limit is configured, make room by closing idle contexts.
1202
1212
  await evictIdlePlaywrightContextsToLimit().catch(() => {});
1203
1213
 
1214
+ const profilePath = getAccountProfilePath(account.id);
1204
1215
  const fingerprint = getFingerprintProfile(account.id);
1205
- const sharedBrowser = await getOrLaunchSharedBrowser(browserType, headless);
1206
- const storageState = loadStorageState(account.id);
1216
+ const { engine, channel } = resolveBrowserEngine(browserType);
1207
1217
 
1208
- const acctContext = await sharedBrowser.newContext({
1209
- ...(storageState ? { storageState } : {}),
1218
+ const launchOptions = {
1219
+ headless,
1220
+ channel,
1210
1221
  userAgent: fingerprint.userAgent,
1211
1222
  locale: fingerprint.locale,
1212
1223
  timezoneId: fingerprint.timezoneId,
@@ -1215,13 +1226,37 @@ export async function initPlaywrightForAccount(
1215
1226
  deviceScaleFactor: 1,
1216
1227
  isMobile: false,
1217
1228
  hasTouch: false,
1218
- colorScheme: "light",
1229
+ colorScheme: "light" as const,
1219
1230
  extraHTTPHeaders: {
1220
1231
  "sec-ch-ua": fingerprint.secChUa,
1221
1232
  "sec-ch-ua-mobile": "?0",
1222
1233
  "sec-ch-ua-platform": '"Windows"',
1223
1234
  },
1224
- });
1235
+ ignoreDefaultArgs: ["--enable-automation", "--enable-blink-features"],
1236
+ args: buildChromiumLaunchArgs(fingerprint.viewport),
1237
+ };
1238
+
1239
+ let acctContext: BrowserContext;
1240
+ try {
1241
+ acctContext = await engine.launchPersistentContext(profilePath, launchOptions);
1242
+ } catch (launchErr: any) {
1243
+ if (launchErr?.message?.includes("Executable doesn't exist")) {
1244
+ autoInstallPlaywrightChromium();
1245
+ acctContext = await engine.launchPersistentContext(profilePath, launchOptions);
1246
+ } else {
1247
+ throw launchErr;
1248
+ }
1249
+ }
1250
+
1251
+ try {
1252
+ const v = acctContext.browser()?.version();
1253
+ if (v) {
1254
+ const major = parseInt(v.split(".")[0], 10);
1255
+ if (major >= 100) {
1256
+ updateChromeMajor(major);
1257
+ }
1258
+ }
1259
+ } catch {}
1225
1260
 
1226
1261
  try {
1227
1262
  // Comprehensive stealth scripts for anti-bot evasion
@@ -1230,7 +1265,38 @@ export async function initPlaywrightForAccount(
1230
1265
  await hook(acctContext);
1231
1266
  }
1232
1267
 
1233
- const acctPage = await acctContext.newPage();
1268
+ // If native profile cookies are empty but a backup storage_state.json exists, restore cookies
1269
+ const storageState = loadStorageState(account.id);
1270
+ if (storageState) {
1271
+ try {
1272
+ const raw = fs.readFileSync(storageState, "utf8");
1273
+ const parsed = JSON.parse(raw);
1274
+ if (Array.isArray(parsed.cookies) && parsed.cookies.length > 0) {
1275
+ const currentCookies = await acctContext.cookies();
1276
+ if (currentCookies.length === 0) {
1277
+ await acctContext.addCookies(parsed.cookies).catch(() => {});
1278
+ }
1279
+ }
1280
+ } catch {}
1281
+ }
1282
+
1283
+ // Persistent contexts may already contain an initial about:blank tab.
1284
+ // Reuse it instead of creating a second tab. Prefer a tab already on the
1285
+ // Qwen origin when one exists.
1286
+ const existingPages = acctContext.pages().filter((p) => !p.isClosed());
1287
+ const acctPage =
1288
+ existingPages.find((p) => p.url().startsWith(qwenOrigin())) ??
1289
+ existingPages[0] ??
1290
+ (await acctContext.newPage());
1291
+
1292
+ // Close any extra blank tabs that may have been created by the browser
1293
+ // profile/startup, but keep the primary page selected above.
1294
+ for (const extraPage of existingPages.slice(1)) {
1295
+ if (extraPage !== acctPage && extraPage.url() === "about:blank") {
1296
+ await extraPage.close({ runBeforeUnload: false }).catch(() => {});
1297
+ }
1298
+ }
1299
+
1234
1300
  acctPage.setDefaultTimeout(config.timeouts.page);
1235
1301
  acctPage.setDefaultNavigationTimeout(config.timeouts.navigation);
1236
1302
  accountContexts.set(account.id, acctContext);
@@ -1345,13 +1411,12 @@ export async function validateAccountLogin(
1345
1411
  );
1346
1412
  try {
1347
1413
  if (accountPages.has(account.id)) return true;
1348
-
1414
+ const profilePath = getAccountProfilePath(account.id);
1349
1415
  const fingerprint = getFingerprintProfile(account.id);
1350
- const sharedBrowser = await getOrLaunchSharedBrowser(browserType, headless);
1351
- const storageState = loadStorageState(account.id);
1352
-
1353
- const acctContext = await sharedBrowser.newContext({
1354
- ...(storageState ? { storageState } : {}),
1416
+ const { engine, channel } = resolveBrowserEngine(browserType);
1417
+ const launchOptions = {
1418
+ headless,
1419
+ channel,
1355
1420
  userAgent: fingerprint.userAgent,
1356
1421
  locale: fingerprint.locale,
1357
1422
  timezoneId: fingerprint.timezoneId,
@@ -1360,18 +1425,51 @@ export async function validateAccountLogin(
1360
1425
  deviceScaleFactor: 1,
1361
1426
  isMobile: false,
1362
1427
  hasTouch: false,
1363
- colorScheme: "light",
1428
+ colorScheme: "light" as const,
1364
1429
  extraHTTPHeaders: {
1365
1430
  "sec-ch-ua": fingerprint.secChUa,
1366
1431
  "sec-ch-ua-mobile": "?0",
1367
1432
  "sec-ch-ua-platform": '"Windows"',
1368
1433
  },
1369
- });
1434
+ ignoreDefaultArgs: ["--enable-automation", "--enable-blink-features"],
1435
+ args: buildChromiumLaunchArgs(fingerprint.viewport),
1436
+ };
1437
+
1438
+ let acctContext: BrowserContext;
1439
+ try {
1440
+ acctContext = await engine.launchPersistentContext(profilePath, launchOptions);
1441
+ } catch (launchErr: any) {
1442
+ if (launchErr?.message?.includes("Executable doesn't exist")) {
1443
+ autoInstallPlaywrightChromium();
1444
+ acctContext = await engine.launchPersistentContext(profilePath, launchOptions);
1445
+ } else {
1446
+ throw launchErr;
1447
+ }
1448
+ }
1370
1449
 
1371
1450
  try {
1372
1451
  await acctContext.addInitScript(getStealthScript(fingerprint));
1373
- const acctPage = await acctContext.newPage();
1374
1452
 
1453
+ // If native profile cookies are empty but a backup storage_state.json exists, restore cookies
1454
+ const storageState = loadStorageState(account.id);
1455
+ if (storageState) {
1456
+ try {
1457
+ const raw = fs.readFileSync(storageState, "utf8");
1458
+ const parsed = JSON.parse(raw);
1459
+ if (Array.isArray(parsed.cookies) && parsed.cookies.length > 0) {
1460
+ const currentCookies = await acctContext.cookies();
1461
+ if (currentCookies.length === 0) {
1462
+ await acctContext.addCookies(parsed.cookies).catch(() => {});
1463
+ }
1464
+ }
1465
+ } catch {}
1466
+ }
1467
+
1468
+ const existingPages = acctContext.pages().filter((p) => !p.isClosed());
1469
+ const acctPage =
1470
+ existingPages.find((p) => p.url().startsWith(qwenOrigin())) ??
1471
+ existingPages[0] ??
1472
+ (await acctContext.newPage());
1375
1473
  // Check if already logged in via cookies
1376
1474
  const cookies = await acctContext.cookies();
1377
1475
  const hasAuthCookie = cookies.some(
@@ -2789,6 +2887,9 @@ export function isPlaywrightAlreadyClosedError(error: unknown): boolean {
2789
2887
  message.includes("Target page, context or browser has been closed") ||
2790
2888
  message.includes("Browser has been closed") ||
2791
2889
  message.includes("Target closed") ||
2890
+ message.includes("Target crashed") ||
2891
+ message.includes("Page crashed") ||
2892
+ message.includes("Assertion error") ||
2792
2893
  message.includes("Cannot find parent object") ||
2793
2894
  message.includes("Connection closed")
2794
2895
  );