dsh-llm-adapters 1.2.0 → 1.2.1

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,10 @@ 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) },
774
791
  ...noBenefit.length === 0 ? {} : { noBenefit }
775
792
  };
776
793
  }
@@ -794,30 +811,30 @@ var AccountStore = class {
794
811
  } catch {
795
812
  return {};
796
813
  }
797
- const raw = record$4(parsed);
814
+ const raw = record$5(parsed);
798
815
  if (raw === void 0) return {};
799
816
  const out = {};
800
- const loomy = record$4(raw.loomy);
801
- const loomySession = text$4(loomy?.session);
817
+ const loomy = record$5(raw.loomy);
818
+ const loomySession = text$5(loomy?.session);
802
819
  if (loomySession !== void 0) out.loomy = {
803
820
  session: loomySession,
804
- deviceId: text$4(loomy?.deviceId) ?? `web-${randomBytes(8).toString("hex")}`,
821
+ deviceId: text$5(loomy?.deviceId) ?? `web-${randomBytes(8).toString("hex")}`,
805
822
  loginAt: number(loomy?.loginAt) ?? Date.now(),
806
823
  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) }
824
+ ...text$5(loomy?.userId) === void 0 ? {} : { userId: text$5(loomy?.userId) },
825
+ ...text$5(loomy?.phone) === void 0 ? {} : { phone: text$5(loomy?.phone) },
826
+ ...text$5(loomy?.name) === void 0 ? {} : { name: text$5(loomy?.name) }
810
827
  };
811
- const raccoon = record$4(raw.raccoon);
812
- const access = text$4(raccoon?.accessToken);
828
+ const raccoon = record$5(raw.raccoon);
829
+ const access = text$5(raccoon?.accessToken);
813
830
  if (access !== void 0) out.raccoon = {
814
831
  accessToken: access,
815
832
  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) }
833
+ ...text$5(raccoon?.phone) === void 0 ? {} : { phone: text$5(raccoon?.phone) },
834
+ ...text$5(raccoon?.name) === void 0 ? {} : { name: text$5(raccoon?.name) },
835
+ ...text$5(raccoon?.ownerType) === void 0 ? {} : { ownerType: text$5(raccoon?.ownerType) }
819
836
  };
820
- const trae = record$4(raw.trae);
837
+ const trae = record$5(raw.trae);
821
838
  if (trae !== void 0) {
822
839
  const cn = traeRecord(trae.cn, "cn");
823
840
  const global = traeRecord(trae.global, "global");
@@ -1051,6 +1068,14 @@ async function handleAccount(req, res, adapters) {
1051
1068
  });
1052
1069
  return;
1053
1070
  }
1071
+ if (action === "client-login") {
1072
+ try {
1073
+ send(res, 200, await adapter.importClientLogin());
1074
+ } catch (error) {
1075
+ send(res, 400, { error: error instanceof Error ? error.message : String(error) });
1076
+ }
1077
+ return;
1078
+ }
1054
1079
  if (action === "checkin") {
1055
1080
  try {
1056
1081
  send(res, 200, {
@@ -1062,7 +1087,7 @@ async function handleAccount(req, res, adapters) {
1062
1087
  }
1063
1088
  return;
1064
1089
  }
1065
- send(res, 400, { error: "action 只接受 browser、browser-cancel、checkin 或 logout" });
1090
+ send(res, 400, { error: "action 只接受 browser、browser-cancel、client-login、checkin 或 logout" });
1066
1091
  }
1067
1092
  async function handleApi(req, res, adapters, openRouter) {
1068
1093
  try {
@@ -2080,7 +2105,8 @@ const TRAE_REGION_PROFILES = {
2080
2105
  payBase: "https://api.trae.cn",
2081
2106
  timezone: "Asia/Shanghai",
2082
2107
  language: "zh-cn",
2083
- usageV2: true
2108
+ usageV2: true,
2109
+ checkin: true
2084
2110
  },
2085
2111
  global: {
2086
2112
  id: "global",
@@ -2092,7 +2118,8 @@ const TRAE_REGION_PROFILES = {
2092
2118
  payBase: "https://growsg-normal.trae.ai",
2093
2119
  timezone: "Asia/Singapore",
2094
2120
  language: "en",
2095
- usageV2: false
2121
+ usageV2: false,
2122
+ checkin: false
2096
2123
  }
2097
2124
  };
2098
2125
  /** The app id Trae's own clients send; not account-specific. */
@@ -2298,12 +2325,13 @@ function mergeSetCookies(current, headers) {
2298
2325
  return [...jar.entries()].map(([name, value]) => `${name}=${value}`).join("; ");
2299
2326
  }
2300
2327
  /**
2301
- * Trae's web login, one per region.
2328
+ * Trae's login, one per region.
2302
2329
  *
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.
2330
+ * A web login stores the `Cloud-IDE-JWT` from the website plus the cookies
2331
+ * behind it, so the short token can be renewed silently until the website
2332
+ * session dies. A client login stores the token the desktop client already
2333
+ * uses; that token has no website session, and the client itself refreshes it.
2334
+ * Only the client source carries the device id the check-in route accepts.
2307
2335
  */
2308
2336
  var TraeAuth = class {
2309
2337
  store;
@@ -2324,18 +2352,32 @@ var TraeAuth = class {
2324
2352
  const stored = await this.store.trae(region);
2325
2353
  if (stored === void 0) return {
2326
2354
  signedIn: false,
2327
- error: `${traeLabel(region)}尚未登录。请在设置页点「打开浏览器登录」。`
2355
+ error: `${traeLabel(region)}尚未登录。可以点「打开浏览器登录」,或复用本机已登录的 Trae 客户端。`
2328
2356
  };
2357
+ const source = stored.source ?? "web";
2329
2358
  const now = Date.now();
2330
2359
  const { token, session } = traeExpiries(stored);
2331
2360
  const tokenExpired = token !== void 0 && token <= now + TRAE_EXPIRY_MARGIN_MS;
2361
+ const account = traeAccount(stored);
2362
+ if (source === "client") {
2363
+ const signedIn = !tokenExpired;
2364
+ return {
2365
+ signedIn,
2366
+ loginSource: "client",
2367
+ loginAt: stored.loginAt,
2368
+ ...token === void 0 ? {} : { expiresAt: token },
2369
+ ...!signedIn ? { possiblyExpired: true } : {},
2370
+ ...Object.keys(account).length === 0 ? {} : { account },
2371
+ ...!signedIn ? { error: `${traeLabel(region)}客户端登录令牌已过期,请先在 Trae 客户端重新登录,再点「复用客户端登录态」。` } : {}
2372
+ };
2373
+ }
2332
2374
  const sessionExpired = session !== void 0 && session <= now + TRAE_EXPIRY_MARGIN_MS;
2333
2375
  const refreshable = stored.cookies !== void 0 && !sessionExpired;
2334
2376
  const signedIn = !tokenExpired || refreshable;
2335
- const account = traeAccount(stored);
2336
2377
  const expiresAt = refreshable ? session ?? token : token;
2337
2378
  return {
2338
2379
  signedIn,
2380
+ loginSource: "web",
2339
2381
  loginAt: stored.loginAt,
2340
2382
  ...expiresAt === void 0 ? {} : { expiresAt },
2341
2383
  ...!signedIn ? { possiblyExpired: true } : {},
@@ -2346,15 +2388,21 @@ var TraeAuth = class {
2346
2388
  /**
2347
2389
  * The bearer token, or an actionable error when there is none.
2348
2390
  *
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.
2391
+ * A web token near its expiry is renewed first; `force` means the gateway
2392
+ * already rejected it, so the renewal is attempted even if the clock
2393
+ * disagrees. A client token is returned until its own expiry and then asks
2394
+ * for a fresh import, because only the client can refresh it.
2351
2395
  */
2352
2396
  async accessToken(force = false) {
2353
2397
  const region = this.region();
2354
2398
  const stored = await this.store.trae(region);
2355
- if (stored === void 0) throw new Error(`${traeLabel(region)}尚未登录。请在设置页点「打开浏览器登录」。`);
2399
+ if (stored === void 0) throw new Error(`${traeLabel(region)}尚未登录。可以点「打开浏览器登录」,或复用本机已登录的 Trae 客户端。`);
2356
2400
  const { token, session } = traeExpiries(stored);
2357
2401
  const expired = token !== void 0 && token <= Date.now() + TRAE_EXPIRY_MARGIN_MS;
2402
+ if (stored.source === "client") {
2403
+ if (!force && !expired) return stored.accessToken;
2404
+ throw new Error(`${traeLabel(region)}客户端登录令牌已${force ? "失效" : "过期"},请先在 Trae 客户端重新登录,再点「复用客户端登录态」。`);
2405
+ }
2358
2406
  if (!force && !expired) return stored.accessToken;
2359
2407
  if (stored.cookies === void 0) throw new Error(`${traeLabel(region)}登录已${force ? "失效" : "过期"},请在设置页重新登录。`);
2360
2408
  if (session !== void 0 && session <= Date.now() + TRAE_EXPIRY_MARGIN_MS) throw new Error(`${traeLabel(region)}网页会话已过期,请重新登录。`);
@@ -2366,7 +2414,7 @@ var TraeAuth = class {
2366
2414
  if (existing !== void 0) return existing;
2367
2415
  const task = (async () => {
2368
2416
  const stored = await this.store.trae(region);
2369
- if (stored === void 0 || stored.cookies === void 0) throw new Error(`${traeLabel(region)}登录已失效,请在设置页重新登录。`);
2417
+ if (stored === void 0 || stored.source === "client" || stored.cookies === void 0) throw new Error(`${traeLabel(region)}登录已失效,请在设置页重新登录。`);
2370
2418
  const profile = TRAE_REGION_PROFILES[region];
2371
2419
  let response;
2372
2420
  try {
@@ -2425,7 +2473,7 @@ var TraeAuth = class {
2425
2473
  async identity() {
2426
2474
  const region = this.region();
2427
2475
  const stored = await this.store.trae(region);
2428
- if (stored === void 0) throw new Error(`${traeLabel(region)}尚未登录。请在设置页点「打开浏览器登录」。`);
2476
+ if (stored === void 0) throw new Error(`${traeLabel(region)}尚未登录。可以点「打开浏览器登录」,或复用本机已登录的 Trae 客户端。`);
2429
2477
  return {
2430
2478
  deviceId: stored.deviceId,
2431
2479
  machineId: stored.machineId,
@@ -2668,11 +2716,11 @@ function resolveConfig(config) {
2668
2716
  raccoonCheckinMinutes: live.raccoonCheckinMinutes()
2669
2717
  };
2670
2718
  }
2671
- function text$3(value) {
2719
+ function text$4(value) {
2672
2720
  return typeof value === "string" ? value.trim() : "";
2673
2721
  }
2674
2722
  function nonEmpty(value, fallback) {
2675
- const valueText = text$3(value);
2723
+ const valueText = text$4(value);
2676
2724
  return valueText.length > 0 ? valueText : fallback;
2677
2725
  }
2678
2726
  function positive(value, fallback) {
@@ -3499,10 +3547,10 @@ function codeartsEfforts(live) {
3499
3547
  }
3500
3548
  /** Every level a CodeArts model can get: the fallback when the router is silent. */
3501
3549
  const CODEARTS_EFFORTS = codeartsEfforts();
3502
- function record$3(value) {
3550
+ function record$4(value) {
3503
3551
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3504
3552
  }
3505
- function text$2(value) {
3553
+ function text$3(value) {
3506
3554
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
3507
3555
  }
3508
3556
  function num$1(value) {
@@ -3521,7 +3569,7 @@ function unwrapEnvelope(payload) {
3521
3569
  const code = payload.code;
3522
3570
  if (typeof code !== "number") return payload;
3523
3571
  if (code !== 0) return void 0;
3524
- return record$3(payload.data);
3572
+ return record$4(payload.data);
3525
3573
  }
3526
3574
  /** `deepseek-v4-flash-0731` → `deepseek-v4-flash`; chat only knows the latter. */
3527
3575
  function normalizeCodeArtsModelId(id) {
@@ -3537,21 +3585,21 @@ function normalizeCodeArtsModelId(id) {
3537
3585
  * how the upstream behaves when a model has no slots at all.
3538
3586
  */
3539
3587
  function pickActiveCredit(credit, slots, nowMs = Date.now()) {
3540
- const entries = credit.map((entry) => record$3(entry)).filter((entry) => entry !== void 0);
3588
+ const entries = credit.map((entry) => record$4(entry)).filter((entry) => entry !== void 0);
3541
3589
  if (entries.length === 0) return void 0;
3542
- const table = record$3(slots);
3590
+ const table = record$4(slots);
3543
3591
  if (table === void 0) return entries[0];
3544
3592
  for (const entry of entries) {
3545
- const slotId = text$2(entry.time_slot_id);
3593
+ const slotId = text$3(entry.time_slot_id);
3546
3594
  if (slotId === void 0) continue;
3547
- const slot = record$3(table[slotId]);
3595
+ const slot = record$4(table[slotId]);
3548
3596
  if (slot === void 0) continue;
3549
3597
  if (slotActive(slot, nowMs)) return entry;
3550
3598
  }
3551
3599
  return entries[0];
3552
3600
  }
3553
3601
  function slotActive(slot, nowMs) {
3554
- const schedule = text$2(slot.schedule);
3602
+ const schedule = text$3(slot.schedule);
3555
3603
  if (schedule === void 0) return false;
3556
3604
  const parts = schedule.split(/\s+/);
3557
3605
  if (parts.length < 3) return false;
@@ -3570,18 +3618,18 @@ function parseCodeArtsRoster(items, nowMs = Date.now()) {
3570
3618
  const seen = /* @__PURE__ */ new Set();
3571
3619
  const out = [];
3572
3620
  for (const item of items) {
3573
- const entry = record$3(item);
3621
+ const entry = record$4(item);
3574
3622
  if (entry === void 0) continue;
3575
- const rawId = text$2(entry.model_id);
3623
+ const rawId = text$3(entry.model_id);
3576
3624
  if (rawId === void 0) continue;
3577
3625
  const id = normalizeCodeArtsModelId(rawId);
3578
3626
  const key = id.toLowerCase();
3579
3627
  if (seen.has(key)) continue;
3580
3628
  seen.add(key);
3581
- const rateLabel = text$2(pickActiveCredit(Array.isArray(entry.credit) ? entry.credit : [], entry.time_slots, nowMs)?.ratio_display);
3629
+ const rateLabel = text$3(pickActiveCredit(Array.isArray(entry.credit) ? entry.credit : [], entry.time_slots, nowMs)?.ratio_display);
3582
3630
  out.push({
3583
3631
  id,
3584
- name: text$2(entry.model_name) ?? id,
3632
+ name: text$3(entry.model_name) ?? id,
3585
3633
  ...rateLabel === void 0 ? {} : { rateLabel },
3586
3634
  ...entry.supports_images === true ? { images: true } : {}
3587
3635
  });
@@ -3762,8 +3810,8 @@ var CodeArtsProvider = class {
3762
3810
  const now = Date.now();
3763
3811
  const gateway = await this.signedGet(credential, CODEARTS_GATEWAY_CONFIG_URL);
3764
3812
  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);
3813
+ const gatewayRows = parseCodeArtsRoster(record$4(record$4(gateway)?.result)?.models, now);
3814
+ const builtinRows = parseCodeArtsRoster(record$4(builtin)?.builtinModels, now);
3767
3815
  if (gatewayRows.length === 0 && builtinRows.length === 0) throw new Error("CodeArts 没有返回任何模型(签名、凭据或网关异常)");
3768
3816
  if (gatewayRows.length > 0) this.benefitModels = new Set(gatewayRows.map((row) => row.id));
3769
3817
  const textOnly = this.options.textOnly();
@@ -3844,7 +3892,7 @@ var CodeArtsProvider = class {
3844
3892
  }
3845
3893
  async readQuota() {
3846
3894
  const credential = await this.credential();
3847
- const payload = record$3(await this.signedGet(credential, `${CODEARTS_SNAP_BASE}${CODEARTS_PACKAGE_INFO_PATH}`));
3895
+ const payload = record$4(await this.signedGet(credential, `${CODEARTS_SNAP_BASE}${CODEARTS_PACKAGE_INFO_PATH}`));
3848
3896
  if (payload === void 0) throw new Error("CodeArts 没有返回账户信息");
3849
3897
  const info = parseCodeArtsPackage(payload);
3850
3898
  const total = info.credit.amount ?? info.credit.remaining;
@@ -3936,26 +3984,26 @@ var CodeArtsProvider = class {
3936
3984
  signal: AbortSignal.timeout(this.options.timeoutMs())
3937
3985
  });
3938
3986
  if (!response.ok) return void 0;
3939
- return unwrapEnvelope(record$3(await response.json()));
3987
+ return unwrapEnvelope(record$4(await response.json()));
3940
3988
  } catch {
3941
3989
  return;
3942
3990
  }
3943
3991
  }
3944
3992
  async accountInfo(credential) {
3945
- const payload = record$3(await this.signedGet(credential, `${CODEARTS_SNAP_BASE}${CODEARTS_PACKAGE_INFO_PATH}`));
3993
+ const payload = record$4(await this.signedGet(credential, `${CODEARTS_SNAP_BASE}${CODEARTS_PACKAGE_INFO_PATH}`));
3946
3994
  return payload === void 0 ? void 0 : parseCodeArtsPackage(payload);
3947
3995
  }
3948
3996
  async activities(credential) {
3949
3997
  const url = `${CODEARTS_SNAP_BASE}${CODEARTS_OPS_DELIVERY_PATH}?channel=IDE`;
3950
- const items = unwrapEnvelope(record$3(await this.signedGet(credential, url)))?.items;
3998
+ const items = unwrapEnvelope(record$4(await this.signedGet(credential, url)))?.items;
3951
3999
  if (!Array.isArray(items)) return void 0;
3952
4000
  return items.map((item) => {
3953
- const entry = record$3(item) ?? {};
4001
+ const entry = record$4(item) ?? {};
3954
4002
  return {
3955
4003
  campaignId: String(entry.campaignId ?? ""),
3956
- type: text$2(entry.type) ?? "",
4004
+ type: text$3(entry.type) ?? "",
3957
4005
  claimable: entry.claimable === true || String(entry.claimable).toLowerCase() === "true",
3958
- status: text$2(entry.status) ?? "",
4006
+ status: text$3(entry.status) ?? "",
3959
4007
  amount: num$1(entry.benefitAmount) ?? num$1(entry.amount) ?? num$1(entry.creditAmount) ?? 0
3960
4008
  };
3961
4009
  });
@@ -3971,7 +4019,7 @@ var CodeArtsProvider = class {
3971
4019
  const cache = /* @__PURE__ */ new Map();
3972
4020
  const file = this.imageCacheFile();
3973
4021
  if (file !== void 0) try {
3974
- const entries = record$3(record$3(JSON.parse(await promises.readFile(file, "utf8")))?.entries);
4022
+ const entries = record$4(record$4(JSON.parse(await promises.readFile(file, "utf8")))?.entries);
3975
4023
  for (const [key, value] of Object.entries(entries ?? {})) if (typeof value === "string" && value.length > 0) cache.set(key, value);
3976
4024
  } catch {}
3977
4025
  this.imageCache = cache;
@@ -4024,7 +4072,7 @@ var CodeArtsProvider = class {
4024
4072
  for (let index = 0; index < parts.length; index++) {
4025
4073
  const part = parts[index];
4026
4074
  if (part.type !== "image_url") continue;
4027
- const url = record$3(part.image_url)?.url;
4075
+ const url = record$4(part.image_url)?.url;
4028
4076
  if (typeof url !== "string" || url.length === 0) continue;
4029
4077
  const key = sha256Hex(url);
4030
4078
  let transcript = cache.get(key);
@@ -4088,9 +4136,9 @@ var CodeArtsProvider = class {
4088
4136
  for await (const chunk of consumeOpenAiStream(response, {
4089
4137
  ...signal === void 0 ? {} : { signal },
4090
4138
  onFrame: (frame) => {
4091
- const code = text$2(frame.error_code);
4139
+ const code = text$3(frame.error_code);
4092
4140
  if (code === void 0) return;
4093
- const message = text$2(frame.error_msg) ?? code;
4141
+ const message = text$3(frame.error_msg) ?? code;
4094
4142
  if (QUEUE_MARKERS.some((marker) => code.includes(marker))) throw new CodeArtsRetry("queue", message);
4095
4143
  throw new ProviderHttpError(message, 502);
4096
4144
  }
@@ -4175,9 +4223,9 @@ var CodeArtsProvider = class {
4175
4223
  for await (const chunk of consumeOpenAiStream(response, {
4176
4224
  ...options.signal === void 0 ? {} : { signal: options.signal },
4177
4225
  onFrame: (frame) => {
4178
- const code = text$2(frame.error_code);
4226
+ const code = text$3(frame.error_code);
4179
4227
  if (code === void 0) return;
4180
- const message = text$2(frame.error_msg) ?? code;
4228
+ const message = text$3(frame.error_msg) ?? code;
4181
4229
  if (QUEUE_MARKERS.some((marker) => code.includes(marker))) throw new CodeArtsRetry("queue", message);
4182
4230
  if (code.includes("InferHub.4004.200")) throw new CodeArtsRetry("benefit", message);
4183
4231
  throw new ProviderHttpError(message, 502);
@@ -4275,8 +4323,8 @@ function clampMaxTokens(value) {
4275
4323
  * for them would claim credits expire when they do not.
4276
4324
  */
4277
4325
  function parseCodeArtsPackage(payload) {
4278
- const pkg = record$3(payload.package) ?? {};
4279
- const endDate = text$2(payload.end_date);
4326
+ const pkg = record$4(payload.package) ?? {};
4327
+ const endDate = text$3(payload.end_date);
4280
4328
  const endMs = endDate === void 0 ? void 0 : Date.parse(endDate);
4281
4329
  const labels = {
4282
4330
  usageBasicPackageCredit: "基础积分包",
@@ -4286,9 +4334,9 @@ function parseCodeArtsPackage(payload) {
4286
4334
  const packs = [];
4287
4335
  let summary;
4288
4336
  for (const item of Array.isArray(payload.metrics) ? payload.metrics : []) {
4289
- const metric = record$3(item);
4337
+ const metric = record$4(item);
4290
4338
  if (metric === void 0) continue;
4291
- const name = text$2(metric.name) ?? "";
4339
+ const name = text$3(metric.name) ?? "";
4292
4340
  const amount = num$1(metric.package_credit_amount);
4293
4341
  const used = num$1(metric.package_credit_used);
4294
4342
  const remain = num$1(metric.package_credit_remain);
@@ -4321,8 +4369,8 @@ function parseCodeArtsPackage(payload) {
4321
4369
  });
4322
4370
  return {
4323
4371
  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) ?? "",
4372
+ packageName: text$3(pkg.package_name_cn) ?? text$3(pkg.package_name_en) ?? "",
4373
+ packageStatus: text$3(pkg.status) ?? "",
4326
4374
  credit: {
4327
4375
  ...credit,
4328
4376
  packs
@@ -6278,7 +6326,7 @@ function numeric(value) {
6278
6326
  if (typeof value === "number" && Number.isFinite(value)) return value;
6279
6327
  if (typeof value === "string" && /^-?\d+(?:\.\d+)?$/.test(value.trim())) return Number(value);
6280
6328
  }
6281
- function record$2(value) {
6329
+ function record$3(value) {
6282
6330
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
6283
6331
  }
6284
6332
  function messageOf(payload) {
@@ -6332,7 +6380,7 @@ async function raccoonAccessTokenValid(token, fetchImpl = fetch) {
6332
6380
  return false;
6333
6381
  }
6334
6382
  }
6335
- function jwtExpiry$1(token) {
6383
+ function jwtExpiry$2(token) {
6336
6384
  const parts = token.split(".");
6337
6385
  if (parts.length < 2) return void 0;
6338
6386
  try {
@@ -6354,7 +6402,7 @@ function raccoonRefreshCandidate(tokens, accessToken) {
6354
6402
  let best;
6355
6403
  for (const token of tokens) {
6356
6404
  if (token === accessToken) continue;
6357
- const expiry = jwtExpiry$1(token);
6405
+ const expiry = jwtExpiry$2(token);
6358
6406
  if (expiry === void 0) continue;
6359
6407
  if (best === void 0 || expiry > best.expiry) best = {
6360
6408
  token,
@@ -6517,13 +6565,13 @@ var RaccoonProvider = class {
6517
6565
  */
6518
6566
  async dailyGrant() {
6519
6567
  const payload = await this.authorizedJson("/points/v2/bills?paging.limit=50&type=all");
6520
- const root = record$2(payload) ?? {};
6568
+ const root = record$3(payload) ?? {};
6521
6569
  if (typeof root.code === "number" && root.code !== 0) throw new Error(messageOf(payload) ?? `小浣熊积分流水返回 code=${root.code}`);
6522
- const data = record$2(root.data) ?? root;
6570
+ const data = record$3(root.data) ?? root;
6523
6571
  const items = Array.isArray(data.items) ? data.items : [];
6524
6572
  const today = raccoonDay(Date.now());
6525
6573
  for (const raw of items) {
6526
- const item = record$2(raw);
6574
+ const item = record$3(raw);
6527
6575
  if (item?.biz_type !== "daily_grant") continue;
6528
6576
  const day = item.grant_at ?? item.created_at;
6529
6577
  if (typeof day !== "string" && typeof day !== "number") continue;
@@ -6633,19 +6681,253 @@ var RaccoonProvider = class {
6633
6681
  }
6634
6682
  }
6635
6683
  };
6684
+ // src/trae-client.ts
6685
+ /**
6686
+ * The local Trae desktop client's sign-in state.
6687
+ *
6688
+ * Trae's check-in route insists on the device id its own client registered, so
6689
+ * the website login cannot use it. When the client is installed and already
6690
+ * signed in, the plugin can reuse that state instead:
6691
+ *
6692
+ * - the `Cloud-IDE-JWT` stored under `iCubeAuthInfo://icube.cloudide`;
6693
+ * - the 16-digit device id encoded in the `iCubeAuthInfo://icube-dc:<id>` key;
6694
+ * - `telemetry.machineId`, the value the client sends as `x-machine-id`.
6695
+ *
6696
+ * The stored value uses the client's own AES-CBC envelope: six magic bytes, a
6697
+ * 32-byte key material, then the ciphertext. The AES key and IV are derived
6698
+ * from that key material with SHA-512 and a fixed salt, and the plaintext is a
6699
+ * SHA-512 digest followed by the JSON document. Nothing here writes to the
6700
+ * client, and the token is never returned by the settings API.
6701
+ *
6702
+ * @module dsh-llm-adapters/trae-client
6703
+ */
6704
+ const AUTH_PROVIDER_KEY = "iCubeAuthInfo://icube.cloudide";
6705
+ const DEVICE_KEY_PREFIX = "iCubeAuthInfo://icube-dc:";
6706
+ const MACHINE_KEY = "telemetry.machineId";
6707
+ const SERVICE_MACHINE_KEY = "storage.serviceMachineId";
6708
+ const AES_MAGIC = Buffer.from([
6709
+ 116,
6710
+ 99,
6711
+ 5,
6712
+ 16,
6713
+ 0,
6714
+ 0
6715
+ ]);
6716
+ const AES_PRIVATE_MAGIC = Buffer.from([
6717
+ 18,
6718
+ 57,
6719
+ 32,
6720
+ 32,
6721
+ 2,
6722
+ 3
6723
+ ]);
6724
+ const KEY_OFFSET = 6;
6725
+ const HASH_LENGTH = 64;
6726
+ /** Aue ^ Due from the client's byteCrypto module. */
6727
+ const AES_SALT = Buffer.from("4dd4c2e6b83162090e52b3c7a6733ba41cb2462b829ab58a196b39db57177524f49baf7f08e8d68d26a72e37c1a95a2f1f05a51892aef2949732b62a38aadd58", "hex");
6728
+ /** Tue ^ Iue, the AES_PRIVATE variant of the same envelope. */
6729
+ const AES_PRIVATE_SALT = Buffer.from("490cc21292b05d0cc06ccbe91fb92e21b5a7c538811dc85e27a96e28104e1cf69c7166efab232807390da78a8b91e49c44b49a43feb941a1e46f2656e613e47b", "hex");
6730
+ const DEVICE_RE = /^\d{16}$/;
6731
+ function record$2(value) {
6732
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
6733
+ }
6734
+ function text$2(value) {
6735
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
6736
+ }
6737
+ /** Accept epoch seconds, epoch milliseconds, or an ISO date. */
6738
+ function epochMs$2(value) {
6739
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) return value < 0xe8d4a51000 ? Math.round(value * 1e3) : Math.round(value);
6740
+ if (typeof value === "string") {
6741
+ if (/^\d+$/.test(value.trim())) return epochMs$2(Number(value));
6742
+ const parsed = Date.parse(value);
6743
+ return Number.isFinite(parsed) ? parsed : void 0;
6744
+ }
6745
+ }
6746
+ function jwtExpiry$1(token) {
6747
+ const parts = token.split(".");
6748
+ if (parts.length < 2) return void 0;
6749
+ try {
6750
+ const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
6751
+ const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
6752
+ return epochMs$2(record$2(JSON.parse(Buffer.from(padded, "base64").toString("utf8")))?.exp);
6753
+ } catch {
6754
+ return;
6755
+ }
6756
+ }
6757
+ /**
6758
+ * Open one `iCubeAuthInfo` value.
6759
+ *
6760
+ * The digest check is the client's own integrity check: the first 64 plaintext
6761
+ * bytes are SHA-512 of the document that follows. A malformed or changed
6762
+ * envelope is rejected instead of being parsed as garbage.
6763
+ */
6764
+ function decryptAuthValue(value) {
6765
+ const bytes = Buffer.from(value, "base64");
6766
+ if (bytes.length < 102) throw new Error("数据长度不足");
6767
+ const magic = bytes.subarray(0, KEY_OFFSET);
6768
+ const salt = magic.equals(AES_MAGIC) ? AES_SALT : magic.equals(AES_PRIVATE_MAGIC) ? AES_PRIVATE_SALT : void 0;
6769
+ if (salt === void 0) throw new Error("未知的加密格式");
6770
+ const keyMaterial = bytes.subarray(KEY_OFFSET, 38);
6771
+ const mixed = createHash("sha512").update(keyMaterial).digest();
6772
+ const derived = createHash("sha512").update(Buffer.concat([mixed, salt])).digest();
6773
+ const decipher = createDecipheriv("aes-128-cbc", derived.subarray(0, 16), derived.subarray(16, 32));
6774
+ const plain = Buffer.concat([decipher.update(bytes.subarray(38)), decipher.final()]);
6775
+ if (plain.length < HASH_LENGTH) throw new Error("解密结果为空");
6776
+ const body = plain.subarray(HASH_LENGTH);
6777
+ if (!createHash("sha512").update(body).digest().equals(plain.subarray(0, HASH_LENGTH))) throw new Error("校验失败");
6778
+ return JSON.parse(body.toString("utf8"));
6779
+ }
6780
+ /**
6781
+ * The client's data directory, most specific first.
6782
+ *
6783
+ * `DSH_TRAE_CLIENT_DIR` is a portable/test override; the region-specific
6784
+ * variables let one machine keep separate CN and international installs.
6785
+ */
6786
+ function traeClientDataDirs(region, options = {}) {
6787
+ const platform = options.platform ?? process.platform;
6788
+ const env = options.env ?? process.env;
6789
+ const home = options.home ?? homedir();
6790
+ const name = region === "cn" ? "Trae CN" : "Trae";
6791
+ const out = [];
6792
+ const add = (directory) => {
6793
+ if (directory !== void 0 && directory !== "") out.push(directory);
6794
+ };
6795
+ add(env[region === "cn" ? "DSH_TRAE_CN_CLIENT_DIR" : "DSH_TRAE_GLOBAL_CLIENT_DIR"]);
6796
+ add(env["DSH_TRAE_CLIENT_DIR"]);
6797
+ if (platform === "win32") {
6798
+ const appData = env["APPDATA"] ?? join(home, "AppData", "Roaming");
6799
+ add(join(appData, name));
6800
+ } else if (platform === "darwin") add(join(home, "Library", "Application Support", name));
6801
+ else {
6802
+ const config = env["XDG_CONFIG_HOME"] ?? join(home, ".config");
6803
+ add(join(config, name));
6804
+ }
6805
+ return [...new Set(out)];
6806
+ }
6807
+ function storageFile(userDataDir) {
6808
+ return join(userDataDir, "User", "globalStorage", "storage.json");
6809
+ }
6810
+ function authValueOf(storage) {
6811
+ const direct = text$2(storage[AUTH_PROVIDER_KEY]);
6812
+ if (direct !== void 0) return direct;
6813
+ for (const key of Object.keys(storage)) if (key.startsWith("iCubeAuthInfo://") && key.endsWith(".cloudide")) {
6814
+ const value = text$2(storage[key]);
6815
+ if (value !== void 0) return value;
6816
+ }
6817
+ }
6818
+ /** The client has one device id per machine; the last valid key is current. */
6819
+ function deviceIdOf(storage) {
6820
+ const keys = Object.keys(storage).filter((key) => key.startsWith(DEVICE_KEY_PREFIX));
6821
+ for (let index = keys.length - 1; index >= 0; index--) {
6822
+ const value = text$2(keys[index].slice(25));
6823
+ if (value !== void 0 && DEVICE_RE.test(value)) return value;
6824
+ }
6825
+ }
6826
+ function regionMismatch(region, userInfo) {
6827
+ const account = record$2(userInfo.account);
6828
+ const evidence = (text$2(record$2(userInfo.userRegion)?.region) ?? text$2(account?.storeRegion))?.toUpperCase();
6829
+ if (evidence === void 0) return false;
6830
+ return region === "cn" ? evidence !== "CN" : evidence === "CN";
6831
+ }
6832
+ /** Parse one `storage.json` document; exported for tests and diagnostics. */
6833
+ function parseTraeClientStorage(region, storage) {
6834
+ const encrypted = authValueOf(storage);
6835
+ if (encrypted === void 0) return { status: {
6836
+ available: true,
6837
+ signedIn: false,
6838
+ error: "本机 Trae 客户端尚未登录。请先在客户端完成登录。"
6839
+ } };
6840
+ let parsed;
6841
+ try {
6842
+ parsed = decryptAuthValue(encrypted);
6843
+ } catch (error) {
6844
+ return { status: {
6845
+ available: true,
6846
+ signedIn: false,
6847
+ error: `读取本机 Trae 客户端登录态失败:${error instanceof Error ? error.message : String(error)}`
6848
+ } };
6849
+ }
6850
+ const userInfo = record$2(parsed);
6851
+ const token = text$2(userInfo?.token);
6852
+ if (userInfo === void 0 || token === void 0) return { status: {
6853
+ available: true,
6854
+ signedIn: false,
6855
+ error: "本机 Trae 客户端登录态里没有令牌,请先在客户端重新登录。"
6856
+ } };
6857
+ const account = record$2(userInfo.account);
6858
+ const name = text$2(account?.username) ?? text$2(account?.name);
6859
+ const userId = text$2(userInfo.userId);
6860
+ const expiresAt = epochMs$2(userInfo.expiredAt) ?? jwtExpiry$1(token);
6861
+ const identity = {
6862
+ ...name === void 0 ? {} : { name },
6863
+ ...userId === void 0 ? {} : { userId },
6864
+ ...expiresAt === void 0 ? {} : { expiresAt }
6865
+ };
6866
+ const unavailable = (error) => ({ status: {
6867
+ available: true,
6868
+ signedIn: false,
6869
+ ...identity,
6870
+ error
6871
+ } });
6872
+ if (regionMismatch(region, userInfo)) return unavailable(region === "cn" ? "本机 Trae 客户端登录的是国际版账号,请切换到国际版后再复用。" : "本机 Trae 客户端登录的是国内版账号,请切换到国内版后再复用。");
6873
+ if (expiresAt !== void 0 && expiresAt <= Date.now()) return unavailable("本机 Trae 客户端登录已过期,请先在客户端重新登录。");
6874
+ const deviceId = deviceIdOf(storage);
6875
+ if (deviceId === void 0) return unavailable("本机 Trae 客户端登录缺少 16 位设备号,请先在客户端重新登录。");
6876
+ const machineId = text$2(storage[MACHINE_KEY]) ?? text$2(storage[SERVICE_MACHINE_KEY]);
6877
+ if (machineId === void 0) return unavailable("本机 Trae 客户端登录缺少机器码,请先在客户端重新登录。");
6878
+ return {
6879
+ status: {
6880
+ available: true,
6881
+ signedIn: true,
6882
+ ...identity
6883
+ },
6884
+ state: {
6885
+ region,
6886
+ token,
6887
+ deviceId,
6888
+ machineId,
6889
+ ...identity
6890
+ }
6891
+ };
6892
+ }
6893
+ /** Read the currently selected region's client state from this machine. */
6894
+ function readTraeClientLogin(region, options = {}) {
6895
+ let available = false;
6896
+ let error;
6897
+ for (const directory of traeClientDataDirs(region, options)) {
6898
+ const file = storageFile(directory);
6899
+ if (!existsSync(file)) continue;
6900
+ available = true;
6901
+ try {
6902
+ const raw = readFileSync(file, "utf8").replace(/^\uFEFF/, "");
6903
+ const storage = record$2(JSON.parse(raw));
6904
+ if (storage === void 0) throw new Error("storage.json 不是对象");
6905
+ return parseTraeClientStorage(region, storage);
6906
+ } catch (cause) {
6907
+ error = `读取本机 Trae 客户端登录态失败:${cause instanceof Error ? cause.message : String(cause)}`;
6908
+ }
6909
+ }
6910
+ return { status: {
6911
+ available,
6912
+ signedIn: false,
6913
+ ...error === void 0 ? {} : { error }
6914
+ } };
6915
+ }
6636
6916
  // src/trae-login.ts
6637
6917
  /**
6638
6918
  * Trae sign-in through the provider's own website.
6639
6919
  *
6640
- * The plugin does not read a desktop install: it opens a dedicated browser
6920
+ * This flow does not read a desktop install: it opens a dedicated browser
6641
6921
  * profile, lets the user sign in on trae.cn / trae.ai, and then takes the
6642
6922
  * session the website itself created. Two things come out of that session:
6643
6923
  *
6644
6924
  * - the `Cloud-IDE-JWT` token, fetched with the page's own cookies through
6645
6925
  * `/cloudide/api/v3/common/GetUserToken` — the same call the site makes;
6646
6926
  * - 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.
6927
+ * the page), stored with the web login.
6928
+ *
6929
+ * The check-in route does not accept this web fingerprint, so check-in is
6930
+ * enabled only for the separate client-login import in `trae-client.ts`.
6649
6931
  *
6650
6932
  * Cookies are captured over the DevTools protocol and kept in the plugin's own
6651
6933
  * account store so the short-lived JWT can be renewed later; the dedicated
@@ -7257,10 +7539,12 @@ var TraeWebLogin = class {
7257
7539
  };
7258
7540
  // src/trae.ts
7259
7541
  /**
7260
- * Trae provider: model directory, SOLO chat and quota.
7542
+ * Trae provider: model directory, SOLO chat, quota and client check-in.
7261
7543
  *
7262
7544
  * 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:
7545
+ * the plugin talks to the same gateways they do. A second sign-in method copies
7546
+ * that token from the local desktop client; only that method carries the device
7547
+ * id the daily check-in route accepts. Two catalog sources are merged:
7264
7548
  *
7265
7549
  * - the Remote directory (`/api/remote/v1/models`) is the model skeleton —
7266
7550
  * display name, context window, multiplier, reasoning options, multimodal;
@@ -7962,6 +8246,22 @@ function parseTraeGlobalUsage(payload) {
7962
8246
  at: Date.now()
7963
8247
  };
7964
8248
  }
8249
+ /**
8250
+ * The check-in body's `req_source`: 1 is the IDE, 2 is Trae Work. The desktop
8251
+ * client sends 1; the claim route rejects the plugin's former empty body.
8252
+ */
8253
+ const TRAE_CHECKIN_REQ_SOURCE = 1;
8254
+ function parseTraeCheckin(payload) {
8255
+ const root = record(payload) ?? {};
8256
+ const reward = num(root.credits) ?? 0;
8257
+ return {
8258
+ available: root.enable !== false,
8259
+ claimed: root.checked_in === true,
8260
+ deviceClaimed: root.did_checked_in === true,
8261
+ ...reward === 0 ? {} : { reward },
8262
+ ...text(root.message) === void 0 ? {} : { message: text(root.message) }
8263
+ };
8264
+ }
7965
8265
  var TraeProvider = class {
7966
8266
  options;
7967
8267
  catalogInstance;
@@ -8172,6 +8472,160 @@ var TraeProvider = class {
8172
8472
  this.quotaInflightRegion = region;
8173
8473
  return this.quotaInflight;
8174
8474
  }
8475
+ /**
8476
+ * The second sign-in method: the desktop client's current state.
8477
+ *
8478
+ * Read-only; the settings page only receives the non-secret status, never
8479
+ * the token.
8480
+ */
8481
+ async localClientLogin() {
8482
+ return this.readClientLogin().status;
8483
+ }
8484
+ readClientLogin() {
8485
+ return (this.options.clientLogin ?? (() => readTraeClientLogin(this.options.region())))();
8486
+ }
8487
+ /** Import the desktop client's token, device id and machine id. */
8488
+ async importClientLogin() {
8489
+ const region = this.ensureRegion();
8490
+ const detection = this.readClientLogin();
8491
+ if (!detection.status.available) throw new Error("本机没有检测到 Trae 客户端。请先安装并登录客户端,或改用网页登录。");
8492
+ const state = detection.state;
8493
+ if (!detection.status.signedIn || state === void 0) throw new Error(detection.status.error ?? "本机 Trae 客户端尚未登录。请先在客户端完成登录。");
8494
+ await this.loginFlow?.cancel();
8495
+ await this.options.store.saveTrae({
8496
+ accessToken: state.token,
8497
+ region,
8498
+ source: "client",
8499
+ deviceId: state.deviceId,
8500
+ machineId: state.machineId,
8501
+ loginAt: Date.now(),
8502
+ ...state.userId === void 0 ? {} : { userId: state.userId },
8503
+ ...state.name === void 0 ? {} : { name: state.name },
8504
+ ...state.expiresAt === void 0 ? {} : { expiresAt: state.expiresAt }
8505
+ });
8506
+ this.invalidateModels();
8507
+ this.invalidateQuota();
8508
+ }
8509
+ async checkinContext() {
8510
+ this.ensureRegion();
8511
+ const profile = this.profile();
8512
+ if (!profile.checkin) return {
8513
+ ok: false,
8514
+ status: {
8515
+ available: false,
8516
+ claimed: false,
8517
+ deviceClaimed: false,
8518
+ message: "Trae 国际版没有签到活动。"
8519
+ }
8520
+ };
8521
+ const auth = await this.options.auth.status();
8522
+ if (!auth.signedIn) return {
8523
+ ok: false,
8524
+ status: {
8525
+ available: false,
8526
+ claimed: false,
8527
+ deviceClaimed: false,
8528
+ message: auth.error ?? "尚未登录,无法签到。"
8529
+ }
8530
+ };
8531
+ if (auth.loginSource !== "client") return {
8532
+ ok: false,
8533
+ status: {
8534
+ available: false,
8535
+ claimed: false,
8536
+ deviceClaimed: false,
8537
+ message: "签到仅在复用本机 Trae 客户端登录时可用;网页登录没有客户端设备号。"
8538
+ }
8539
+ };
8540
+ let token;
8541
+ let identity;
8542
+ try {
8543
+ token = await this.options.auth.accessToken();
8544
+ identity = await this.options.auth.identity();
8545
+ } catch (error) {
8546
+ return {
8547
+ ok: false,
8548
+ status: {
8549
+ available: false,
8550
+ claimed: false,
8551
+ deviceClaimed: false,
8552
+ message: error instanceof Error ? error.message : String(error)
8553
+ }
8554
+ };
8555
+ }
8556
+ if (!/^\d{16}$/.test(identity.deviceId)) return {
8557
+ ok: false,
8558
+ status: {
8559
+ available: false,
8560
+ claimed: false,
8561
+ deviceClaimed: false,
8562
+ message: "本机 Trae 客户端登录缺少 16 位设备号,请先在客户端重新登录后再复用。"
8563
+ }
8564
+ };
8565
+ return {
8566
+ ok: true,
8567
+ profile,
8568
+ token,
8569
+ identity: {
8570
+ deviceId: identity.deviceId,
8571
+ machineId: identity.machineId
8572
+ }
8573
+ };
8574
+ }
8575
+ async checkinStatus() {
8576
+ const context = await this.checkinContext();
8577
+ if (!context.ok) return context.status;
8578
+ const response = await this.fetchImpl(`${context.profile.apiBase}/trae/api/v2/ug/checkin_credits/status`, {
8579
+ method: "POST",
8580
+ headers: {
8581
+ ...this.headers(context.token, context.profile),
8582
+ "x-user-region": "CN",
8583
+ "x-device-id": context.identity.deviceId,
8584
+ "x-machine-id": context.identity.machineId
8585
+ },
8586
+ body: JSON.stringify({ req_source: 1 }),
8587
+ signal: AbortSignal.timeout(this.options.timeoutMs())
8588
+ });
8589
+ if (!response.ok) throw new ProviderHttpError(await responseError(response), response.status);
8590
+ return parseTraeCheckin(await response.json());
8591
+ }
8592
+ /** Claim today's reward. The only state-changing call in this provider. */
8593
+ async claimCheckin() {
8594
+ const context = await this.checkinContext();
8595
+ if (!context.ok) throw new Error(context.status.message ?? "当前登录方式不能签到。");
8596
+ const response = await this.fetchImpl(`${context.profile.apiBase}/trae/api/v2/ug/checkin_credits/claim`, {
8597
+ method: "POST",
8598
+ headers: {
8599
+ ...this.headers(context.token, context.profile),
8600
+ "x-user-region": "CN",
8601
+ "x-device-id": context.identity.deviceId,
8602
+ "x-machine-id": context.identity.machineId
8603
+ },
8604
+ body: JSON.stringify({ req_source: 1 }),
8605
+ signal: AbortSignal.timeout(this.options.timeoutMs())
8606
+ });
8607
+ if (!response.ok) throw new ProviderHttpError(await responseError(response), response.status);
8608
+ const payload = record(await response.json()) ?? {};
8609
+ const code = typeof payload.code === "number" ? payload.code : 0;
8610
+ if (code === 0) {
8611
+ this.invalidateQuota();
8612
+ const reward = num(payload.credits);
8613
+ return {
8614
+ available: true,
8615
+ claimed: true,
8616
+ deviceClaimed: false,
8617
+ ...reward === void 0 || reward === 0 ? {} : { reward },
8618
+ ...text(payload.message) === void 0 ? {} : { message: text(payload.message) }
8619
+ };
8620
+ }
8621
+ if (code === 9095) return {
8622
+ available: true,
8623
+ claimed: false,
8624
+ deviceClaimed: true,
8625
+ message: "本机今日已签到"
8626
+ };
8627
+ throw new Error(text(payload.message) ?? `签到失败(code ${code})`);
8628
+ }
8175
8629
  async *stream(options, model, readImage, onInvalidate) {
8176
8630
  this.ensureRegion();
8177
8631
  const profile = this.profile();
@@ -8229,6 +8683,7 @@ var TraeProvider = class {
8229
8683
  await this.options.store.saveTrae({
8230
8684
  accessToken: account.accessToken,
8231
8685
  region: target,
8686
+ source: "web",
8232
8687
  deviceId: account.deviceId,
8233
8688
  machineId: account.machineId,
8234
8689
  loginAt: Date.now(),
@@ -8510,4 +8965,4 @@ function apply(ctx, config) {
8510
8965
  ctx.logger?.info(`llm-adapters: 设置接口已挂载于 ${API_PREFIX}`);
8511
8966
  });
8512
8967
  }
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 };
8968
+ 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_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, 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, sharedSessionWindow, signDpopJws, signRequestHuawei, sseEvents, startLoginPage, traeClientDataDirs, traeMessages, traeTools };