dsh-llm-adapters 1.2.0 → 1.2.2

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/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import path, { dirname, join } from "node:path";
2
2
  import { LlmAdapter, attributionHeaders, offloadedImageText, requestImageHandleText } from "@deepseek-ai/dsh-llm";
3
3
  import { existsSync, mkdirSync, mkdtempSync, promises, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
4
- import { createHash, createHmac, createPrivateKey, generateKeyPairSync, randomBytes, randomUUID, sign } from "node:crypto";
4
+ import { createDecipheriv, createHash, createHmac, createPrivateKey, generateKeyPairSync, randomBytes, randomUUID, sign } from "node:crypto";
5
5
  import { homedir, tmpdir } from "node:os";
6
6
  import { isVolatile } from "@deepseek-ai/cosmokit";
7
7
  import z from "@deepseek-ai/schemastery";
@@ -485,6 +485,14 @@ var LocalAdapter = class extends LlmAdapter {
485
485
  this.invalidateUsage();
486
486
  return result;
487
487
  }
488
+ /** Import a local client login and return the refreshed account reading. */
489
+ async importClientLogin() {
490
+ if (this.client.importClientLogin === void 0) throw new Error("该供应商不支持复用本机客户端登录");
491
+ await this.client.importClientLogin();
492
+ this.invalidateCatalog();
493
+ this.invalidateUsage();
494
+ return this.usageInfo(true);
495
+ }
488
496
  invalidateCatalog() {
489
497
  this.client.invalidateModels();
490
498
  }
@@ -494,9 +502,15 @@ var LocalAdapter = class extends LlmAdapter {
494
502
  /** Login state plus quota, shaped for the settings page. */
495
503
  async usageInfo(fresh = false) {
496
504
  const auth = await this.client.status();
505
+ const clientLogin = this.client.localClientLogin === void 0 ? void 0 : await this.client.localClientLogin().catch((error) => ({
506
+ available: false,
507
+ signedIn: false,
508
+ error: error instanceof Error ? error.message : String(error)
509
+ }));
497
510
  const base = {
498
511
  brand: this.brand,
499
- ...auth
512
+ ...auth,
513
+ ...clientLogin === void 0 ? {} : { clientLogin }
500
514
  };
501
515
  const checkin = this.client.checkinStatus === void 0 ? void 0 : await this.client.checkinStatus().catch(() => void 0);
502
516
  const withCheckin = checkin === void 0 ? base : {
@@ -720,45 +734,48 @@ function defaultRoot() {
720
734
  const base = dshHome !== void 0 && dshHome !== "" ? dshHome : path.join(homedir(), ".dsh");
721
735
  return path.join(base, "llm-adapters");
722
736
  }
723
- function record$4(value) {
737
+ function record$5(value) {
724
738
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
725
739
  }
726
- function text$4(value) {
740
+ function text$5(value) {
727
741
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
728
742
  }
729
743
  function number(value) {
730
744
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
731
745
  }
732
746
  function traeRecord(value, region) {
733
- const raw = record$4(value);
734
- const accessToken = text$4(raw?.accessToken);
747
+ const raw = record$5(value);
748
+ const accessToken = text$5(raw?.accessToken);
735
749
  if (raw === void 0 || accessToken === void 0) return void 0;
750
+ const deviceId = text$5(raw.deviceId);
751
+ const source = raw.source === "web" || raw.source === "client" ? raw.source : void 0;
736
752
  return {
737
753
  accessToken,
738
754
  region,
739
- deviceId: text$4(raw.deviceId) ?? `web-${randomBytes(8).toString("hex")}`,
740
- machineId: text$4(raw.machineId) ?? `web-${randomBytes(16).toString("hex")}`,
755
+ ...source === void 0 ? {} : { source },
756
+ deviceId: deviceId ?? `web-${randomBytes(8).toString("hex")}`,
757
+ machineId: text$5(raw.machineId) ?? `web-${randomBytes(16).toString("hex")}`,
741
758
  loginAt: number(raw.loginAt) ?? Date.now(),
742
- ...text$4(raw.userId) === void 0 ? {} : { userId: text$4(raw.userId) },
743
- ...text$4(raw.name) === void 0 ? {} : { name: text$4(raw.name) },
759
+ ...text$5(raw.userId) === void 0 ? {} : { userId: text$5(raw.userId) },
760
+ ...text$5(raw.name) === void 0 ? {} : { name: text$5(raw.name) },
744
761
  ...number(raw.expiresAt) === void 0 ? {} : { expiresAt: number(raw.expiresAt) },
745
- ...text$4(raw.cookies) === void 0 ? {} : { cookies: text$4(raw.cookies) },
762
+ ...text$5(raw.cookies) === void 0 ? {} : { cookies: text$5(raw.cookies) },
746
763
  ...number(raw.sessionExpiresAt) === void 0 ? {} : { sessionExpiresAt: number(raw.sessionExpiresAt) }
747
764
  };
748
765
  }
749
766
  function codeartsRecord(value) {
750
- const raw = record$4(value);
751
- const accessKeyId = text$4(raw?.accessKeyId);
752
- const secretAccessKey = text$4(raw?.secretAccessKey);
753
- const securityToken = text$4(raw?.securityToken);
767
+ const raw = record$5(value);
768
+ const accessKeyId = text$5(raw?.accessKeyId);
769
+ const secretAccessKey = text$5(raw?.secretAccessKey);
770
+ const securityToken = text$5(raw?.securityToken);
754
771
  if (raw === void 0 || accessKeyId === void 0 || secretAccessKey === void 0 || securityToken === void 0) return;
755
- const jwk = record$4(raw.dpopPrivateJwk);
756
- const key = jwk !== void 0 && text$4(jwk.x) !== void 0 && text$4(jwk.y) !== void 0 && text$4(jwk.d) !== void 0 ? {
772
+ const jwk = record$5(raw.dpopPrivateJwk);
773
+ const key = jwk !== void 0 && text$5(jwk.x) !== void 0 && text$5(jwk.y) !== void 0 && text$5(jwk.d) !== void 0 ? {
757
774
  kty: "EC",
758
775
  crv: "P-256",
759
- x: text$4(jwk.x),
760
- y: text$4(jwk.y),
761
- d: text$4(jwk.d)
776
+ x: text$5(jwk.x),
777
+ y: text$5(jwk.y),
778
+ d: text$5(jwk.d)
762
779
  } : void 0;
763
780
  const noBenefit = Array.isArray(raw.noBenefit) ? raw.noBenefit.filter((id) => typeof id === "string" && id.trim().length > 0).map((id) => id.trim()) : [];
764
781
  return {
@@ -767,10 +784,11 @@ function codeartsRecord(value) {
767
784
  securityToken,
768
785
  loginAt: number(raw.loginAt) ?? Date.now(),
769
786
  ...number(raw.expiresAt) === void 0 ? {} : { expiresAt: number(raw.expiresAt) },
770
- ...text$4(raw.refreshToken) === void 0 ? {} : { refreshToken: text$4(raw.refreshToken) },
771
- ...text$4(raw.codeVerifier) === void 0 ? {} : { codeVerifier: text$4(raw.codeVerifier) },
787
+ ...text$5(raw.refreshToken) === void 0 ? {} : { refreshToken: text$5(raw.refreshToken) },
788
+ ...text$5(raw.codeVerifier) === void 0 ? {} : { codeVerifier: text$5(raw.codeVerifier) },
772
789
  ...key === void 0 ? {} : { dpopPrivateJwk: key },
773
- ...text$4(raw.userName) === void 0 ? {} : { userName: text$4(raw.userName) },
790
+ ...text$5(raw.userName) === void 0 ? {} : { userName: text$5(raw.userName) },
791
+ ...text$5(raw.otSessionId) === void 0 ? {} : { otSessionId: text$5(raw.otSessionId) },
774
792
  ...noBenefit.length === 0 ? {} : { noBenefit }
775
793
  };
776
794
  }
@@ -794,30 +812,30 @@ var AccountStore = class {
794
812
  } catch {
795
813
  return {};
796
814
  }
797
- const raw = record$4(parsed);
815
+ const raw = record$5(parsed);
798
816
  if (raw === void 0) return {};
799
817
  const out = {};
800
- const loomy = record$4(raw.loomy);
801
- const loomySession = text$4(loomy?.session);
818
+ const loomy = record$5(raw.loomy);
819
+ const loomySession = text$5(loomy?.session);
802
820
  if (loomySession !== void 0) out.loomy = {
803
821
  session: loomySession,
804
- deviceId: text$4(loomy?.deviceId) ?? `web-${randomBytes(8).toString("hex")}`,
822
+ deviceId: text$5(loomy?.deviceId) ?? `web-${randomBytes(8).toString("hex")}`,
805
823
  loginAt: number(loomy?.loginAt) ?? Date.now(),
806
824
  expiresAt: number(loomy?.expiresAt) ?? Date.now(),
807
- ...text$4(loomy?.userId) === void 0 ? {} : { userId: text$4(loomy?.userId) },
808
- ...text$4(loomy?.phone) === void 0 ? {} : { phone: text$4(loomy?.phone) },
809
- ...text$4(loomy?.name) === void 0 ? {} : { name: text$4(loomy?.name) }
825
+ ...text$5(loomy?.userId) === void 0 ? {} : { userId: text$5(loomy?.userId) },
826
+ ...text$5(loomy?.phone) === void 0 ? {} : { phone: text$5(loomy?.phone) },
827
+ ...text$5(loomy?.name) === void 0 ? {} : { name: text$5(loomy?.name) }
810
828
  };
811
- const raccoon = record$4(raw.raccoon);
812
- const access = text$4(raccoon?.accessToken);
829
+ const raccoon = record$5(raw.raccoon);
830
+ const access = text$5(raccoon?.accessToken);
813
831
  if (access !== void 0) out.raccoon = {
814
832
  accessToken: access,
815
833
  loginAt: number(raccoon?.loginAt) ?? Date.now(),
816
- ...text$4(raccoon?.phone) === void 0 ? {} : { phone: text$4(raccoon?.phone) },
817
- ...text$4(raccoon?.name) === void 0 ? {} : { name: text$4(raccoon?.name) },
818
- ...text$4(raccoon?.ownerType) === void 0 ? {} : { ownerType: text$4(raccoon?.ownerType) }
834
+ ...text$5(raccoon?.phone) === void 0 ? {} : { phone: text$5(raccoon?.phone) },
835
+ ...text$5(raccoon?.name) === void 0 ? {} : { name: text$5(raccoon?.name) },
836
+ ...text$5(raccoon?.ownerType) === void 0 ? {} : { ownerType: text$5(raccoon?.ownerType) }
819
837
  };
820
- const trae = record$4(raw.trae);
838
+ const trae = record$5(raw.trae);
821
839
  if (trae !== void 0) {
822
840
  const cn = traeRecord(trae.cn, "cn");
823
841
  const global = traeRecord(trae.global, "global");
@@ -1051,6 +1069,14 @@ async function handleAccount(req, res, adapters) {
1051
1069
  });
1052
1070
  return;
1053
1071
  }
1072
+ if (action === "client-login") {
1073
+ try {
1074
+ send(res, 200, await adapter.importClientLogin());
1075
+ } catch (error) {
1076
+ send(res, 400, { error: error instanceof Error ? error.message : String(error) });
1077
+ }
1078
+ return;
1079
+ }
1054
1080
  if (action === "checkin") {
1055
1081
  try {
1056
1082
  send(res, 200, {
@@ -1062,7 +1088,7 @@ async function handleAccount(req, res, adapters) {
1062
1088
  }
1063
1089
  return;
1064
1090
  }
1065
- send(res, 400, { error: "action 只接受 browser、browser-cancel、checkin 或 logout" });
1091
+ send(res, 400, { error: "action 只接受 browser、browser-cancel、client-login、checkin 或 logout" });
1066
1092
  }
1067
1093
  async function handleApi(req, res, adapters, openRouter) {
1068
1094
  try {
@@ -1400,6 +1426,18 @@ function readUserProfile(token) {
1400
1426
  ...domainId === void 0 ? {} : { domainId }
1401
1427
  };
1402
1428
  }
1429
+ /**
1430
+ * The session id this account should chat under, derived from who the account is.
1431
+ *
1432
+ * 32 lowercase hex, the shape the IDE's own kernel sends. Nothing but stability
1433
+ * matters here — the gateway uses the value as a key, not as an identity — so it
1434
+ * is hashed from the principal id rather than kept in a file.
1435
+ */
1436
+ function otSessionIdOf(profile) {
1437
+ const seed = profile?.userId ?? profile?.userName;
1438
+ if (seed === void 0) return void 0;
1439
+ return createHash("sha256").update(`codearts-ot-session:${seed}`).digest("hex").slice(0, 32);
1440
+ }
1403
1441
  /** The portal URL the browser opens; the callback is on loopback. */
1404
1442
  function buildAuthorizeUrl(port, pkce, ticketId) {
1405
1443
  return `${CODEARTS_PORTAL_AUTHORIZE}?theme=2&locale=zh-cn&uri_scheme=${CODEARTS_CLIENT_ID}&client_id=${CODEARTS_CLIENT_ID}&port=${port}&code_challenge=${pkce.codeChallenge}&code_challenge_method=SHA-256&ticket_id=${ticketId}&plugin-name=snap_AIIDE&plugin-version=5.2.0`;
@@ -1450,25 +1488,30 @@ async function exchangeRefreshToken(refreshToken, codeVerifier, keyPair, fetcher
1450
1488
  grant_type: "refresh_token",
1451
1489
  refresh_token: refreshToken
1452
1490
  }, keyPair, fetcher), codeVerifier, keyPair);
1453
- const userName = credential.userName ?? readUserProfile(refreshToken)?.userName;
1491
+ const previous = readUserProfile(refreshToken);
1492
+ const userName = credential.userName ?? previous?.userName;
1493
+ const otSessionId = credential.otSessionId ?? otSessionIdOf(previous);
1454
1494
  return {
1455
1495
  ...credential,
1456
1496
  refreshToken: credential.refreshToken ?? refreshToken,
1457
- ...userName === void 0 ? {} : { userName }
1497
+ ...userName === void 0 ? {} : { userName },
1498
+ ...otSessionId === void 0 ? {} : { otSessionId }
1458
1499
  };
1459
1500
  }
1460
1501
  function credentialFrom(token, codeVerifier, keyPair) {
1461
1502
  const credentials = token.credentials ?? {};
1462
1503
  const expiration = credentials.expiration;
1463
1504
  const expiresAt = expiration === void 0 ? void 0 : Date.parse(expiration);
1464
- const userName = readUserProfile(token.refresh_token)?.userName;
1505
+ const profile = readUserProfile(token.refresh_token);
1506
+ const otSessionId = otSessionIdOf(profile);
1465
1507
  return {
1466
1508
  accessKeyId: credentials.access_key_id ?? "",
1467
1509
  secretAccessKey: credentials.secret_access_key ?? "",
1468
1510
  securityToken: credentials.security_token ?? "",
1469
1511
  ...expiresAt === void 0 || Number.isNaN(expiresAt) ? {} : { expiresAt },
1470
1512
  ...token.refresh_token === void 0 ? {} : { refreshToken: token.refresh_token },
1471
- ...userName === void 0 ? {} : { userName },
1513
+ ...profile?.userName === void 0 ? {} : { userName: profile.userName },
1514
+ ...otSessionId === void 0 ? {} : { otSessionId },
1472
1515
  codeVerifier,
1473
1516
  dpopPrivateJwk: keyPair.privateKeyJwk
1474
1517
  };
@@ -2080,7 +2123,8 @@ const TRAE_REGION_PROFILES = {
2080
2123
  payBase: "https://api.trae.cn",
2081
2124
  timezone: "Asia/Shanghai",
2082
2125
  language: "zh-cn",
2083
- usageV2: true
2126
+ usageV2: true,
2127
+ checkin: true
2084
2128
  },
2085
2129
  global: {
2086
2130
  id: "global",
@@ -2092,7 +2136,8 @@ const TRAE_REGION_PROFILES = {
2092
2136
  payBase: "https://growsg-normal.trae.ai",
2093
2137
  timezone: "Asia/Singapore",
2094
2138
  language: "en",
2095
- usageV2: false
2139
+ usageV2: false,
2140
+ checkin: false
2096
2141
  }
2097
2142
  };
2098
2143
  /** The app id Trae's own clients send; not account-specific. */
@@ -2197,7 +2242,8 @@ function decodeJwtFacts(token) {
2197
2242
  ...typeof record.name === "string" ? { name: record.name } : {},
2198
2243
  ...typeof record.phone === "string" ? { phone: record.phone } : {},
2199
2244
  ...typeof record.owner_type === "string" ? { ownerType: record.owner_type } : {},
2200
- ...typeof record.sid === "string" ? { sessionId: record.sid } : {}
2245
+ ...typeof record.sid === "string" ? { sessionId: record.sid } : {},
2246
+ ...typeof record.sub === "string" ? { subject: record.sub } : {}
2201
2247
  };
2202
2248
  } catch {
2203
2249
  return {};
@@ -2298,12 +2344,13 @@ function mergeSetCookies(current, headers) {
2298
2344
  return [...jar.entries()].map(([name, value]) => `${name}=${value}`).join("; ");
2299
2345
  }
2300
2346
  /**
2301
- * Trae's web login, one per region.
2347
+ * Trae's login, one per region.
2302
2348
  *
2303
- * The `Cloud-IDE-JWT` the website issues lives about eight hours, but the
2304
- * website session behind it lives about fourteen days. The cookies captured at
2305
- * sign-in are replayed against `GetUserToken` to renew the short token silently;
2306
- * only when the session itself dies does the user have to sign in again.
2349
+ * A web login stores the `Cloud-IDE-JWT` from the website plus the cookies
2350
+ * behind it, so the short token can be renewed silently until the website
2351
+ * session dies. A client login stores the token the desktop client already
2352
+ * uses; that token has no website session, and the client itself refreshes it.
2353
+ * Only the client source carries the device id the check-in route accepts.
2307
2354
  */
2308
2355
  var TraeAuth = class {
2309
2356
  store;
@@ -2324,18 +2371,32 @@ var TraeAuth = class {
2324
2371
  const stored = await this.store.trae(region);
2325
2372
  if (stored === void 0) return {
2326
2373
  signedIn: false,
2327
- error: `${traeLabel(region)}尚未登录。请在设置页点「打开浏览器登录」。`
2374
+ error: `${traeLabel(region)}尚未登录。可以点「打开浏览器登录」,或复用本机已登录的 Trae 客户端。`
2328
2375
  };
2376
+ const source = stored.source ?? "web";
2329
2377
  const now = Date.now();
2330
2378
  const { token, session } = traeExpiries(stored);
2331
2379
  const tokenExpired = token !== void 0 && token <= now + TRAE_EXPIRY_MARGIN_MS;
2380
+ const account = traeAccount(stored);
2381
+ if (source === "client") {
2382
+ const signedIn = !tokenExpired;
2383
+ return {
2384
+ signedIn,
2385
+ loginSource: "client",
2386
+ loginAt: stored.loginAt,
2387
+ ...token === void 0 ? {} : { expiresAt: token },
2388
+ ...!signedIn ? { possiblyExpired: true } : {},
2389
+ ...Object.keys(account).length === 0 ? {} : { account },
2390
+ ...!signedIn ? { error: `${traeLabel(region)}客户端登录令牌已过期,请先在 Trae 客户端重新登录,再点「复用客户端登录态」。` } : {}
2391
+ };
2392
+ }
2332
2393
  const sessionExpired = session !== void 0 && session <= now + TRAE_EXPIRY_MARGIN_MS;
2333
2394
  const refreshable = stored.cookies !== void 0 && !sessionExpired;
2334
2395
  const signedIn = !tokenExpired || refreshable;
2335
- const account = traeAccount(stored);
2336
2396
  const expiresAt = refreshable ? session ?? token : token;
2337
2397
  return {
2338
2398
  signedIn,
2399
+ loginSource: "web",
2339
2400
  loginAt: stored.loginAt,
2340
2401
  ...expiresAt === void 0 ? {} : { expiresAt },
2341
2402
  ...!signedIn ? { possiblyExpired: true } : {},
@@ -2346,15 +2407,21 @@ var TraeAuth = class {
2346
2407
  /**
2347
2408
  * The bearer token, or an actionable error when there is none.
2348
2409
  *
2349
- * A token near its expiry is renewed first; `force` means the gateway already
2350
- * rejected it, so the renewal is attempted even if the clock disagrees.
2410
+ * A web token near its expiry is renewed first; `force` means the gateway
2411
+ * already rejected it, so the renewal is attempted even if the clock
2412
+ * disagrees. A client token is returned until its own expiry and then asks
2413
+ * for a fresh import, because only the client can refresh it.
2351
2414
  */
2352
2415
  async accessToken(force = false) {
2353
2416
  const region = this.region();
2354
2417
  const stored = await this.store.trae(region);
2355
- if (stored === void 0) throw new Error(`${traeLabel(region)}尚未登录。请在设置页点「打开浏览器登录」。`);
2418
+ if (stored === void 0) throw new Error(`${traeLabel(region)}尚未登录。可以点「打开浏览器登录」,或复用本机已登录的 Trae 客户端。`);
2356
2419
  const { token, session } = traeExpiries(stored);
2357
2420
  const expired = token !== void 0 && token <= Date.now() + TRAE_EXPIRY_MARGIN_MS;
2421
+ if (stored.source === "client") {
2422
+ if (!force && !expired) return stored.accessToken;
2423
+ throw new Error(`${traeLabel(region)}客户端登录令牌已${force ? "失效" : "过期"},请先在 Trae 客户端重新登录,再点「复用客户端登录态」。`);
2424
+ }
2358
2425
  if (!force && !expired) return stored.accessToken;
2359
2426
  if (stored.cookies === void 0) throw new Error(`${traeLabel(region)}登录已${force ? "失效" : "过期"},请在设置页重新登录。`);
2360
2427
  if (session !== void 0 && session <= Date.now() + TRAE_EXPIRY_MARGIN_MS) throw new Error(`${traeLabel(region)}网页会话已过期,请重新登录。`);
@@ -2366,7 +2433,7 @@ var TraeAuth = class {
2366
2433
  if (existing !== void 0) return existing;
2367
2434
  const task = (async () => {
2368
2435
  const stored = await this.store.trae(region);
2369
- if (stored === void 0 || stored.cookies === void 0) throw new Error(`${traeLabel(region)}登录已失效,请在设置页重新登录。`);
2436
+ if (stored === void 0 || stored.source === "client" || stored.cookies === void 0) throw new Error(`${traeLabel(region)}登录已失效,请在设置页重新登录。`);
2370
2437
  const profile = TRAE_REGION_PROFILES[region];
2371
2438
  let response;
2372
2439
  try {
@@ -2425,7 +2492,7 @@ var TraeAuth = class {
2425
2492
  async identity() {
2426
2493
  const region = this.region();
2427
2494
  const stored = await this.store.trae(region);
2428
- if (stored === void 0) throw new Error(`${traeLabel(region)}尚未登录。请在设置页点「打开浏览器登录」。`);
2495
+ if (stored === void 0) throw new Error(`${traeLabel(region)}尚未登录。可以点「打开浏览器登录」,或复用本机已登录的 Trae 客户端。`);
2429
2496
  return {
2430
2497
  deviceId: stored.deviceId,
2431
2498
  machineId: stored.machineId,
@@ -2509,7 +2576,8 @@ var CodeArtsAuth = class {
2509
2576
  securityToken: next.securityToken,
2510
2577
  ...next.expiresAt === void 0 ? {} : { expiresAt: next.expiresAt },
2511
2578
  ...next.refreshToken === void 0 ? {} : { refreshToken: next.refreshToken },
2512
- ...next.userName === void 0 ? {} : { userName: next.userName }
2579
+ ...next.userName === void 0 ? {} : { userName: next.userName },
2580
+ ...next.otSessionId === void 0 ? {} : { otSessionId: next.otSessionId }
2513
2581
  });
2514
2582
  this.lastError = void 0;
2515
2583
  return next;
@@ -2529,6 +2597,7 @@ var CodeArtsAuth = class {
2529
2597
  }
2530
2598
  };
2531
2599
  function credentialOf(record) {
2600
+ const otSessionId = record.otSessionId ?? otSessionIdOf(readUserProfile(record.refreshToken));
2532
2601
  return {
2533
2602
  accessKeyId: record.accessKeyId,
2534
2603
  secretAccessKey: record.secretAccessKey,
@@ -2536,7 +2605,8 @@ function credentialOf(record) {
2536
2605
  ...record.expiresAt === void 0 ? {} : { expiresAt: record.expiresAt },
2537
2606
  ...record.refreshToken === void 0 ? {} : { refreshToken: record.refreshToken },
2538
2607
  ...record.codeVerifier === void 0 ? {} : { codeVerifier: record.codeVerifier },
2539
- ...record.dpopPrivateJwk === void 0 ? {} : { dpopPrivateJwk: record.dpopPrivateJwk }
2608
+ ...record.dpopPrivateJwk === void 0 ? {} : { dpopPrivateJwk: record.dpopPrivateJwk },
2609
+ ...otSessionId === void 0 ? {} : { otSessionId }
2540
2610
  };
2541
2611
  }
2542
2612
  // src/config.ts
@@ -2583,8 +2653,7 @@ const Config$1 = z.object({
2583
2653
  defaultMaxTokens: z.number().step(1).min(1).default(32e3).description("模型目录未声明输出上限时使用的输出上限。").volatile(),
2584
2654
  modelCacheTtlSeconds: z.number().step(1).min(10).max(86400).default(120).description("模型列表缓存时长(秒)。调小可更快看到账号侧变化。").volatile(),
2585
2655
  catalogFetchTimeoutSeconds: z.number().step(1).min(1).max(300).default(20).description("读取模型目录、额度等请求的时限(秒)。").volatile(),
2586
- openRouterSyncMinutes: z.number().step(1).min(0).max(43200).default(60).description("OpenRouter 模型元数据的自动同步间隔(分钟)。0 表示关闭自动同步;缓存保存在本机,连不上时继续使用上次成功同步的数据。").volatile(),
2587
- raccoonCheckinMinutes: z.number().step(1).min(0).max(1440).default(30).description("小浣熊每日积分自动监测间隔(分钟):到点核对积分流水,未到账时按网页方式尝试触发。0 表示关闭自动监测;「打开网页签到」入口始终保留。").volatile()
2656
+ openRouterSyncMinutes: z.number().step(1).min(0).max(43200).default(60).description("OpenRouter 模型元数据的自动同步间隔(分钟)。0 表示关闭自动同步;缓存保存在本机,连不上时继续使用上次成功同步的数据。").volatile()
2588
2657
  });
2589
2658
  function liveConfig(config) {
2590
2659
  const source = config ?? {};
@@ -2625,8 +2694,7 @@ function liveConfig(config) {
2625
2694
  defaultMaxTokens: () => positive(field("defaultMaxTokens"), 32e3),
2626
2695
  modelCacheTtlMs: () => positive(field("modelCacheTtlSeconds"), 120) * 1e3,
2627
2696
  catalogFetchTimeoutMs: () => positive(field("catalogFetchTimeoutSeconds"), 20) * 1e3,
2628
- openRouterSyncMinutes: () => nonNegative(field("openRouterSyncMinutes"), 60),
2629
- raccoonCheckinMinutes: () => nonNegative(field("raccoonCheckinMinutes"), 30)
2697
+ openRouterSyncMinutes: () => nonNegative(field("openRouterSyncMinutes"), 60)
2630
2698
  };
2631
2699
  }
2632
2700
  function resolveConfig(config) {
@@ -2664,15 +2732,14 @@ function resolveConfig(config) {
2664
2732
  defaultMaxTokens: live.defaultMaxTokens(),
2665
2733
  modelCacheTtlMs: live.modelCacheTtlMs(),
2666
2734
  catalogFetchTimeoutMs: live.catalogFetchTimeoutMs(),
2667
- openRouterSyncMinutes: live.openRouterSyncMinutes(),
2668
- raccoonCheckinMinutes: live.raccoonCheckinMinutes()
2735
+ openRouterSyncMinutes: live.openRouterSyncMinutes()
2669
2736
  };
2670
2737
  }
2671
- function text$3(value) {
2738
+ function text$4(value) {
2672
2739
  return typeof value === "string" ? value.trim() : "";
2673
2740
  }
2674
2741
  function nonEmpty(value, fallback) {
2675
- const valueText = text$3(value);
2742
+ const valueText = text$4(value);
2676
2743
  return valueText.length > 0 ? valueText : fallback;
2677
2744
  }
2678
2745
  function positive(value, fallback) {
@@ -3008,117 +3075,6 @@ function headersForFetch(signed) {
3008
3075
  }
3009
3076
  return out;
3010
3077
  }
3011
- // src/codearts-window.ts
3012
- /**
3013
- * The trial plan's chat budget.
3014
- *
3015
- * The gateway opens one "session" per chat request and allows **three per
3016
- * rolling minute** for the whole account. The window is shared by every model,
3017
- * it is not keyed by `session-id` (re-sending the same id does not buy a slot),
3018
- * and a request rejected with `InferHub.4004.200` spends a slot just like a
3019
- * successful one. Measured behaviour, three slots then a lockout:
3020
- *
3021
- * ```
3022
- * t+0s ok ok ok cap cap cap burst of six
3023
- * t+10s cap …
3024
- * t+51s cap
3025
- * t+61s ok ok ok the burst ages out of the window
3026
- * ```
3027
- *
3028
- * So a turn cannot be made to "go faster" by retrying: the only way a request
3029
- * stops failing is to spend the budget deliberately — wait for the oldest start
3030
- * to age out instead of firing into a wall. This class owns that bookkeeping and
3031
- * is shared by every provider instance in the process, because a hot reload
3032
- * must not reset the count the gateway still remembers.
3033
- *
3034
- * @module dsh-llm-adapters/codearts-window
3035
- */
3036
- /** Sessions the trial plan allows inside one window. */
3037
- const CODEARTS_SESSION_LIMIT = 3;
3038
- /** How long a start keeps its slot. Measured: the burst frees at ~60 s. */
3039
- const CODEARTS_SESSION_WINDOW_MS = 6e4;
3040
- /** A little slack so a request never lands a few milliseconds early. */
3041
- const MARGIN_MS = 1500;
3042
- function sleep$2(ms, signal) {
3043
- return new Promise((resolve, reject) => {
3044
- if (signal?.aborted === true) {
3045
- reject(signal.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("aborted"));
3046
- return;
3047
- }
3048
- const onAbort = () => {
3049
- clearTimeout(timer);
3050
- reject(signal?.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("aborted"));
3051
- };
3052
- const timer = setTimeout(() => {
3053
- signal?.removeEventListener("abort", onAbort);
3054
- resolve();
3055
- }, ms);
3056
- signal?.addEventListener("abort", onAbort, { once: true });
3057
- });
3058
- }
3059
- var CodeArtsSessionWindow = class {
3060
- limit;
3061
- windowMs;
3062
- now;
3063
- starts = [];
3064
- /** Serialises waiters so two callers cannot both take the last slot. */
3065
- tail = Promise.resolve();
3066
- constructor(limit = 3, windowMs = CODEARTS_SESSION_WINDOW_MS, now = Date.now) {
3067
- this.limit = limit;
3068
- this.windowMs = windowMs;
3069
- this.now = now;
3070
- }
3071
- /** Starts recorded inside the current window. */
3072
- get size() {
3073
- this.trim();
3074
- return this.starts.length;
3075
- }
3076
- /** Milliseconds until another start fits; `0` when the window has room. */
3077
- waitMs() {
3078
- this.trim();
3079
- if (this.starts.length < this.limit) return 0;
3080
- const oldest = this.starts[0] ?? 0;
3081
- return Math.max(0, this.windowMs - (this.now() - oldest));
3082
- }
3083
- /**
3084
- * Claim a slot, waiting for the window to roll over when it is full.
3085
- *
3086
- * The slot is recorded on return, not on completion: the gateway counts the
3087
- * start, and holds it for a fixed time whether the answer was short or long.
3088
- */
3089
- async open(signal) {
3090
- const run = this.tail.then(async () => {
3091
- for (;;) {
3092
- const wait = this.waitMs();
3093
- if (wait === 0) {
3094
- const start = this.now();
3095
- this.starts.push(start);
3096
- return { release: () => {
3097
- const index = this.starts.indexOf(start);
3098
- if (index >= 0) this.starts.splice(index, 1);
3099
- } };
3100
- }
3101
- await sleep$2(wait + MARGIN_MS, signal);
3102
- }
3103
- });
3104
- this.tail = run.then(() => void 0, () => void 0);
3105
- return await run;
3106
- }
3107
- /** Forget every start (tests, and a fresh login on another account). */
3108
- reset() {
3109
- this.starts.length = 0;
3110
- }
3111
- trim() {
3112
- const now = this.now();
3113
- while (this.starts.length > 0 && now - (this.starts[0] ?? 0) >= this.windowMs) this.starts.shift();
3114
- }
3115
- };
3116
- let shared;
3117
- /** The process-wide window; a plugin reload reuses it on purpose. */
3118
- function sharedSessionWindow() {
3119
- shared ??= new CodeArtsSessionWindow();
3120
- return shared;
3121
- }
3122
3078
  // src/openrouter.ts
3123
3079
  /**
3124
3080
  * Live capability lookup from OpenRouter's public model catalog.
@@ -3499,10 +3455,10 @@ function codeartsEfforts(live) {
3499
3455
  }
3500
3456
  /** Every level a CodeArts model can get: the fallback when the router is silent. */
3501
3457
  const CODEARTS_EFFORTS = codeartsEfforts();
3502
- function record$3(value) {
3458
+ function record$4(value) {
3503
3459
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3504
3460
  }
3505
- function text$2(value) {
3461
+ function text$3(value) {
3506
3462
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
3507
3463
  }
3508
3464
  function num$1(value) {
@@ -3521,7 +3477,7 @@ function unwrapEnvelope(payload) {
3521
3477
  const code = payload.code;
3522
3478
  if (typeof code !== "number") return payload;
3523
3479
  if (code !== 0) return void 0;
3524
- return record$3(payload.data);
3480
+ return record$4(payload.data);
3525
3481
  }
3526
3482
  /** `deepseek-v4-flash-0731` → `deepseek-v4-flash`; chat only knows the latter. */
3527
3483
  function normalizeCodeArtsModelId(id) {
@@ -3537,21 +3493,21 @@ function normalizeCodeArtsModelId(id) {
3537
3493
  * how the upstream behaves when a model has no slots at all.
3538
3494
  */
3539
3495
  function pickActiveCredit(credit, slots, nowMs = Date.now()) {
3540
- const entries = credit.map((entry) => record$3(entry)).filter((entry) => entry !== void 0);
3496
+ const entries = credit.map((entry) => record$4(entry)).filter((entry) => entry !== void 0);
3541
3497
  if (entries.length === 0) return void 0;
3542
- const table = record$3(slots);
3498
+ const table = record$4(slots);
3543
3499
  if (table === void 0) return entries[0];
3544
3500
  for (const entry of entries) {
3545
- const slotId = text$2(entry.time_slot_id);
3501
+ const slotId = text$3(entry.time_slot_id);
3546
3502
  if (slotId === void 0) continue;
3547
- const slot = record$3(table[slotId]);
3503
+ const slot = record$4(table[slotId]);
3548
3504
  if (slot === void 0) continue;
3549
3505
  if (slotActive(slot, nowMs)) return entry;
3550
3506
  }
3551
3507
  return entries[0];
3552
3508
  }
3553
3509
  function slotActive(slot, nowMs) {
3554
- const schedule = text$2(slot.schedule);
3510
+ const schedule = text$3(slot.schedule);
3555
3511
  if (schedule === void 0) return false;
3556
3512
  const parts = schedule.split(/\s+/);
3557
3513
  if (parts.length < 3) return false;
@@ -3570,18 +3526,18 @@ function parseCodeArtsRoster(items, nowMs = Date.now()) {
3570
3526
  const seen = /* @__PURE__ */ new Set();
3571
3527
  const out = [];
3572
3528
  for (const item of items) {
3573
- const entry = record$3(item);
3529
+ const entry = record$4(item);
3574
3530
  if (entry === void 0) continue;
3575
- const rawId = text$2(entry.model_id);
3531
+ const rawId = text$3(entry.model_id);
3576
3532
  if (rawId === void 0) continue;
3577
3533
  const id = normalizeCodeArtsModelId(rawId);
3578
3534
  const key = id.toLowerCase();
3579
3535
  if (seen.has(key)) continue;
3580
3536
  seen.add(key);
3581
- const rateLabel = text$2(pickActiveCredit(Array.isArray(entry.credit) ? entry.credit : [], entry.time_slots, nowMs)?.ratio_display);
3537
+ const rateLabel = text$3(pickActiveCredit(Array.isArray(entry.credit) ? entry.credit : [], entry.time_slots, nowMs)?.ratio_display);
3582
3538
  out.push({
3583
3539
  id,
3584
- name: text$2(entry.model_name) ?? id,
3540
+ name: text$3(entry.model_name) ?? id,
3585
3541
  ...rateLabel === void 0 ? {} : { rateLabel },
3586
3542
  ...entry.supports_images === true ? { images: true } : {}
3587
3543
  });
@@ -3644,18 +3600,20 @@ const sleep$1 = (ms, signal) => new Promise((resolve, reject) => {
3644
3600
  }, ms);
3645
3601
  signal?.addEventListener("abort", onAbort, { once: true });
3646
3602
  });
3647
- /** Retries for a gateway that is busy for reasons the window cannot predict. */
3603
+ /** Retries for a gateway that is busy for reasons the caller cannot predict. */
3648
3604
  const QUEUE_ATTEMPTS = 3;
3649
- /** Minimum pause before retrying a rejected call; the window adds its own. */
3605
+ /** Pause before retrying a rejected call. */
3650
3606
  const QUEUE_BACKOFF_MS = 2e4;
3651
3607
  /**
3652
3608
  * The message a user sees once waiting is hopeless.
3653
3609
  *
3654
- * The raw gateway text (`TM.00001041`) reads like a bug; the limit is the
3655
- * trial plan's, and the only fix on this side is to say so.
3610
+ * The raw gateway text (`TM.00001041`) reads like a bug. The three concurrent
3611
+ * sessions are the trial plan's, and one plugin chatting under a single session
3612
+ * id is not meant to reach them: the slots are being held by something else on
3613
+ * the same account.
3656
3614
  */
3657
3615
  function queueExhausted() {
3658
- return new ProviderHttpError("CodeArts 体验版每分钟只允许 3 个会话,当前已被占满。请稍等片刻再发,或关闭其它 CodeArts 会话。", 429);
3616
+ return new ProviderHttpError("CodeArts 这个账号的 3 个并发会话已被占满(通常是码道 IDE 或另一台设备),请稍后再发,或关闭其它 CodeArts 会话。", 429);
3659
3617
  }
3660
3618
  /** Image transcripts live beside accounts.json, so a restart does not re-bill. */
3661
3619
  const IMAGE_CACHE_FILE = "codearts-image-cache.json";
@@ -3688,7 +3646,6 @@ var CodeArtsProvider = class {
3688
3646
  /** Models that answered `InferHub.4004.200`: no allowance pack to bill. */
3689
3647
  noBenefit = /* @__PURE__ */ new Set();
3690
3648
  noBenefitLoaded = false;
3691
- window;
3692
3649
  openRouter;
3693
3650
  sessionId = randomUUID();
3694
3651
  /**
@@ -3703,7 +3660,6 @@ var CodeArtsProvider = class {
3703
3660
  imageCacheWrite = Promise.resolve();
3704
3661
  constructor(options) {
3705
3662
  this.options = options;
3706
- this.window = options.window ?? sharedSessionWindow();
3707
3663
  this.openRouter = options.openRouter ?? new OpenRouterCatalog(options.fetchImpl ?? fetch);
3708
3664
  this.catalog = new ModelCatalog(() => this.loadModels(), options.ttlMs);
3709
3665
  this.login = new CodeArtsLogin({
@@ -3712,7 +3668,6 @@ var CodeArtsProvider = class {
3712
3668
  ...options.logger === void 0 ? {} : { logger: options.logger },
3713
3669
  save: async (account) => {
3714
3670
  await options.store.saveCodearts(account);
3715
- this.window.reset();
3716
3671
  this.noBenefit.clear();
3717
3672
  this.invalidateModels();
3718
3673
  this.invalidateQuota();
@@ -3762,8 +3717,8 @@ var CodeArtsProvider = class {
3762
3717
  const now = Date.now();
3763
3718
  const gateway = await this.signedGet(credential, CODEARTS_GATEWAY_CONFIG_URL);
3764
3719
  const builtin = await this.signedGet(credential, `${CODEARTS_SNAP_BASE}${CODEARTS_MODEL_BUILTIN_PATH}`);
3765
- const gatewayRows = parseCodeArtsRoster(record$3(record$3(gateway)?.result)?.models, now);
3766
- const builtinRows = parseCodeArtsRoster(record$3(builtin)?.builtinModels, now);
3720
+ const gatewayRows = parseCodeArtsRoster(record$4(record$4(gateway)?.result)?.models, now);
3721
+ const builtinRows = parseCodeArtsRoster(record$4(builtin)?.builtinModels, now);
3767
3722
  if (gatewayRows.length === 0 && builtinRows.length === 0) throw new Error("CodeArts 没有返回任何模型(签名、凭据或网关异常)");
3768
3723
  if (gatewayRows.length > 0) this.benefitModels = new Set(gatewayRows.map((row) => row.id));
3769
3724
  const textOnly = this.options.textOnly();
@@ -3844,7 +3799,7 @@ var CodeArtsProvider = class {
3844
3799
  }
3845
3800
  async readQuota() {
3846
3801
  const credential = await this.credential();
3847
- const payload = record$3(await this.signedGet(credential, `${CODEARTS_SNAP_BASE}${CODEARTS_PACKAGE_INFO_PATH}`));
3802
+ const payload = record$4(await this.signedGet(credential, `${CODEARTS_SNAP_BASE}${CODEARTS_PACKAGE_INFO_PATH}`));
3848
3803
  if (payload === void 0) throw new Error("CodeArts 没有返回账户信息");
3849
3804
  const info = parseCodeArtsPackage(payload);
3850
3805
  const total = info.credit.amount ?? info.credit.remaining;
@@ -3936,26 +3891,26 @@ var CodeArtsProvider = class {
3936
3891
  signal: AbortSignal.timeout(this.options.timeoutMs())
3937
3892
  });
3938
3893
  if (!response.ok) return void 0;
3939
- return unwrapEnvelope(record$3(await response.json()));
3894
+ return unwrapEnvelope(record$4(await response.json()));
3940
3895
  } catch {
3941
3896
  return;
3942
3897
  }
3943
3898
  }
3944
3899
  async accountInfo(credential) {
3945
- const payload = record$3(await this.signedGet(credential, `${CODEARTS_SNAP_BASE}${CODEARTS_PACKAGE_INFO_PATH}`));
3900
+ const payload = record$4(await this.signedGet(credential, `${CODEARTS_SNAP_BASE}${CODEARTS_PACKAGE_INFO_PATH}`));
3946
3901
  return payload === void 0 ? void 0 : parseCodeArtsPackage(payload);
3947
3902
  }
3948
3903
  async activities(credential) {
3949
3904
  const url = `${CODEARTS_SNAP_BASE}${CODEARTS_OPS_DELIVERY_PATH}?channel=IDE`;
3950
- const items = unwrapEnvelope(record$3(await this.signedGet(credential, url)))?.items;
3905
+ const items = unwrapEnvelope(record$4(await this.signedGet(credential, url)))?.items;
3951
3906
  if (!Array.isArray(items)) return void 0;
3952
3907
  return items.map((item) => {
3953
- const entry = record$3(item) ?? {};
3908
+ const entry = record$4(item) ?? {};
3954
3909
  return {
3955
3910
  campaignId: String(entry.campaignId ?? ""),
3956
- type: text$2(entry.type) ?? "",
3911
+ type: text$3(entry.type) ?? "",
3957
3912
  claimable: entry.claimable === true || String(entry.claimable).toLowerCase() === "true",
3958
- status: text$2(entry.status) ?? "",
3913
+ status: text$3(entry.status) ?? "",
3959
3914
  amount: num$1(entry.benefitAmount) ?? num$1(entry.amount) ?? num$1(entry.creditAmount) ?? 0
3960
3915
  };
3961
3916
  });
@@ -3971,7 +3926,7 @@ var CodeArtsProvider = class {
3971
3926
  const cache = /* @__PURE__ */ new Map();
3972
3927
  const file = this.imageCacheFile();
3973
3928
  if (file !== void 0) try {
3974
- const entries = record$3(record$3(JSON.parse(await promises.readFile(file, "utf8")))?.entries);
3929
+ const entries = record$4(record$4(JSON.parse(await promises.readFile(file, "utf8")))?.entries);
3975
3930
  for (const [key, value] of Object.entries(entries ?? {})) if (typeof value === "string" && value.length > 0) cache.set(key, value);
3976
3931
  } catch {}
3977
3932
  this.imageCache = cache;
@@ -4024,7 +3979,7 @@ var CodeArtsProvider = class {
4024
3979
  for (let index = 0; index < parts.length; index++) {
4025
3980
  const part = parts[index];
4026
3981
  if (part.type !== "image_url") continue;
4027
- const url = record$3(part.image_url)?.url;
3982
+ const url = record$4(part.image_url)?.url;
4028
3983
  if (typeof url !== "string" || url.length === 0) continue;
4029
3984
  const key = sha256Hex(url);
4030
3985
  let transcript = cache.get(key);
@@ -4041,7 +3996,7 @@ var CodeArtsProvider = class {
4041
3996
  }
4042
3997
  }
4043
3998
  }
4044
- /** Ask the vision model to describe one image, within the same chat budget. */
3999
+ /** Ask the vision model to describe one image; another turn on the same session. */
4045
4000
  async describeImage(dataUrl, signal) {
4046
4001
  const payload = JSON.stringify({
4047
4002
  model: CODEARTS_VISION_MODEL,
@@ -4062,7 +4017,6 @@ var CodeArtsProvider = class {
4062
4017
  let authRefreshed = false;
4063
4018
  let queueAttempts = 0;
4064
4019
  for (;;) {
4065
- const slot = await this.window.open(signal);
4066
4020
  let response;
4067
4021
  try {
4068
4022
  response = await this.send(chatId, payload, CODEARTS_VISION_MODEL, false, signal);
@@ -4073,10 +4027,9 @@ var CodeArtsProvider = class {
4073
4027
  continue;
4074
4028
  }
4075
4029
  if (error instanceof CodeArtsRetry && error.kind === "queue") {
4076
- slot.release();
4077
4030
  if (queueAttempts < QUEUE_ATTEMPTS) {
4078
4031
  queueAttempts++;
4079
- await sleep$1(Math.max(this.window.waitMs(), QUEUE_BACKOFF_MS), signal);
4032
+ await sleep$1(QUEUE_BACKOFF_MS, signal);
4080
4033
  continue;
4081
4034
  }
4082
4035
  throw queueExhausted();
@@ -4088,19 +4041,18 @@ var CodeArtsProvider = class {
4088
4041
  for await (const chunk of consumeOpenAiStream(response, {
4089
4042
  ...signal === void 0 ? {} : { signal },
4090
4043
  onFrame: (frame) => {
4091
- const code = text$2(frame.error_code);
4044
+ const code = text$3(frame.error_code);
4092
4045
  if (code === void 0) return;
4093
- const message = text$2(frame.error_msg) ?? code;
4046
+ const message = text$3(frame.error_msg) ?? code;
4094
4047
  if (QUEUE_MARKERS.some((marker) => code.includes(marker))) throw new CodeArtsRetry("queue", message);
4095
4048
  throw new ProviderHttpError(message, 502);
4096
4049
  }
4097
4050
  })) if (chunk.type === "text-delta") transcript += chunk.text;
4098
4051
  } catch (error) {
4099
4052
  if (error instanceof CodeArtsRetry && error.kind === "queue") {
4100
- slot.release();
4101
4053
  if (queueAttempts < QUEUE_ATTEMPTS) {
4102
4054
  queueAttempts++;
4103
- await sleep$1(Math.max(this.window.waitMs(), QUEUE_BACKOFF_MS), signal);
4055
+ await sleep$1(QUEUE_BACKOFF_MS, signal);
4104
4056
  continue;
4105
4057
  }
4106
4058
  throw queueExhausted();
@@ -4139,7 +4091,6 @@ var CodeArtsProvider = class {
4139
4091
  let benefitDropped = false;
4140
4092
  let queueAttempts = 0;
4141
4093
  for (;;) {
4142
- const slot = await this.window.open(options.signal);
4143
4094
  let response;
4144
4095
  try {
4145
4096
  response = await this.send(chatId, payload, model, benefitDropped, options.signal);
@@ -4162,8 +4113,7 @@ var CodeArtsProvider = class {
4162
4113
  }
4163
4114
  if (error.kind === "queue" && queueAttempts < QUEUE_ATTEMPTS) {
4164
4115
  queueAttempts++;
4165
- slot.release();
4166
- await sleep$1(Math.max(this.window.waitMs(), QUEUE_BACKOFF_MS), options.signal);
4116
+ await sleep$1(QUEUE_BACKOFF_MS, options.signal);
4167
4117
  continue;
4168
4118
  }
4169
4119
  if (error.kind === "queue") throw queueExhausted();
@@ -4175,9 +4125,9 @@ var CodeArtsProvider = class {
4175
4125
  for await (const chunk of consumeOpenAiStream(response, {
4176
4126
  ...options.signal === void 0 ? {} : { signal: options.signal },
4177
4127
  onFrame: (frame) => {
4178
- const code = text$2(frame.error_code);
4128
+ const code = text$3(frame.error_code);
4179
4129
  if (code === void 0) return;
4180
- const message = text$2(frame.error_msg) ?? code;
4130
+ const message = text$3(frame.error_msg) ?? code;
4181
4131
  if (QUEUE_MARKERS.some((marker) => code.includes(marker))) throw new CodeArtsRetry("queue", message);
4182
4132
  if (code.includes("InferHub.4004.200")) throw new CodeArtsRetry("benefit", message);
4183
4133
  throw new ProviderHttpError(message, 502);
@@ -4190,8 +4140,7 @@ var CodeArtsProvider = class {
4190
4140
  } catch (error) {
4191
4141
  if (!produced && error instanceof CodeArtsRetry && error.kind === "queue" && queueAttempts < QUEUE_ATTEMPTS) {
4192
4142
  queueAttempts++;
4193
- slot.release();
4194
- await sleep$1(Math.max(this.window.waitMs(), QUEUE_BACKOFF_MS), options.signal);
4143
+ await sleep$1(QUEUE_BACKOFF_MS, options.signal);
4195
4144
  continue;
4196
4145
  }
4197
4146
  if (error instanceof CodeArtsRetry && error.kind === "queue") throw queueExhausted();
@@ -4211,13 +4160,17 @@ var CodeArtsProvider = class {
4211
4160
  async send(chatId, payload, model, benefitDropped, signal) {
4212
4161
  const credential = await this.credential();
4213
4162
  const benefit = !benefitDropped && this.isBenefitModel(model);
4163
+ const otSessionId = credential.otSessionId ?? this.sessionId.replace(/-/g, "");
4214
4164
  const headers = {
4215
4165
  ...headersForFetch(await signRequestHuawei(credential.accessKeyId, credential.secretAccessKey, credential.securityToken, "POST", CODEARTS_CHAT_URL, payload, benefit ? { maas_type: "benefit" } : void 0)),
4216
4166
  "content-type": "application/json",
4217
4167
  accept: "text/event-stream",
4218
4168
  "chat-id": chatId,
4219
4169
  "session-id": this.sessionId,
4220
- lang: "en"
4170
+ lang: "en",
4171
+ "x-ot-session-id": otSessionId,
4172
+ "user-session-id": otSessionId,
4173
+ "x-ot-trace-id": randomUUID().replace(/-/g, "")
4221
4174
  };
4222
4175
  const timeout = AbortSignal.timeout(Math.max(this.options.timeoutMs(), 6e4));
4223
4176
  const response = await this.fetcher(CODEARTS_CHAT_URL, {
@@ -4247,7 +4200,6 @@ var CodeArtsProvider = class {
4247
4200
  }
4248
4201
  async logoutAccount() {
4249
4202
  await this.options.auth.logout();
4250
- this.window.reset();
4251
4203
  this.invalidateModels();
4252
4204
  this.invalidateQuota();
4253
4205
  }
@@ -4275,8 +4227,8 @@ function clampMaxTokens(value) {
4275
4227
  * for them would claim credits expire when they do not.
4276
4228
  */
4277
4229
  function parseCodeArtsPackage(payload) {
4278
- const pkg = record$3(payload.package) ?? {};
4279
- const endDate = text$2(payload.end_date);
4230
+ const pkg = record$4(payload.package) ?? {};
4231
+ const endDate = text$3(payload.end_date);
4280
4232
  const endMs = endDate === void 0 ? void 0 : Date.parse(endDate);
4281
4233
  const labels = {
4282
4234
  usageBasicPackageCredit: "基础积分包",
@@ -4286,9 +4238,9 @@ function parseCodeArtsPackage(payload) {
4286
4238
  const packs = [];
4287
4239
  let summary;
4288
4240
  for (const item of Array.isArray(payload.metrics) ? payload.metrics : []) {
4289
- const metric = record$3(item);
4241
+ const metric = record$4(item);
4290
4242
  if (metric === void 0) continue;
4291
- const name = text$2(metric.name) ?? "";
4243
+ const name = text$3(metric.name) ?? "";
4292
4244
  const amount = num$1(metric.package_credit_amount);
4293
4245
  const used = num$1(metric.package_credit_used);
4294
4246
  const remain = num$1(metric.package_credit_remain);
@@ -4321,8 +4273,8 @@ function parseCodeArtsPackage(payload) {
4321
4273
  });
4322
4274
  return {
4323
4275
  isCreditPackage: pkg.is_credit_package === true || String(pkg.is_credit_package).toLowerCase() === "true",
4324
- packageName: text$2(pkg.package_name_cn) ?? text$2(pkg.package_name_en) ?? "",
4325
- packageStatus: text$2(pkg.status) ?? "",
4276
+ packageName: text$3(pkg.package_name_cn) ?? text$3(pkg.package_name_en) ?? "",
4277
+ packageStatus: text$3(pkg.status) ?? "",
4326
4278
  credit: {
4327
4279
  ...credit,
4328
4280
  packs
@@ -6278,7 +6230,7 @@ function numeric(value) {
6278
6230
  if (typeof value === "number" && Number.isFinite(value)) return value;
6279
6231
  if (typeof value === "string" && /^-?\d+(?:\.\d+)?$/.test(value.trim())) return Number(value);
6280
6232
  }
6281
- function record$2(value) {
6233
+ function record$3(value) {
6282
6234
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
6283
6235
  }
6284
6236
  function messageOf(payload) {
@@ -6311,6 +6263,100 @@ function raccoonHeaders(token) {
6311
6263
  "x-client-version": RACCOON_CLIENT_VERSION
6312
6264
  };
6313
6265
  }
6266
+ /** Wording the gateway has used alongside that code. */
6267
+ const RATE_LIMIT_WORDS = [
6268
+ "user_model_rate_limited",
6269
+ "rate_limit",
6270
+ "频率超限"
6271
+ ];
6272
+ /**
6273
+ * Whether a rejection is the token wall rather than a generic failure.
6274
+ *
6275
+ * Matched on the status, the code and the wording, because the same condition
6276
+ * arrives as an HTTP 429 and as an error frame inside a 200 stream.
6277
+ */
6278
+ function isRaccoonRateLimited(status, text) {
6279
+ if (status === 429) return true;
6280
+ const lower = text.toLowerCase();
6281
+ return RATE_LIMIT_WORDS.some((word) => lower.includes(word.toLowerCase()));
6282
+ }
6283
+ /**
6284
+ * The wall, as an error the caller can tell apart from a generic 429.
6285
+ *
6286
+ * A subclass rather than a flag: the in-band path and the HTTP path would
6287
+ * otherwise both be caught by the same handler, and the second pass would wrap
6288
+ * the message the first pass had already rewritten.
6289
+ */
6290
+ var RaccoonRateLimitedError = class extends ProviderHttpError {
6291
+ constructor(message) {
6292
+ super(message, 429);
6293
+ this.name = "RaccoonRateLimitedError";
6294
+ }
6295
+ };
6296
+ /**
6297
+ * The message a user sees once the wall is hit.
6298
+ *
6299
+ * The raw text says "retry later" and gives no reason, which is why this reads
6300
+ * as the plugin being broken. The honest answer names the cause and the one
6301
+ * lever the user actually has on this side.
6302
+ */
6303
+ function raccoonRateLimited(detail) {
6304
+ return new RaccoonRateLimitedError(`小浣熊账号的模型吞吐已达上限(不是每分钟请求数,而是按账号的 token 速率)。一轮对话带的上下文越大越快撞到,撞到后额度按吞吐缓慢回血,上游也没有给恢复时间。稍等一会儿再发;把该模型的上下文窗口调小,同一会话能撑得更久。上游原文:${detail}`);
6305
+ }
6306
+ /** The `error` object a compatible gateway puts in an SSE frame. */
6307
+ function frameFailure(frame) {
6308
+ const error = frame.error;
6309
+ if (error === void 0) return void 0;
6310
+ if (typeof error === "string") return { message: error };
6311
+ if (error === null || typeof error !== "object") return void 0;
6312
+ const record = error;
6313
+ const code = typeof record.code === "string" ? record.code : void 0;
6314
+ const message = typeof record.message === "string" ? record.message : void 0;
6315
+ return {
6316
+ ...code === void 0 ? {} : { code },
6317
+ ...message === void 0 ? {} : { message }
6318
+ };
6319
+ }
6320
+ /**
6321
+ * One conversation's session id, stable across the turns of that conversation.
6322
+ *
6323
+ * The official client never opens a session per request — measured on its own
6324
+ * logs, 125 calls over 26 minutes all carried one `sess-0-53927d74` — while an
6325
+ * implementation that mints a fresh id per request tells the gateway that one
6326
+ * account opened hundreds of sessions within minutes.
6327
+ *
6328
+ * The id is a pure function of (account, model) rather than a random value, so a
6329
+ * restarted or hot-reloaded process continues the same conversation instead of
6330
+ * presenting a new session, and two models stay two conversations. The principal
6331
+ * itself never leaves the process — only a truncated digest of it does.
6332
+ */
6333
+ var RaccoonSessionIds = class {
6334
+ principal;
6335
+ /**
6336
+ * @param principal - a value that is stable for one account and identifies it
6337
+ * without being the token itself (the JWT's `sub`).
6338
+ */
6339
+ constructor(principal) {
6340
+ this.principal = principal;
6341
+ }
6342
+ async for(model) {
6343
+ const principal = await this.principal();
6344
+ if (principal === void 0) return void 0;
6345
+ return raccoonSessionId(principal, model);
6346
+ }
6347
+ };
6348
+ /**
6349
+ * `sess-<sequence>-<8 hex>`, the shape the official client sends.
6350
+ *
6351
+ * The sequence is the official client's process-local counter and is pinned to 0
6352
+ * here on purpose: an incrementing value would make one conversation look like a
6353
+ * different session after a restart, which is the one thing this id exists to
6354
+ * prevent. Identity lives in the digest, which is truncated on purpose — the id
6355
+ * must separate accounts without naming them on the wire.
6356
+ */
6357
+ function raccoonSessionId(principal, model) {
6358
+ return `sess-0-${createHash("sha256").update(`${principal}\0${model}`).digest("hex").slice(0, 8)}`;
6359
+ }
6314
6360
  /** Whether the gateway accepts a token harvested from the browser. */
6315
6361
  async function raccoonAccessTokenValid(token, fetchImpl = fetch) {
6316
6362
  try {
@@ -6332,7 +6378,7 @@ async function raccoonAccessTokenValid(token, fetchImpl = fetch) {
6332
6378
  return false;
6333
6379
  }
6334
6380
  }
6335
- function jwtExpiry$1(token) {
6381
+ function jwtExpiry$2(token) {
6336
6382
  const parts = token.split(".");
6337
6383
  if (parts.length < 2) return void 0;
6338
6384
  try {
@@ -6354,7 +6400,7 @@ function raccoonRefreshCandidate(tokens, accessToken) {
6354
6400
  let best;
6355
6401
  for (const token of tokens) {
6356
6402
  if (token === accessToken) continue;
6357
- const expiry = jwtExpiry$1(token);
6403
+ const expiry = jwtExpiry$2(token);
6358
6404
  if (expiry === void 0) continue;
6359
6405
  if (best === void 0 || expiry > best.expiry) best = {
6360
6406
  token,
@@ -6368,12 +6414,16 @@ var RaccoonProvider = class {
6368
6414
  catalog;
6369
6415
  quotaCache;
6370
6416
  quotaInflight;
6371
- checkinInflight;
6372
6417
  loginFlow;
6373
6418
  openRouter;
6419
+ sessionIds;
6374
6420
  constructor(options) {
6375
6421
  this.options = options;
6376
6422
  this.openRouter = options.openRouter ?? new OpenRouterCatalog(options.fetchImpl ?? fetch);
6423
+ this.sessionIds = options.sessionIds ?? new RaccoonSessionIds(async () => {
6424
+ const stored = await options.store.raccoon();
6425
+ return stored === void 0 ? void 0 : decodeJwtFacts(stored.accessToken).subject;
6426
+ });
6377
6427
  this.catalog = new ModelCatalog(() => this.fetchModels(), options.ttlMs);
6378
6428
  }
6379
6429
  async status() {
@@ -6427,6 +6477,21 @@ var RaccoonProvider = class {
6427
6477
  headers(token) {
6428
6478
  return raccoonHeaders(token);
6429
6479
  }
6480
+ /**
6481
+ * The chat headers, including the session id the gateway counts sessions by.
6482
+ *
6483
+ * Only chat carries it: the catalogue, quota and check-in routes are plain
6484
+ * business calls the official client sends outside any session, so leaving
6485
+ * them alone keeps this change to the one route it was measured on.
6486
+ */
6487
+ async chatHeaders(token, model) {
6488
+ const base = raccoonHeaders(token);
6489
+ const sessionId = await this.sessionIds.for(model).catch(() => void 0);
6490
+ return sessionId === void 0 ? base : {
6491
+ ...base,
6492
+ "x-raccoon-session-id": sessionId
6493
+ };
6494
+ }
6430
6495
  /** Run one JSON call; a rejected token means this login needs a new scan. */
6431
6496
  async authorizedJson(path, init = {}, extraHeaders = {}) {
6432
6497
  const token = await this.options.auth.accessToken();
@@ -6517,13 +6582,13 @@ var RaccoonProvider = class {
6517
6582
  */
6518
6583
  async dailyGrant() {
6519
6584
  const payload = await this.authorizedJson("/points/v2/bills?paging.limit=50&type=all");
6520
- const root = record$2(payload) ?? {};
6585
+ const root = record$3(payload) ?? {};
6521
6586
  if (typeof root.code === "number" && root.code !== 0) throw new Error(messageOf(payload) ?? `小浣熊积分流水返回 code=${root.code}`);
6522
- const data = record$2(root.data) ?? root;
6587
+ const data = record$3(root.data) ?? root;
6523
6588
  const items = Array.isArray(data.items) ? data.items : [];
6524
6589
  const today = raccoonDay(Date.now());
6525
6590
  for (const raw of items) {
6526
- const item = record$2(raw);
6591
+ const item = record$3(raw);
6527
6592
  if (item?.biz_type !== "daily_grant") continue;
6528
6593
  const day = item.grant_at ?? item.created_at;
6529
6594
  if (typeof day !== "string" && typeof day !== "number") continue;
@@ -6542,6 +6607,15 @@ var RaccoonProvider = class {
6542
6607
  };
6543
6608
  }
6544
6609
  }
6610
+ /**
6611
+ * Read today's grant. **Read-only on purpose.**
6612
+ *
6613
+ * The gateway has no claim endpoint: points are issued on the day's first
6614
+ * website visit. Replaying that visit (`setting_info`) can trigger the grant
6615
+ * from here, but doing so takes a state-changing action on the account without
6616
+ * the user asking for it in that moment, so the plugin only reports. Claiming
6617
+ * is the website's job, and the card links there.
6618
+ */
6545
6619
  async checkinStatus() {
6546
6620
  const grant = await this.dailyGrant();
6547
6621
  if (grant !== void 0) return {
@@ -6561,46 +6635,6 @@ var RaccoonProvider = class {
6561
6635
  url: RACCOON_CHECKIN_URL
6562
6636
  };
6563
6637
  }
6564
- async claimCheckin() {
6565
- await this.authorizedJson("/office/v3/setting_info", {}, {
6566
- "x-client-platform": "web",
6567
- "x-client-version": "131.0.0.0"
6568
- });
6569
- const grant = await this.dailyGrant();
6570
- if (grant === void 0) return {
6571
- available: true,
6572
- claimed: false,
6573
- deviceClaimed: false,
6574
- message: "每日积分尚未到账;请打开网页签到",
6575
- url: RACCOON_CHECKIN_URL
6576
- };
6577
- this.invalidateQuota();
6578
- return {
6579
- available: true,
6580
- claimed: true,
6581
- deviceClaimed: false,
6582
- ...grant.points === void 0 ? {} : { reward: grant.points },
6583
- ...grant.grantedAt === void 0 ? {} : { grantedAt: grant.grantedAt },
6584
- message: "每日积分已到账",
6585
- url: RACCOON_CHECKIN_URL
6586
- };
6587
- }
6588
- /**
6589
- * One automatic check-in pass: report today's grant, or ask the website path
6590
- * to issue it. Shared so overlapping timers cannot spend two call slots.
6591
- */
6592
- async ensureDailyCheckin() {
6593
- if (this.checkinInflight !== void 0) return this.checkinInflight;
6594
- this.checkinInflight = (async () => {
6595
- const status = await this.checkinStatus();
6596
- return status.claimed ? status : this.claimCheckin();
6597
- })();
6598
- try {
6599
- return await this.checkinInflight;
6600
- } finally {
6601
- this.checkinInflight = void 0;
6602
- }
6603
- }
6604
6638
  async *stream(options, model, readImage, onUnauthorized) {
6605
6639
  const token = await this.options.auth.accessToken();
6606
6640
  const body = buildBody(options, model, await buildMessages(options, readImage), buildTools(options));
@@ -6617,7 +6651,7 @@ var RaccoonProvider = class {
6617
6651
  try {
6618
6652
  yield* openAiStream({
6619
6653
  url: `${RACCOON_BASE}/llm/v2/chat/completions`,
6620
- headers: this.headers(token),
6654
+ headers: await this.chatHeaders(token, model),
6621
6655
  body,
6622
6656
  signal: options.signal,
6623
6657
  fetchImpl: this.options.fetchImpl,
@@ -6625,27 +6659,268 @@ var RaccoonProvider = class {
6625
6659
  this.invalidateModels();
6626
6660
  this.invalidateQuota();
6627
6661
  onUnauthorized?.();
6662
+ },
6663
+ onFrame: (frame) => {
6664
+ const failure = frameFailure(frame);
6665
+ if (failure === void 0) return;
6666
+ const detail = failure.message ?? failure.code ?? "上游返回错误";
6667
+ throw isRaccoonRateLimited(void 0, `${failure.code ?? ""} ${detail}`) ? raccoonRateLimited(detail) : new ProviderHttpError(detail, 502);
6628
6668
  }
6629
6669
  });
6630
6670
  } catch (error) {
6631
6671
  if (error instanceof ProviderHttpError && (error.status === 401 || error.status === 403)) throw new Error("小浣熊登录已失效,请在设置页点「打开扫码登录」。");
6672
+ if (error instanceof ProviderHttpError && !(error instanceof RaccoonRateLimitedError) && error.status === 429) throw raccoonRateLimited(error.message);
6632
6673
  throw error;
6633
6674
  }
6634
6675
  }
6635
6676
  };
6677
+ // src/trae-client.ts
6678
+ /**
6679
+ * The local Trae desktop client's sign-in state.
6680
+ *
6681
+ * Trae's check-in route insists on the device id its own client registered, so
6682
+ * the website login cannot use it. When the client is installed and already
6683
+ * signed in, the plugin can reuse that state instead:
6684
+ *
6685
+ * - the `Cloud-IDE-JWT` stored under `iCubeAuthInfo://icube.cloudide`;
6686
+ * - the 16-digit device id encoded in the `iCubeAuthInfo://icube-dc:<id>` key;
6687
+ * - `telemetry.machineId`, the value the client sends as `x-machine-id`.
6688
+ *
6689
+ * The stored value uses the client's own AES-CBC envelope: six magic bytes, a
6690
+ * 32-byte key material, then the ciphertext. The AES key and IV are derived
6691
+ * from that key material with SHA-512 and a fixed salt, and the plaintext is a
6692
+ * SHA-512 digest followed by the JSON document. Nothing here writes to the
6693
+ * client, and the token is never returned by the settings API.
6694
+ *
6695
+ * @module dsh-llm-adapters/trae-client
6696
+ */
6697
+ const AUTH_PROVIDER_KEY = "iCubeAuthInfo://icube.cloudide";
6698
+ const DEVICE_KEY_PREFIX = "iCubeAuthInfo://icube-dc:";
6699
+ const MACHINE_KEY = "telemetry.machineId";
6700
+ const SERVICE_MACHINE_KEY = "storage.serviceMachineId";
6701
+ const AES_MAGIC = Buffer.from([
6702
+ 116,
6703
+ 99,
6704
+ 5,
6705
+ 16,
6706
+ 0,
6707
+ 0
6708
+ ]);
6709
+ const AES_PRIVATE_MAGIC = Buffer.from([
6710
+ 18,
6711
+ 57,
6712
+ 32,
6713
+ 32,
6714
+ 2,
6715
+ 3
6716
+ ]);
6717
+ const KEY_OFFSET = 6;
6718
+ const HASH_LENGTH = 64;
6719
+ /** Aue ^ Due from the client's byteCrypto module. */
6720
+ const AES_SALT = Buffer.from("4dd4c2e6b83162090e52b3c7a6733ba41cb2462b829ab58a196b39db57177524f49baf7f08e8d68d26a72e37c1a95a2f1f05a51892aef2949732b62a38aadd58", "hex");
6721
+ /** Tue ^ Iue, the AES_PRIVATE variant of the same envelope. */
6722
+ const AES_PRIVATE_SALT = Buffer.from("490cc21292b05d0cc06ccbe91fb92e21b5a7c538811dc85e27a96e28104e1cf69c7166efab232807390da78a8b91e49c44b49a43feb941a1e46f2656e613e47b", "hex");
6723
+ const DEVICE_RE = /^\d{16}$/;
6724
+ function record$2(value) {
6725
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
6726
+ }
6727
+ function text$2(value) {
6728
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
6729
+ }
6730
+ /** Accept epoch seconds, epoch milliseconds, or an ISO date. */
6731
+ function epochMs$2(value) {
6732
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) return value < 0xe8d4a51000 ? Math.round(value * 1e3) : Math.round(value);
6733
+ if (typeof value === "string") {
6734
+ if (/^\d+$/.test(value.trim())) return epochMs$2(Number(value));
6735
+ const parsed = Date.parse(value);
6736
+ return Number.isFinite(parsed) ? parsed : void 0;
6737
+ }
6738
+ }
6739
+ function jwtExpiry$1(token) {
6740
+ const parts = token.split(".");
6741
+ if (parts.length < 2) return void 0;
6742
+ try {
6743
+ const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
6744
+ const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
6745
+ return epochMs$2(record$2(JSON.parse(Buffer.from(padded, "base64").toString("utf8")))?.exp);
6746
+ } catch {
6747
+ return;
6748
+ }
6749
+ }
6750
+ /**
6751
+ * Open one `iCubeAuthInfo` value.
6752
+ *
6753
+ * The digest check is the client's own integrity check: the first 64 plaintext
6754
+ * bytes are SHA-512 of the document that follows. A malformed or changed
6755
+ * envelope is rejected instead of being parsed as garbage.
6756
+ */
6757
+ function decryptAuthValue(value) {
6758
+ const bytes = Buffer.from(value, "base64");
6759
+ if (bytes.length < 102) throw new Error("数据长度不足");
6760
+ const magic = bytes.subarray(0, KEY_OFFSET);
6761
+ const salt = magic.equals(AES_MAGIC) ? AES_SALT : magic.equals(AES_PRIVATE_MAGIC) ? AES_PRIVATE_SALT : void 0;
6762
+ if (salt === void 0) throw new Error("未知的加密格式");
6763
+ const keyMaterial = bytes.subarray(KEY_OFFSET, 38);
6764
+ const mixed = createHash("sha512").update(keyMaterial).digest();
6765
+ const derived = createHash("sha512").update(Buffer.concat([mixed, salt])).digest();
6766
+ const decipher = createDecipheriv("aes-128-cbc", derived.subarray(0, 16), derived.subarray(16, 32));
6767
+ const plain = Buffer.concat([decipher.update(bytes.subarray(38)), decipher.final()]);
6768
+ if (plain.length < HASH_LENGTH) throw new Error("解密结果为空");
6769
+ const body = plain.subarray(HASH_LENGTH);
6770
+ if (!createHash("sha512").update(body).digest().equals(plain.subarray(0, HASH_LENGTH))) throw new Error("校验失败");
6771
+ return JSON.parse(body.toString("utf8"));
6772
+ }
6773
+ /**
6774
+ * The client's data directory, most specific first.
6775
+ *
6776
+ * `DSH_TRAE_CLIENT_DIR` is a portable/test override; the region-specific
6777
+ * variables let one machine keep separate CN and international installs.
6778
+ */
6779
+ function traeClientDataDirs(region, options = {}) {
6780
+ const platform = options.platform ?? process.platform;
6781
+ const env = options.env ?? process.env;
6782
+ const home = options.home ?? homedir();
6783
+ const name = region === "cn" ? "Trae CN" : "Trae";
6784
+ const out = [];
6785
+ const add = (directory) => {
6786
+ if (directory !== void 0 && directory !== "") out.push(directory);
6787
+ };
6788
+ add(env[region === "cn" ? "DSH_TRAE_CN_CLIENT_DIR" : "DSH_TRAE_GLOBAL_CLIENT_DIR"]);
6789
+ add(env["DSH_TRAE_CLIENT_DIR"]);
6790
+ if (platform === "win32") {
6791
+ const appData = env["APPDATA"] ?? join(home, "AppData", "Roaming");
6792
+ add(join(appData, name));
6793
+ } else if (platform === "darwin") add(join(home, "Library", "Application Support", name));
6794
+ else {
6795
+ const config = env["XDG_CONFIG_HOME"] ?? join(home, ".config");
6796
+ add(join(config, name));
6797
+ }
6798
+ return [...new Set(out)];
6799
+ }
6800
+ function storageFile(userDataDir) {
6801
+ return join(userDataDir, "User", "globalStorage", "storage.json");
6802
+ }
6803
+ function authValueOf(storage) {
6804
+ const direct = text$2(storage[AUTH_PROVIDER_KEY]);
6805
+ if (direct !== void 0) return direct;
6806
+ for (const key of Object.keys(storage)) if (key.startsWith("iCubeAuthInfo://") && key.endsWith(".cloudide")) {
6807
+ const value = text$2(storage[key]);
6808
+ if (value !== void 0) return value;
6809
+ }
6810
+ }
6811
+ /** The client has one device id per machine; the last valid key is current. */
6812
+ function deviceIdOf(storage) {
6813
+ const keys = Object.keys(storage).filter((key) => key.startsWith(DEVICE_KEY_PREFIX));
6814
+ for (let index = keys.length - 1; index >= 0; index--) {
6815
+ const value = text$2(keys[index].slice(25));
6816
+ if (value !== void 0 && DEVICE_RE.test(value)) return value;
6817
+ }
6818
+ }
6819
+ function regionMismatch(region, userInfo) {
6820
+ const account = record$2(userInfo.account);
6821
+ const evidence = (text$2(record$2(userInfo.userRegion)?.region) ?? text$2(account?.storeRegion))?.toUpperCase();
6822
+ if (evidence === void 0) return false;
6823
+ return region === "cn" ? evidence !== "CN" : evidence === "CN";
6824
+ }
6825
+ /** Parse one `storage.json` document; exported for tests and diagnostics. */
6826
+ function parseTraeClientStorage(region, storage) {
6827
+ const encrypted = authValueOf(storage);
6828
+ if (encrypted === void 0) return { status: {
6829
+ available: true,
6830
+ signedIn: false,
6831
+ error: "本机 Trae 客户端尚未登录。请先在客户端完成登录。"
6832
+ } };
6833
+ let parsed;
6834
+ try {
6835
+ parsed = decryptAuthValue(encrypted);
6836
+ } catch (error) {
6837
+ return { status: {
6838
+ available: true,
6839
+ signedIn: false,
6840
+ error: `读取本机 Trae 客户端登录态失败:${error instanceof Error ? error.message : String(error)}`
6841
+ } };
6842
+ }
6843
+ const userInfo = record$2(parsed);
6844
+ const token = text$2(userInfo?.token);
6845
+ if (userInfo === void 0 || token === void 0) return { status: {
6846
+ available: true,
6847
+ signedIn: false,
6848
+ error: "本机 Trae 客户端登录态里没有令牌,请先在客户端重新登录。"
6849
+ } };
6850
+ const account = record$2(userInfo.account);
6851
+ const name = text$2(account?.username) ?? text$2(account?.name);
6852
+ const userId = text$2(userInfo.userId);
6853
+ const expiresAt = epochMs$2(userInfo.expiredAt) ?? jwtExpiry$1(token);
6854
+ const identity = {
6855
+ ...name === void 0 ? {} : { name },
6856
+ ...userId === void 0 ? {} : { userId },
6857
+ ...expiresAt === void 0 ? {} : { expiresAt }
6858
+ };
6859
+ const unavailable = (error) => ({ status: {
6860
+ available: true,
6861
+ signedIn: false,
6862
+ ...identity,
6863
+ error
6864
+ } });
6865
+ if (regionMismatch(region, userInfo)) return unavailable(region === "cn" ? "本机 Trae 客户端登录的是国际版账号,请切换到国际版后再复用。" : "本机 Trae 客户端登录的是国内版账号,请切换到国内版后再复用。");
6866
+ if (expiresAt !== void 0 && expiresAt <= Date.now()) return unavailable("本机 Trae 客户端登录已过期,请先在客户端重新登录。");
6867
+ const deviceId = deviceIdOf(storage);
6868
+ if (deviceId === void 0) return unavailable("本机 Trae 客户端登录缺少 16 位设备号,请先在客户端重新登录。");
6869
+ const machineId = text$2(storage[MACHINE_KEY]) ?? text$2(storage[SERVICE_MACHINE_KEY]);
6870
+ if (machineId === void 0) return unavailable("本机 Trae 客户端登录缺少机器码,请先在客户端重新登录。");
6871
+ return {
6872
+ status: {
6873
+ available: true,
6874
+ signedIn: true,
6875
+ ...identity
6876
+ },
6877
+ state: {
6878
+ region,
6879
+ token,
6880
+ deviceId,
6881
+ machineId,
6882
+ ...identity
6883
+ }
6884
+ };
6885
+ }
6886
+ /** Read the currently selected region's client state from this machine. */
6887
+ function readTraeClientLogin(region, options = {}) {
6888
+ let available = false;
6889
+ let error;
6890
+ for (const directory of traeClientDataDirs(region, options)) {
6891
+ const file = storageFile(directory);
6892
+ if (!existsSync(file)) continue;
6893
+ available = true;
6894
+ try {
6895
+ const raw = readFileSync(file, "utf8").replace(/^\uFEFF/, "");
6896
+ const storage = record$2(JSON.parse(raw));
6897
+ if (storage === void 0) throw new Error("storage.json 不是对象");
6898
+ return parseTraeClientStorage(region, storage);
6899
+ } catch (cause) {
6900
+ error = `读取本机 Trae 客户端登录态失败:${cause instanceof Error ? cause.message : String(cause)}`;
6901
+ }
6902
+ }
6903
+ return { status: {
6904
+ available,
6905
+ signedIn: false,
6906
+ ...error === void 0 ? {} : { error }
6907
+ } };
6908
+ }
6636
6909
  // src/trae-login.ts
6637
6910
  /**
6638
6911
  * Trae sign-in through the provider's own website.
6639
6912
  *
6640
- * The plugin does not read a desktop install: it opens a dedicated browser
6913
+ * This flow does not read a desktop install: it opens a dedicated browser
6641
6914
  * profile, lets the user sign in on trae.cn / trae.ai, and then takes the
6642
6915
  * session the website itself created. Two things come out of that session:
6643
6916
  *
6644
6917
  * - the `Cloud-IDE-JWT` token, fetched with the page's own cookies through
6645
6918
  * `/cloudide/api/v3/common/GetUserToken` — the same call the site makes;
6646
6919
  * - the device fingerprint (`__msuuid__` from the page, or one generated in
6647
- * the page), which Trae's check-in route requires and which therefore must
6648
- * belong to this web session rather than to a machine install.
6920
+ * the page), stored with the web login.
6921
+ *
6922
+ * The check-in route does not accept this web fingerprint, so check-in is
6923
+ * enabled only for the separate client-login import in `trae-client.ts`.
6649
6924
  *
6650
6925
  * Cookies are captured over the DevTools protocol and kept in the plugin's own
6651
6926
  * account store so the short-lived JWT can be renewed later; the dedicated
@@ -7257,10 +7532,12 @@ var TraeWebLogin = class {
7257
7532
  };
7258
7533
  // src/trae.ts
7259
7534
  /**
7260
- * Trae provider: model directory, SOLO chat and quota.
7535
+ * Trae provider: model directory, SOLO chat, quota and client check-in.
7261
7536
  *
7262
7537
  * Trae's website login yields the same `Cloud-IDE-JWT` its own clients use, so
7263
- * the plugin talks to the same gateways they do. Two catalog sources are merged:
7538
+ * the plugin talks to the same gateways they do. A second sign-in method copies
7539
+ * that token from the local desktop client; only that method carries the device
7540
+ * id the daily check-in route accepts. Two catalog sources are merged:
7264
7541
  *
7265
7542
  * - the Remote directory (`/api/remote/v1/models`) is the model skeleton —
7266
7543
  * display name, context window, multiplier, reasoning options, multimodal;
@@ -7962,6 +8239,22 @@ function parseTraeGlobalUsage(payload) {
7962
8239
  at: Date.now()
7963
8240
  };
7964
8241
  }
8242
+ /**
8243
+ * The check-in body's `req_source`: 1 is the IDE, 2 is Trae Work. The desktop
8244
+ * client sends 1; the claim route rejects the plugin's former empty body.
8245
+ */
8246
+ const TRAE_CHECKIN_REQ_SOURCE = 1;
8247
+ function parseTraeCheckin(payload) {
8248
+ const root = record(payload) ?? {};
8249
+ const reward = num(root.credits) ?? 0;
8250
+ return {
8251
+ available: root.enable !== false,
8252
+ claimed: root.checked_in === true,
8253
+ deviceClaimed: root.did_checked_in === true,
8254
+ ...reward === 0 ? {} : { reward },
8255
+ ...text(root.message) === void 0 ? {} : { message: text(root.message) }
8256
+ };
8257
+ }
7965
8258
  var TraeProvider = class {
7966
8259
  options;
7967
8260
  catalogInstance;
@@ -8172,6 +8465,160 @@ var TraeProvider = class {
8172
8465
  this.quotaInflightRegion = region;
8173
8466
  return this.quotaInflight;
8174
8467
  }
8468
+ /**
8469
+ * The second sign-in method: the desktop client's current state.
8470
+ *
8471
+ * Read-only; the settings page only receives the non-secret status, never
8472
+ * the token.
8473
+ */
8474
+ async localClientLogin() {
8475
+ return this.readClientLogin().status;
8476
+ }
8477
+ readClientLogin() {
8478
+ return (this.options.clientLogin ?? (() => readTraeClientLogin(this.options.region())))();
8479
+ }
8480
+ /** Import the desktop client's token, device id and machine id. */
8481
+ async importClientLogin() {
8482
+ const region = this.ensureRegion();
8483
+ const detection = this.readClientLogin();
8484
+ if (!detection.status.available) throw new Error("本机没有检测到 Trae 客户端。请先安装并登录客户端,或改用网页登录。");
8485
+ const state = detection.state;
8486
+ if (!detection.status.signedIn || state === void 0) throw new Error(detection.status.error ?? "本机 Trae 客户端尚未登录。请先在客户端完成登录。");
8487
+ await this.loginFlow?.cancel();
8488
+ await this.options.store.saveTrae({
8489
+ accessToken: state.token,
8490
+ region,
8491
+ source: "client",
8492
+ deviceId: state.deviceId,
8493
+ machineId: state.machineId,
8494
+ loginAt: Date.now(),
8495
+ ...state.userId === void 0 ? {} : { userId: state.userId },
8496
+ ...state.name === void 0 ? {} : { name: state.name },
8497
+ ...state.expiresAt === void 0 ? {} : { expiresAt: state.expiresAt }
8498
+ });
8499
+ this.invalidateModels();
8500
+ this.invalidateQuota();
8501
+ }
8502
+ async checkinContext() {
8503
+ this.ensureRegion();
8504
+ const profile = this.profile();
8505
+ if (!profile.checkin) return {
8506
+ ok: false,
8507
+ status: {
8508
+ available: false,
8509
+ claimed: false,
8510
+ deviceClaimed: false,
8511
+ message: "Trae 国际版没有签到活动。"
8512
+ }
8513
+ };
8514
+ const auth = await this.options.auth.status();
8515
+ if (!auth.signedIn) return {
8516
+ ok: false,
8517
+ status: {
8518
+ available: false,
8519
+ claimed: false,
8520
+ deviceClaimed: false,
8521
+ message: auth.error ?? "尚未登录,无法签到。"
8522
+ }
8523
+ };
8524
+ if (auth.loginSource !== "client") return {
8525
+ ok: false,
8526
+ status: {
8527
+ available: false,
8528
+ claimed: false,
8529
+ deviceClaimed: false,
8530
+ message: "签到仅在复用本机 Trae 客户端登录时可用;网页登录没有客户端设备号。"
8531
+ }
8532
+ };
8533
+ let token;
8534
+ let identity;
8535
+ try {
8536
+ token = await this.options.auth.accessToken();
8537
+ identity = await this.options.auth.identity();
8538
+ } catch (error) {
8539
+ return {
8540
+ ok: false,
8541
+ status: {
8542
+ available: false,
8543
+ claimed: false,
8544
+ deviceClaimed: false,
8545
+ message: error instanceof Error ? error.message : String(error)
8546
+ }
8547
+ };
8548
+ }
8549
+ if (!/^\d{16}$/.test(identity.deviceId)) return {
8550
+ ok: false,
8551
+ status: {
8552
+ available: false,
8553
+ claimed: false,
8554
+ deviceClaimed: false,
8555
+ message: "本机 Trae 客户端登录缺少 16 位设备号,请先在客户端重新登录后再复用。"
8556
+ }
8557
+ };
8558
+ return {
8559
+ ok: true,
8560
+ profile,
8561
+ token,
8562
+ identity: {
8563
+ deviceId: identity.deviceId,
8564
+ machineId: identity.machineId
8565
+ }
8566
+ };
8567
+ }
8568
+ async checkinStatus() {
8569
+ const context = await this.checkinContext();
8570
+ if (!context.ok) return context.status;
8571
+ const response = await this.fetchImpl(`${context.profile.apiBase}/trae/api/v2/ug/checkin_credits/status`, {
8572
+ method: "POST",
8573
+ headers: {
8574
+ ...this.headers(context.token, context.profile),
8575
+ "x-user-region": "CN",
8576
+ "x-device-id": context.identity.deviceId,
8577
+ "x-machine-id": context.identity.machineId
8578
+ },
8579
+ body: JSON.stringify({ req_source: 1 }),
8580
+ signal: AbortSignal.timeout(this.options.timeoutMs())
8581
+ });
8582
+ if (!response.ok) throw new ProviderHttpError(await responseError(response), response.status);
8583
+ return parseTraeCheckin(await response.json());
8584
+ }
8585
+ /** Claim today's reward. The only state-changing call in this provider. */
8586
+ async claimCheckin() {
8587
+ const context = await this.checkinContext();
8588
+ if (!context.ok) throw new Error(context.status.message ?? "当前登录方式不能签到。");
8589
+ const response = await this.fetchImpl(`${context.profile.apiBase}/trae/api/v2/ug/checkin_credits/claim`, {
8590
+ method: "POST",
8591
+ headers: {
8592
+ ...this.headers(context.token, context.profile),
8593
+ "x-user-region": "CN",
8594
+ "x-device-id": context.identity.deviceId,
8595
+ "x-machine-id": context.identity.machineId
8596
+ },
8597
+ body: JSON.stringify({ req_source: 1 }),
8598
+ signal: AbortSignal.timeout(this.options.timeoutMs())
8599
+ });
8600
+ if (!response.ok) throw new ProviderHttpError(await responseError(response), response.status);
8601
+ const payload = record(await response.json()) ?? {};
8602
+ const code = typeof payload.code === "number" ? payload.code : 0;
8603
+ if (code === 0) {
8604
+ this.invalidateQuota();
8605
+ const reward = num(payload.credits);
8606
+ return {
8607
+ available: true,
8608
+ claimed: true,
8609
+ deviceClaimed: false,
8610
+ ...reward === void 0 || reward === 0 ? {} : { reward },
8611
+ ...text(payload.message) === void 0 ? {} : { message: text(payload.message) }
8612
+ };
8613
+ }
8614
+ if (code === 9095) return {
8615
+ available: true,
8616
+ claimed: false,
8617
+ deviceClaimed: true,
8618
+ message: "本机今日已签到"
8619
+ };
8620
+ throw new Error(text(payload.message) ?? `签到失败(code ${code})`);
8621
+ }
8175
8622
  async *stream(options, model, readImage, onInvalidate) {
8176
8623
  this.ensureRegion();
8177
8624
  const profile = this.profile();
@@ -8229,6 +8676,7 @@ var TraeProvider = class {
8229
8676
  await this.options.store.saveTrae({
8230
8677
  accessToken: account.accessToken,
8231
8678
  region: target,
8679
+ source: "web",
8232
8680
  deviceId: account.deviceId,
8233
8681
  machineId: account.machineId,
8234
8682
  loginAt: Date.now(),
@@ -8428,23 +8876,6 @@ function apply(ctx, config) {
8428
8876
  syncTimer.unref?.();
8429
8877
  syncOpenRouter();
8430
8878
  ctx.effect(() => () => clearInterval(syncTimer), "llm-adapters: openrouter sync");
8431
- let lastRaccoonCheckin = Date.now();
8432
- const checkinMonitor = () => {
8433
- if (!live.raccoonEnabled()) return;
8434
- const minutes = live.raccoonCheckinMinutes();
8435
- if (minutes <= 0) return;
8436
- if (Date.now() - lastRaccoonCheckin < minutes * 6e4) return;
8437
- lastRaccoonCheckin = Date.now();
8438
- (async () => {
8439
- if (!await raccoonProvider.signedIn()) return;
8440
- await raccoonProvider.ensureDailyCheckin();
8441
- })().catch((error) => {
8442
- ctx.logger?.warn(`llm-adapters: 小浣熊签到监测失败:${error instanceof Error ? error.message : String(error)}`);
8443
- });
8444
- };
8445
- const checkinTimer = setInterval(checkinMonitor, 6e4);
8446
- checkinTimer.unref?.();
8447
- ctx.effect(() => () => clearInterval(checkinTimer), "llm-adapters: raccoon check-in monitor");
8448
8879
  ctx.inject(["attachments"], (scope) => {
8449
8880
  attachments = scope.attachments;
8450
8881
  scope.effect(() => () => {
@@ -8510,4 +8941,4 @@ function apply(ctx, config) {
8510
8941
  ctx.logger?.info(`llm-adapters: 设置接口已挂载于 ${API_PREFIX}`);
8511
8942
  });
8512
8943
  }
8513
- export { API_PREFIX, AccountStore, BRANDS, CODEARTS_BENEFIT_FALLBACK, CODEARTS_BRAND, CODEARTS_CHAT_URL, CODEARTS_CONTEXT_WINDOWS, CODEARTS_DEFAULT_EFFORT, CODEARTS_EFFORTS, CODEARTS_EFFORT_LEVELS, CODEARTS_FALLBACK_CONTEXT_WINDOW, CODEARTS_MAX_OUTPUT_TOKENS, CODEARTS_PROVIDER, CODEARTS_SESSION_LIMIT, CODEARTS_SESSION_WINDOW_MS, CODEARTS_SNAP_BASE, CODEARTS_VISION_MODELS, CodeArtsAdapter, CodeArtsAuth, CodeArtsLogin, CodeArtsProvider, CodeArtsSessionWindow, Config, EMPTY_OPENROUTER_INDEX, LOOMY_ACCOUNT_BASE, LOOMY_BASE, LOOMY_BRAND, LOOMY_PROVIDER, LOOMY_VERSION, LOOMY_WECHAT_APP_ID, LOOMY_WECHAT_REDIRECT_URI, LocalAdapter, LoomyAdapter, LoomyAuth, LoomyProvider, LoomyQrLogin, ModelCatalog, OpenRouterCatalog, ProviderHttpError, RACCOON_BASE, RACCOON_BRAND, RACCOON_CLIENT_VERSION, RACCOON_IAM_BASE, RACCOON_PROVIDER, RACCOON_QR_APP_NAME, RACCOON_QR_ORIGIN, RaccoonAdapter, RaccoonAuth, RaccoonIamError, RaccoonProvider, RaccoonQrLogin, RefreshTokenExpiredError, TRAE_APP_ID, TRAE_BRAND, TRAE_CHAT_FUNCTIONS, TRAE_DIRECTORY_FUNCTIONS, TRAE_PROVIDER, TRAE_REGION_PROFILES, TRAE_SOLO_CHAT_PATH, TRAE_WEB_USER_AGENT, TraeAdapter, TraeAuth, TraeProvider, TraeWebLogin, apply, buildAuthorizeUrl, buildLoomyWechatAuthUrl, buildMessages, buildOpenRouterIndex, buildQrMatrix, buildRows, buildTools, clampMaxTokens, codeartsContextWindow, codeartsEfforts, consumeOpenAiStream, createRaccoonQr, decodeJwtFacts, defaultEffortOf, effortLabel, exchangeAuthorizationCode, exchangeRefreshToken, extractWechatUuid, fetchLoomyWechatUuid, generateDpopKeyPair, generatePkcePair, headersForFetch, inject, isBrandId, liveConfig, loomyGatewaySessionValid, machineIdOf, mergeTraeModels, modelKey, name, normalizeCodeArtsModelId, normalizeEffort, normalizeTraeRegion, openAiStream, openTraeStream, parseCodeArtsPackage, parseCodeArtsRoster, parseLoomyCatalog, parseModelName, parseOpenRouterModels, parseRaccoonCatalog, parseTraeGlobalUsage, parseTraeRemoteModels, parseTraeUsage, parseTraeWireModels, pickActiveCredit, pollRaccoonQr, priceFactorOf, priceFactorOfLabel, raccoonAccessTokenValid, raccoonQrContent, raccoonRefreshCandidate, readJwtExpiry, readOpenRouter, renderQrSvg, resolveCapabilities, resolveConfig, resolveModelId, sharedSessionWindow, signDpopJws, signRequestHuawei, sseEvents, startLoginPage, traeMessages, traeTools };
8944
+ export { API_PREFIX, AccountStore, BRANDS, CODEARTS_BENEFIT_FALLBACK, CODEARTS_BRAND, CODEARTS_CHAT_URL, CODEARTS_CONTEXT_WINDOWS, CODEARTS_DEFAULT_EFFORT, CODEARTS_EFFORTS, CODEARTS_EFFORT_LEVELS, CODEARTS_FALLBACK_CONTEXT_WINDOW, CODEARTS_MAX_OUTPUT_TOKENS, CODEARTS_PROVIDER, CODEARTS_SNAP_BASE, CODEARTS_VISION_MODELS, CodeArtsAdapter, CodeArtsAuth, CodeArtsLogin, CodeArtsProvider, Config, EMPTY_OPENROUTER_INDEX, LOOMY_ACCOUNT_BASE, LOOMY_BASE, LOOMY_BRAND, LOOMY_PROVIDER, LOOMY_VERSION, LOOMY_WECHAT_APP_ID, LOOMY_WECHAT_REDIRECT_URI, LocalAdapter, LoomyAdapter, LoomyAuth, LoomyProvider, LoomyQrLogin, ModelCatalog, OpenRouterCatalog, ProviderHttpError, RACCOON_BASE, RACCOON_BRAND, RACCOON_CLIENT_VERSION, RACCOON_IAM_BASE, RACCOON_PROVIDER, RACCOON_QR_APP_NAME, RACCOON_QR_ORIGIN, RaccoonAdapter, RaccoonAuth, RaccoonIamError, RaccoonProvider, RaccoonQrLogin, RefreshTokenExpiredError, TRAE_APP_ID, TRAE_BRAND, TRAE_CHAT_FUNCTIONS, TRAE_CHECKIN_REQ_SOURCE, TRAE_DIRECTORY_FUNCTIONS, TRAE_PROVIDER, TRAE_REGION_PROFILES, TRAE_SOLO_CHAT_PATH, TRAE_WEB_USER_AGENT, TraeAdapter, TraeAuth, TraeProvider, TraeWebLogin, apply, buildAuthorizeUrl, buildLoomyWechatAuthUrl, buildMessages, buildOpenRouterIndex, buildQrMatrix, buildRows, buildTools, clampMaxTokens, codeartsContextWindow, codeartsEfforts, consumeOpenAiStream, createRaccoonQr, decodeJwtFacts, defaultEffortOf, effortLabel, exchangeAuthorizationCode, exchangeRefreshToken, extractWechatUuid, fetchLoomyWechatUuid, generateDpopKeyPair, generatePkcePair, headersForFetch, inject, isBrandId, liveConfig, loomyGatewaySessionValid, machineIdOf, mergeTraeModels, modelKey, name, normalizeCodeArtsModelId, normalizeEffort, normalizeTraeRegion, openAiStream, openTraeStream, otSessionIdOf, parseCodeArtsPackage, parseCodeArtsRoster, parseLoomyCatalog, parseModelName, parseOpenRouterModels, parseRaccoonCatalog, parseTraeCheckin, parseTraeClientStorage, parseTraeGlobalUsage, parseTraeRemoteModels, parseTraeUsage, parseTraeWireModels, pickActiveCredit, pollRaccoonQr, priceFactorOf, priceFactorOfLabel, raccoonAccessTokenValid, raccoonQrContent, raccoonRefreshCandidate, readJwtExpiry, readOpenRouter, readTraeClientLogin, renderQrSvg, resolveCapabilities, resolveConfig, resolveModelId, signDpopJws, signRequestHuawei, sseEvents, startLoginPage, traeClientDataDirs, traeMessages, traeTools };