qwenproxy-cli 1.0.6 → 1.0.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwenproxy-cli",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
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
  }
@@ -1201,12 +1201,13 @@ export async function initPlaywrightForAccount(
1201
1201
  // If a context limit is configured, make room by closing idle contexts.
1202
1202
  await evictIdlePlaywrightContextsToLimit().catch(() => {});
1203
1203
 
1204
+ const profilePath = getAccountProfilePath(account.id);
1204
1205
  const fingerprint = getFingerprintProfile(account.id);
1205
- const sharedBrowser = await getOrLaunchSharedBrowser(browserType, headless);
1206
- const storageState = loadStorageState(account.id);
1206
+ const { engine, channel } = resolveBrowserEngine(browserType);
1207
1207
 
1208
- const acctContext = await sharedBrowser.newContext({
1209
- ...(storageState ? { storageState } : {}),
1208
+ const launchOptions = {
1209
+ headless,
1210
+ channel,
1210
1211
  userAgent: fingerprint.userAgent,
1211
1212
  locale: fingerprint.locale,
1212
1213
  timezoneId: fingerprint.timezoneId,
@@ -1215,13 +1216,37 @@ export async function initPlaywrightForAccount(
1215
1216
  deviceScaleFactor: 1,
1216
1217
  isMobile: false,
1217
1218
  hasTouch: false,
1218
- colorScheme: "light",
1219
+ colorScheme: "light" as const,
1219
1220
  extraHTTPHeaders: {
1220
1221
  "sec-ch-ua": fingerprint.secChUa,
1221
1222
  "sec-ch-ua-mobile": "?0",
1222
1223
  "sec-ch-ua-platform": '"Windows"',
1223
1224
  },
1224
- });
1225
+ ignoreDefaultArgs: ["--enable-automation", "--enable-blink-features"],
1226
+ args: buildChromiumLaunchArgs(fingerprint.viewport),
1227
+ };
1228
+
1229
+ let acctContext: BrowserContext;
1230
+ try {
1231
+ acctContext = await engine.launchPersistentContext(profilePath, launchOptions);
1232
+ } catch (launchErr: any) {
1233
+ if (launchErr?.message?.includes("Executable doesn't exist")) {
1234
+ autoInstallPlaywrightChromium();
1235
+ acctContext = await engine.launchPersistentContext(profilePath, launchOptions);
1236
+ } else {
1237
+ throw launchErr;
1238
+ }
1239
+ }
1240
+
1241
+ try {
1242
+ const v = acctContext.browser()?.version();
1243
+ if (v) {
1244
+ const major = parseInt(v.split(".")[0], 10);
1245
+ if (major >= 100) {
1246
+ updateChromeMajor(major);
1247
+ }
1248
+ }
1249
+ } catch {}
1225
1250
 
1226
1251
  try {
1227
1252
  // Comprehensive stealth scripts for anti-bot evasion
@@ -1230,7 +1255,38 @@ export async function initPlaywrightForAccount(
1230
1255
  await hook(acctContext);
1231
1256
  }
1232
1257
 
1233
- const acctPage = await acctContext.newPage();
1258
+ // If native profile cookies are empty but a backup storage_state.json exists, restore cookies
1259
+ const storageState = loadStorageState(account.id);
1260
+ if (storageState) {
1261
+ try {
1262
+ const raw = fs.readFileSync(storageState, "utf8");
1263
+ const parsed = JSON.parse(raw);
1264
+ if (Array.isArray(parsed.cookies) && parsed.cookies.length > 0) {
1265
+ const currentCookies = await acctContext.cookies();
1266
+ if (currentCookies.length === 0) {
1267
+ await acctContext.addCookies(parsed.cookies).catch(() => {});
1268
+ }
1269
+ }
1270
+ } catch {}
1271
+ }
1272
+
1273
+ // Persistent contexts may already contain an initial about:blank tab.
1274
+ // Reuse it instead of creating a second tab. Prefer a tab already on the
1275
+ // Qwen origin when one exists.
1276
+ const existingPages = acctContext.pages().filter((p) => !p.isClosed());
1277
+ const acctPage =
1278
+ existingPages.find((p) => p.url().startsWith(qwenOrigin())) ??
1279
+ existingPages[0] ??
1280
+ (await acctContext.newPage());
1281
+
1282
+ // Close any extra blank tabs that may have been created by the browser
1283
+ // profile/startup, but keep the primary page selected above.
1284
+ for (const extraPage of existingPages.slice(1)) {
1285
+ if (extraPage !== acctPage && extraPage.url() === "about:blank") {
1286
+ await extraPage.close({ runBeforeUnload: false }).catch(() => {});
1287
+ }
1288
+ }
1289
+
1234
1290
  acctPage.setDefaultTimeout(config.timeouts.page);
1235
1291
  acctPage.setDefaultNavigationTimeout(config.timeouts.navigation);
1236
1292
  accountContexts.set(account.id, acctContext);
@@ -1345,13 +1401,12 @@ export async function validateAccountLogin(
1345
1401
  );
1346
1402
  try {
1347
1403
  if (accountPages.has(account.id)) return true;
1348
-
1404
+ const profilePath = getAccountProfilePath(account.id);
1349
1405
  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 } : {}),
1406
+ const { engine, channel } = resolveBrowserEngine(browserType);
1407
+ const launchOptions = {
1408
+ headless,
1409
+ channel,
1355
1410
  userAgent: fingerprint.userAgent,
1356
1411
  locale: fingerprint.locale,
1357
1412
  timezoneId: fingerprint.timezoneId,
@@ -1360,18 +1415,51 @@ export async function validateAccountLogin(
1360
1415
  deviceScaleFactor: 1,
1361
1416
  isMobile: false,
1362
1417
  hasTouch: false,
1363
- colorScheme: "light",
1418
+ colorScheme: "light" as const,
1364
1419
  extraHTTPHeaders: {
1365
1420
  "sec-ch-ua": fingerprint.secChUa,
1366
1421
  "sec-ch-ua-mobile": "?0",
1367
1422
  "sec-ch-ua-platform": '"Windows"',
1368
1423
  },
1369
- });
1424
+ ignoreDefaultArgs: ["--enable-automation", "--enable-blink-features"],
1425
+ args: buildChromiumLaunchArgs(fingerprint.viewport),
1426
+ };
1427
+
1428
+ let acctContext: BrowserContext;
1429
+ try {
1430
+ acctContext = await engine.launchPersistentContext(profilePath, launchOptions);
1431
+ } catch (launchErr: any) {
1432
+ if (launchErr?.message?.includes("Executable doesn't exist")) {
1433
+ autoInstallPlaywrightChromium();
1434
+ acctContext = await engine.launchPersistentContext(profilePath, launchOptions);
1435
+ } else {
1436
+ throw launchErr;
1437
+ }
1438
+ }
1370
1439
 
1371
1440
  try {
1372
1441
  await acctContext.addInitScript(getStealthScript(fingerprint));
1373
- const acctPage = await acctContext.newPage();
1374
1442
 
1443
+ // If native profile cookies are empty but a backup storage_state.json exists, restore cookies
1444
+ const storageState = loadStorageState(account.id);
1445
+ if (storageState) {
1446
+ try {
1447
+ const raw = fs.readFileSync(storageState, "utf8");
1448
+ const parsed = JSON.parse(raw);
1449
+ if (Array.isArray(parsed.cookies) && parsed.cookies.length > 0) {
1450
+ const currentCookies = await acctContext.cookies();
1451
+ if (currentCookies.length === 0) {
1452
+ await acctContext.addCookies(parsed.cookies).catch(() => {});
1453
+ }
1454
+ }
1455
+ } catch {}
1456
+ }
1457
+
1458
+ const existingPages = acctContext.pages().filter((p) => !p.isClosed());
1459
+ const acctPage =
1460
+ existingPages.find((p) => p.url().startsWith(qwenOrigin())) ??
1461
+ existingPages[0] ??
1462
+ (await acctContext.newPage());
1375
1463
  // Check if already logged in via cookies
1376
1464
  const cookies = await acctContext.cookies();
1377
1465
  const hasAuthCookie = cookies.some(
@@ -2789,6 +2877,9 @@ export function isPlaywrightAlreadyClosedError(error: unknown): boolean {
2789
2877
  message.includes("Target page, context or browser has been closed") ||
2790
2878
  message.includes("Browser has been closed") ||
2791
2879
  message.includes("Target closed") ||
2880
+ message.includes("Target crashed") ||
2881
+ message.includes("Page crashed") ||
2882
+ message.includes("Assertion error") ||
2792
2883
  message.includes("Cannot find parent object") ||
2793
2884
  message.includes("Connection closed")
2794
2885
  );