dsh-connect-trae 4.4.4 → 4.4.6

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
@@ -6,7 +6,7 @@ import { PiAiAdapter } from "@deepseek-ai/dsh-llm-pi-ai";
6
6
  import { existsSync, readFileSync } from "node:fs";
7
7
  import { dirname, join } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
- import { copyFile, readFile, rm, stat } from "node:fs/promises";
9
+ import { copyFile, readFile, rename, rm, stat } from "node:fs/promises";
10
10
  import { createCipheriv, createDecipheriv, createHash, randomBytes, randomUUID, sign, timingSafeEqual } from "node:crypto";
11
11
  import { withFileLock, writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
12
12
  import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
@@ -2223,21 +2223,55 @@ function credentialOfStored(stored) {
2223
2223
  * needed and an older install's file is never rejected. A malformed row is
2224
2224
  * dropped rather than failing the whole file — one bad imported account must
2225
2225
  * not hide the rest.
2226
+ *
2227
+ * Returns `undefined` — NOT an empty array — for a file this build cannot parse
2228
+ * at all: a torn JSON write, a hand-edited document, or a version nothing here
2229
+ * knows. The distinction is load-bearing (see {@link readOwnForMerge}): every
2230
+ * merge writer treats `[]` as the merge base but `undefined` as "do not build
2231
+ * on this", because an unparseable file may still HOLD every account, and
2232
+ * merging onto "empty" would erase them on the very next refresh.
2226
2233
  */
2227
2234
  function parseOwn(text) {
2235
+ let document;
2228
2236
  try {
2229
- const document = JSON.parse(text);
2230
- if (document.version === OWN_VERSION) {
2231
- if (!Array.isArray(document.credentials)) return [];
2232
- return document.credentials.map(credentialOfStored).filter((value) => value !== void 0);
2233
- }
2234
- if (document.version === OWN_VERSION_SINGLE) {
2235
- const single = credentialOfStored(document.credential);
2236
- return single === void 0 ? [] : [single];
2237
- }
2238
- return [];
2237
+ const parsed = JSON.parse(text);
2238
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return void 0;
2239
+ document = parsed;
2239
2240
  } catch {
2240
- return [];
2241
+ return;
2242
+ }
2243
+ if (document.version === OWN_VERSION) {
2244
+ if (!Array.isArray(document.credentials)) return void 0;
2245
+ return document.credentials.map(credentialOfStored).filter((value) => value !== void 0);
2246
+ }
2247
+ if (document.version === OWN_VERSION_SINGLE) {
2248
+ const single = credentialOfStored(document.credential);
2249
+ return single === void 0 ? void 0 : [single];
2250
+ }
2251
+ }
2252
+ /**
2253
+ * Move a plugin-owned file this build can no longer parse out of the way, so
2254
+ * the next write starts from a clean file instead of building on — or being
2255
+ * refused by — the unreadable one.
2256
+ *
2257
+ * The rename is the data preservation: the bytes stay on disk beside the live
2258
+ * path (`.corrupt-<stamp>`), so a future build that understands the format (or
2259
+ * a user with a text editor) can still recover the accounts it carried. It is
2260
+ * best-effort on purpose: a quarantine that cannot happen must not fail the
2261
+ * user action that triggered it, and the merge writers refuse to overwrite a
2262
+ * file they could not quarantine (see {@link readOwnForMerge}).
2263
+ *
2264
+ * Must be called under the same lock the caller writes under: without the lock
2265
+ * the rename could interleave with a concurrent writer holding the pre-rename
2266
+ * content and write it right back.
2267
+ */
2268
+ async function quarantineCorruptOwn(path) {
2269
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2270
+ try {
2271
+ await rename(path, `${path}.corrupt-${stamp}`);
2272
+ return true;
2273
+ } catch {
2274
+ return false;
2241
2275
  }
2242
2276
  }
2243
2277
  /**
@@ -2415,9 +2449,26 @@ var TraeCredentialStore = class {
2415
2449
  return /* @__PURE__ */ new Set();
2416
2450
  }
2417
2451
  }
2418
- async writeTombstones(ids) {
2452
+ /**
2453
+ * Read, mutate, and write the tombstone list under ONE lock.
2454
+ *
2455
+ * The tombstone file is read-modify-write state shared by two flows that run
2456
+ * concurrently: `mirrorLocalLogins` (drops tombstones whose logins are gone)
2457
+ * and `deleteAccount` (adds the id just deleted). A read OUTSIDE the write's
2458
+ * lock loses updates in both directions — a dropped-add resurrects a deleted
2459
+ * account on the next mirror, a dropped-drop keeps a tombstone forever and
2460
+ * hides every future sign-in of that account. The mutator therefore runs on
2461
+ * the freshly-read set INSIDE the lock, and what it returns is what gets
2462
+ * written, with no window in between.
2463
+ *
2464
+ * Fire-and-forget safe: a failed update only delays a tombstone change until
2465
+ * the next mirror or delete, so errors are swallowed like the readers'.
2466
+ */
2467
+ async updateTombstones(mutator) {
2419
2468
  const path = this.deletedPath();
2420
2469
  await withFileLock(path, async () => {
2470
+ const ids = await this.readTombstones();
2471
+ mutator(ids);
2421
2472
  await writeFileAtomic(path, `${JSON.stringify({
2422
2473
  version: 1,
2423
2474
  ids: [...ids]
@@ -2425,7 +2476,31 @@ var TraeCredentialStore = class {
2425
2476
  mode: 384,
2426
2477
  dirMode: 448
2427
2478
  });
2428
- });
2479
+ }).catch(() => void 0);
2480
+ }
2481
+ /**
2482
+ * Read the plugin-owned file as the merge base for a read-modify-write.
2483
+ *
2484
+ * This is the fail-safe that keeps one unreadable file from erasing every
2485
+ * account: `parseOwn` now distinguishes a VALID empty file (`[]`) from one
2486
+ * this build cannot parse (`undefined`), and only the former may serve as a
2487
+ * merge base. For the latter the file is quarantined — renamed aside with
2488
+ * its bytes intact — and the merge then starts from nothing, so the action
2489
+ * the user pressed still succeeds and the lost entries are recoverable from
2490
+ * the quarantine copy instead of being overwritten silently. A quarantine
2491
+ * that fails (an exotic filesystem, a name collision) aborts the write
2492
+ * instead: losing one refresh is recoverable by pressing it again, losing
2493
+ * every account is not.
2494
+ *
2495
+ * Every caller must hold the {@link withFileLock} of `path` across BOTH the
2496
+ * read and the subsequent write — this does not lock by itself.
2497
+ */
2498
+ async readOwnForMerge(path) {
2499
+ const text = await readFile(path, "utf8").catch(() => void 0);
2500
+ if (text === void 0) return [];
2501
+ const parsed = parseOwn(text);
2502
+ if (parsed !== void 0) return parsed;
2503
+ return await quarantineCorruptOwn(path) ? [] : void 0;
2429
2504
  }
2430
2505
  /**
2431
2506
  * Copy every local Trae sign-in into this store's own file, once.
@@ -2447,20 +2522,17 @@ var TraeCredentialStore = class {
2447
2522
  const { credentials: discovered } = await this.readDesktopAll();
2448
2523
  const own = await this.readOwns();
2449
2524
  const ownIds = new Set(own.map((credential) => traeAccountId(credential)));
2450
- const tombstones = await this.readTombstones();
2451
2525
  const liveIds = new Set(discovered.map((credential) => traeAccountId(credential)));
2452
- let tombstonesChanged = false;
2453
- for (const id of [...tombstones]) {
2454
- if (liveIds.has(id)) continue;
2455
- tombstones.delete(id);
2456
- tombstonesChanged = true;
2457
- }
2526
+ const tombstones = await this.readTombstones();
2527
+ if ([...tombstones].some((id) => !liveIds.has(id))) await this.updateTombstones((ids) => {
2528
+ for (const id of [...ids]) if (!liveIds.has(id)) ids.delete(id);
2529
+ });
2458
2530
  const missing = discovered.filter((credential) => this.matchesRegion(credential) && !ownIds.has(traeAccountId(credential)) && !tombstones.has(traeAccountId(credential)));
2459
- if (tombstonesChanged) await this.writeTombstones(tombstones);
2460
2531
  if (missing.length === 0) return;
2461
2532
  const ownPath = this.ownAuthPath();
2462
2533
  await withFileLock(ownPath, async () => {
2463
- const existing = await readFile(ownPath, "utf8").then(parseOwn, () => []);
2534
+ const existing = await this.readOwnForMerge(ownPath);
2535
+ if (existing === void 0) return;
2464
2536
  const byId = new Map(existing.map((credential) => [traeAccountId(credential), credential]));
2465
2537
  for (const credential of missing) {
2466
2538
  const id = traeAccountId(credential);
@@ -2748,7 +2820,8 @@ var TraeCredentialStore = class {
2748
2820
  const ownPath = this.ownAuthPath();
2749
2821
  const written = [];
2750
2822
  await withFileLock(ownPath, async () => {
2751
- const existing = await readFile(ownPath, "utf8").then(parseOwn, () => []);
2823
+ const existing = await this.readOwnForMerge(ownPath);
2824
+ if (existing === void 0) return;
2752
2825
  const byId = /* @__PURE__ */ new Map();
2753
2826
  for (const item of existing) byId.set(traeAccountId(item), item);
2754
2827
  for (const credential of credentials) {
@@ -2907,7 +2980,7 @@ var TraeCredentialStore = class {
2907
2980
  let mutated = false;
2908
2981
  for (const path of this.ownCandidates()) {
2909
2982
  const dropped = await withFileLock(path, async () => {
2910
- const existing = await readFile(path, "utf8").then(parseOwn, () => null);
2983
+ const existing = await readFile(path, "utf8").then((text) => parseOwn(text) ?? null, () => null);
2911
2984
  if (existing === null || !existing.some((credential) => traeAccountId(credential) === accountId)) return false;
2912
2985
  const next = existing.filter((credential) => traeAccountId(credential) !== accountId);
2913
2986
  await writeOwn(path, next);
@@ -2915,11 +2988,9 @@ var TraeCredentialStore = class {
2915
2988
  }).catch(() => false);
2916
2989
  mutated = mutated || dropped;
2917
2990
  }
2918
- const tombstones = await this.readTombstones();
2919
- if (!tombstones.has(accountId)) {
2920
- tombstones.add(accountId);
2921
- await this.writeTombstones(tombstones).catch(() => void 0);
2922
- }
2991
+ await this.updateTombstones((ids) => {
2992
+ ids.add(accountId);
2993
+ });
2923
2994
  if (this.accountId === accountId) {
2924
2995
  this.accountId = void 0;
2925
2996
  this.inflight = void 0;
@@ -3039,7 +3110,8 @@ var TraeCredentialStore = class {
3039
3110
  async readOwns() {
3040
3111
  const copies = [];
3041
3112
  for (const path of this.ownCandidates()) try {
3042
- copies.push(...parseOwn(await readFile(path, "utf8")));
3113
+ const parsed = parseOwn(await readFile(path, "utf8"));
3114
+ if (parsed !== void 0) copies.push(...parsed);
3043
3115
  } catch {}
3044
3116
  return copies;
3045
3117
  }
@@ -3061,7 +3133,8 @@ var TraeCredentialStore = class {
3061
3133
  };
3062
3134
  const ownPath = this.ownAuthPath();
3063
3135
  await withFileLock(ownPath, async () => {
3064
- const existing = await readFile(ownPath, "utf8").then(parseOwn, () => []);
3136
+ const existing = await this.readOwnForMerge(ownPath);
3137
+ if (existing === void 0) return;
3065
3138
  const refreshedId = traeAccountId(refreshed);
3066
3139
  const entries = existing.filter((item) => traeAccountId(item) !== refreshedId);
3067
3140
  entries.push(refreshed);
@@ -3513,6 +3586,182 @@ function identityHeaders(identity) {
3513
3586
  };
3514
3587
  }
3515
3588
  //#endregion
3589
+ //#region src/model-order.ts
3590
+ /**
3591
+ * Wire name of the default SOLO chat function.
3592
+ *
3593
+ * Lives here rather than in `solo.ts` so this module stays importable from the
3594
+ * client; `solo.ts` re-exports it, so there is still exactly one definition.
3595
+ */
3596
+ const TRAE_SOLO_FUNCTION = "solo_work_lite";
3597
+ /**
3598
+ * The chat functions of one region, in PRIORITY order.
3599
+ *
3600
+ * Two consumers read it, and both mean "priority", which is why they can share
3601
+ * one array: the wire asks these functions in order and the first to list a
3602
+ * config owns it (`solo.ts`), and the card draws the groups in the same order so
3603
+ * the row a user sees first is the one the resolver prefers.
3604
+ */
3605
+ const TRAE_CHANNEL_PRIORITY = {
3606
+ cn: [
3607
+ "solo_work_remote",
3608
+ TRAE_SOLO_FUNCTION,
3609
+ "solo_agent_remote",
3610
+ "chat_v3",
3611
+ "solo_coder"
3612
+ ],
3613
+ ai: [
3614
+ "solo_work_remote",
3615
+ TRAE_SOLO_FUNCTION,
3616
+ "solo_agent_remote",
3617
+ "chat_v3"
3618
+ ]
3619
+ };
3620
+ /**
3621
+ * Rank of one channel inside the region's priority list.
3622
+ *
3623
+ * Three cases, and the order between them is deliberate:
3624
+ *
3625
+ * - a known chat channel: its index in the priority list;
3626
+ * - an UNKNOWN channel name (a function the region does not call through —
3627
+ * `withChatCallableFunctions` clears those, so this is a defensive branch):
3628
+ * after every known channel, then alphabetically among themselves;
3629
+ * - NO channel at all (a row demoted to the default chat function): last. Such
3630
+ * a row is the one the resolver knows least about, so it must not push a
3631
+ * properly attributed row down the list.
3632
+ */
3633
+ function channelRank(channel, priority) {
3634
+ if (channel === void 0 || channel === "") return priority.length + 1;
3635
+ const known = priority.indexOf(channel);
3636
+ return known === -1 ? priority.length : known;
3637
+ }
3638
+ /**
3639
+ * Compare two display names the way a person reading a model list expects:
3640
+ * case-insensitively, with embedded numbers compared as NUMBERS, so `GLM-5.10`
3641
+ * sorts after `GLM-5.9`. Plain text comparison puts `5.10` first, which reads as
3642
+ * a bug to anyone who sees the version numbers.
3643
+ */
3644
+ function compareNames(left, right) {
3645
+ const byName = left.trim().localeCompare(right.trim(), void 0, {
3646
+ numeric: true,
3647
+ sensitivity: "base"
3648
+ });
3649
+ return byName !== 0 ? byName : left.localeCompare(right);
3650
+ }
3651
+ /**
3652
+ * The catalog in display order: channel priority first, then name.
3653
+ *
3654
+ * Total and stable — ties fall through to `id`, so two rows with the same name
3655
+ * in the same channel still get one fixed order rather than the order they
3656
+ * arrived in.
3657
+ */
3658
+ function orderTraeModels(rows, region = "cn") {
3659
+ const priority = TRAE_CHANNEL_PRIORITY[region] ?? TRAE_CHANNEL_PRIORITY.cn;
3660
+ return [...rows].sort((left, right) => {
3661
+ const byRank = channelRank(left.wireFunction, priority) - channelRank(right.wireFunction, priority);
3662
+ if (byRank !== 0) return byRank;
3663
+ if (left.wireFunction !== right.wireFunction) return (left.wireFunction ?? "").localeCompare(right.wireFunction ?? "");
3664
+ const byName = compareNames(left.name, right.name);
3665
+ return byName !== 0 ? byName : left.id.localeCompare(right.id);
3666
+ });
3667
+ }
3668
+ /**
3669
+ * Split rows into channel groups, in display order.
3670
+ *
3671
+ * Ordering is re-applied here rather than trusted from the caller: a group list
3672
+ * built from an unsorted input would put the groups in arrival order, which is
3673
+ * the exact instability this module exists to remove.
3674
+ */
3675
+ function groupTraeModels(rows, region = "cn") {
3676
+ const groups = [];
3677
+ const byChannel = /* @__PURE__ */ new Map();
3678
+ for (const row of orderTraeModels(rows, region)) {
3679
+ const channel = row.wireFunction === "" ? void 0 : row.wireFunction;
3680
+ const key = channel ?? "\0default";
3681
+ const existing = byChannel.get(key);
3682
+ if (existing !== void 0) {
3683
+ existing.rows.push(row);
3684
+ continue;
3685
+ }
3686
+ const group = {
3687
+ channel,
3688
+ rows: [row]
3689
+ };
3690
+ byChannel.set(key, group);
3691
+ groups.push(group);
3692
+ }
3693
+ return groups;
3694
+ }
3695
+ /**
3696
+ * Does one row match a search query?
3697
+ *
3698
+ * Matches the display name, the display id and the WIRE config name — the last
3699
+ * one because a user debugging a `4001 config_name rejected` arrives with the
3700
+ * upstream name in hand, and the two differ exactly on the rows that carry a
3701
+ * suffix (`DeepSeek-V4-Flash` vs `DeepSeek-V4-Flash-Official`).
3702
+ */
3703
+ function traeModelMatchesQuery(model, query) {
3704
+ const needle = query.trim().toLowerCase();
3705
+ if (needle === "") return true;
3706
+ if (model.name.toLowerCase().includes(needle)) return true;
3707
+ if (model.id.toLowerCase().includes(needle)) return true;
3708
+ return (model.wireConfigName ?? "").toLowerCase().includes(needle);
3709
+ }
3710
+ /** Filter by {@link traeModelMatchesQuery}, preserving order. */
3711
+ function filterTraeModels(rows, query) {
3712
+ if (query.trim() === "") return [...rows];
3713
+ return rows.filter((row) => traeModelMatchesQuery(row, query));
3714
+ }
3715
+ /**
3716
+ * The channels a NORMAL Trae chat session actually runs on: the `solo_work_*`
3717
+ * family, i.e. the panel's own chat modes.
3718
+ *
3719
+ * Deliberately not "every channel we can call". After issue #52 P1 the catalog
3720
+ * is 34 rows and 10 of them are reachable only through a SPECIALIST channel:
3721
+ * `solo_coder` (7: `Doubao-Seed-2.0-Code`, `glm-5.1`, `glm-5v-turbo`,
3722
+ * `kimi-k2.5`, `minimax-m2.7`, `qwen-3.5`, `qwen-3.6-plus`), `chat_v3` (2:
3723
+ * `kimi-k2.8-preview`, `qwen3.8-flash`) and `solo_agent_remote` (1:
3724
+ * `Doubao-Seed-Code`). Measured 2026-10-23 on a live CN account. Those rows work
3725
+ * — that is why P1 kept them — but they are not what Trae's main chat offers,
3726
+ * and mixing them into the default view is exactly the "a code model answering a
3727
+ * solo request" shape issue #52 asked about.
3728
+ */
3729
+ const TRAE_COMMON_FUNCTIONS = {
3730
+ cn: ["solo_work_remote", TRAE_SOLO_FUNCTION],
3731
+ ai: ["solo_work_remote", TRAE_SOLO_FUNCTION]
3732
+ };
3733
+ /**
3734
+ * The function a request for this row will NAME.
3735
+ *
3736
+ * A row with no `wireFunction` is not "unknown to the wire": the bridge sends
3737
+ * the region default (`TRAE_SOLO_FUNCTION`), so for every question about where
3738
+ * the turn goes, the default is the answer. The distinction still matters for
3739
+ * DISPLAY (`groupTraeModels` keeps these in their own group, because their
3740
+ * provenance — which directory group advertised them — really is unknown), but
3741
+ * not for scope: a `solo_work_lite` turn is a normal chat turn either way.
3742
+ */
3743
+ function traeModelCallChannel(model) {
3744
+ const channel = model.wireFunction;
3745
+ return channel === void 0 || channel === "" ? TRAE_SOLO_FUNCTION : channel;
3746
+ }
3747
+ /**
3748
+ * Does this row belong to the view?
3749
+ *
3750
+ * Judged on {@link traeModelCallChannel}, never on `wireFunction` directly:
3751
+ * otherwise the one row without a function (`doubao_1_8`, measured 2026-10-23)
3752
+ * would fall OUT of the common view while being called through exactly the
3753
+ * common channel it belongs to.
3754
+ */
3755
+ function traeModelInView(model, view, region = "cn") {
3756
+ if (view === "all") return true;
3757
+ return (TRAE_COMMON_FUNCTIONS[region] ?? TRAE_COMMON_FUNCTIONS.cn).includes(traeModelCallChannel(model));
3758
+ }
3759
+ /** Keep the rows a view asks for, preserving order. */
3760
+ function scopeTraeModels(rows, view, region = "cn") {
3761
+ if (view === "all") return [...rows];
3762
+ return rows.filter((row) => traeModelInView(row, view, region));
3763
+ }
3764
+ //#endregion
3516
3765
  //#region src/shim.ts
3517
3766
  const BODY_LIMIT = 67108864;
3518
3767
  const LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
@@ -3772,6 +4021,48 @@ function createTraeShim(options) {
3772
4021
  };
3773
4022
  }
3774
4023
  //#endregion
4024
+ //#region src/model-detail.ts
4025
+ const TRAE_MODEL_DETAIL_PATH = "/api/ide/v1/batch_get_detail_param";
4026
+ const TRAE_MODEL_DETAIL_FUNCTIONS = [
4027
+ "ui_builder_v2",
4028
+ "solo_coder",
4029
+ "chat_v3",
4030
+ "solo_builder",
4031
+ "builder_v3",
4032
+ "builder",
4033
+ "chat",
4034
+ "inline_chat",
4035
+ "git_ai",
4036
+ "custom_agent_generation",
4037
+ "utils",
4038
+ "code_reviewer",
4039
+ "code_review_summary",
4040
+ "solo_agent",
4041
+ "solo_agent_remote",
4042
+ "solo_work_remote",
4043
+ "solo_agent_lite",
4044
+ "solo_work_lite",
4045
+ "solo_design_lite",
4046
+ "solo_design_remote",
4047
+ "multimodal",
4048
+ "system_diagnosis"
4049
+ ];
4050
+ /** Build the exact request shape observed in successful current Trae CN logs. */
4051
+ function buildTraeModelDetailRequest(configName = "", custom = false) {
4052
+ return {
4053
+ functions: [...TRAE_MODEL_DETAIL_FUNCTIONS],
4054
+ agent_type: configName === "" ? "" : "solo_agent",
4055
+ current_config_info: {
4056
+ config_name: configName,
4057
+ is_custom_model: custom
4058
+ },
4059
+ mode_type: 0,
4060
+ access_type: 0,
4061
+ ab_force_vids: "",
4062
+ ab_autotest_advanced_mode: 0
4063
+ };
4064
+ }
4065
+ //#endregion
3775
4066
  //#region src/protocol.ts
3776
4067
  const TRAE_CN_AGENT_TASK_PATH = "/api/agent/v3/create_agent_task";
3777
4068
  const TRAE_CN_TITLE_PATH = "/api/agent/v3/llm_utils_chat";
@@ -3875,6 +4166,231 @@ function traeEndpoint(baseUrl, path) {
3875
4166
  return `${baseUrl.replace(/\/$/, "")}/${path.replace(/^\//, "")}`;
3876
4167
  }
3877
4168
  //#endregion
4169
+ //#region src/official-roster.ts
4170
+ const CHAT_USAGE = "chat_completion";
4171
+ /**
4172
+ * Parse a `batch_get_detail_param` answer into a per-(config, function) index.
4173
+ *
4174
+ * Tolerant on purpose: this document is 1.8 MB of upstream-owned shape
4175
+ * (`function_configs[] → config_info_list[] → model_detail_list[]`), and the
4176
+ * plugin must not lose the whole refresh because one nested field changed
4177
+ * spelling. Rows that cannot be read are skipped; a document with no
4178
+ * `function_configs` at all yields an empty roster, which the filter reads as
4179
+ * "nothing to say" rather than "hide everything".
4180
+ *
4181
+ * Duplicate pairs are collapsed first-wins, matching the rest of the catalog's
4182
+ * convention (`mergeTraeModelSources`). Two functions can both list a config,
4183
+ * and those are DIFFERENT pairs, so both are kept.
4184
+ */
4185
+ function parseTraeOfficialRoster(payload) {
4186
+ const channels = /* @__PURE__ */ new Map();
4187
+ const seen = /* @__PURE__ */ new Set();
4188
+ let pairs = 0;
4189
+ const document = typeof payload === "object" && payload !== null ? payload : {};
4190
+ const groups = Array.isArray(document["function_configs"]) ? document["function_configs"] : [];
4191
+ for (const group of groups) {
4192
+ if (typeof group !== "object" || group === null) continue;
4193
+ const record = group;
4194
+ const functionName = typeof record["function"] === "string" ? record["function"] : "";
4195
+ if (functionName === "") continue;
4196
+ const list = Array.isArray(record["config_info_list"]) ? record["config_info_list"] : [];
4197
+ for (const item of list) {
4198
+ if (typeof item !== "object" || item === null) continue;
4199
+ const config = item;
4200
+ const configName = typeof config["config_name"] === "string" ? config["config_name"].trim() : "";
4201
+ if (configName === "") continue;
4202
+ const key = `${functionName}\u0000${configName.toLowerCase()}`;
4203
+ if (seen.has(key)) continue;
4204
+ seen.add(key);
4205
+ const channel = {
4206
+ function: functionName,
4207
+ usage: typeof config["usage"] === "string" ? config["usage"] : "",
4208
+ enabled: config["config_switch"] !== false,
4209
+ invisible: config["is_invisible_to_user"] === true
4210
+ };
4211
+ pairs += 1;
4212
+ const bucket = channels.get(configName.toLowerCase());
4213
+ if (bucket === void 0) channels.set(configName.toLowerCase(), [channel]);
4214
+ else bucket.push(channel);
4215
+ }
4216
+ }
4217
+ return {
4218
+ channels,
4219
+ size: {
4220
+ configs: channels.size,
4221
+ pairs
4222
+ }
4223
+ };
4224
+ }
4225
+ /** The `config_name` a catalog row would actually send (see `wireConfigName`). */
4226
+ function officialKeyOf(model) {
4227
+ return (model.wireConfigName ?? model.id).trim().toLowerCase();
4228
+ }
4229
+ /**
4230
+ * Decide one row from the roster.
4231
+ *
4232
+ * `chatFunctions` is the region's own `TRAE_DIRECTORY_FUNCTIONS` list, in
4233
+ * priority order: only those functions can carry a VERDICT, because only those
4234
+ * are functions `llm_utils_chat` accepts. A config the vendor shows under
4235
+ * `code_review_summary` alone is not offered as a chat model at all — that is
4236
+ * exactly the `glm-4.7` case, and it is a "hide", not a "re-pin".
4237
+ *
4238
+ * The verdict is `keep` when the roster cannot speak: no entry for the config
4239
+ * (measured 2026-10-23: `kimi-k2.8-preview` and `qwen3.8-flash` exist only in
4240
+ * the Remote directory's `chat_v3` group), only tool rows (`usage: 'summary'`,
4241
+ * `fast_apply`, …), or no chat function at all. Silence is never a verdict.
4242
+ */
4243
+ function officialVerdictOf(model, roster, chatFunctions) {
4244
+ const entries = roster.channels.get(officialKeyOf(model));
4245
+ if (entries === void 0 || entries.length === 0) return { kind: "keep" };
4246
+ const callable = entries.filter((entry) => entry.usage === "" || entry.usage === CHAT_USAGE).filter((entry) => chatFunctions.includes(entry.function));
4247
+ if (callable.length === 0) return { kind: "keep" };
4248
+ const listedBy = callable.map((entry) => entry.function);
4249
+ const shown = callable.filter((entry) => !entry.invisible);
4250
+ if (shown.length === 0) return {
4251
+ kind: "hide",
4252
+ function: model.wireFunction,
4253
+ listedBy
4254
+ };
4255
+ const own = model.wireFunction === void 0 ? void 0 : callable.find((entry) => entry.function === model.wireFunction);
4256
+ if (own !== void 0 && !own.invisible) return { kind: "keep" };
4257
+ const to = chatFunctions.find((fn) => shown.some((entry) => entry.function === fn));
4258
+ if (to === void 0) return { kind: "keep" };
4259
+ return {
4260
+ kind: "repin",
4261
+ from: model.wireFunction,
4262
+ to,
4263
+ listedBy
4264
+ };
4265
+ }
4266
+ /**
4267
+ * Apply the roster: retire what no chat function offers any more, and re-pin
4268
+ * what the vendor offers under a different channel.
4269
+ *
4270
+ * FAIL-OPEN BY CONSTRUCTION, which is the whole safety argument for applying
4271
+ * this by default:
4272
+ *
4273
+ * - `undefined` roster (transport failure, restricted network, a credential
4274
+ * that cannot read the IDE document) keeps EVERY row. A refresh must never
4275
+ * lose models because a second, optional endpoint was unreachable.
4276
+ * - `keep` covers every ambiguity — no entry, tool-only entries, a channel the
4277
+ * roster does not name. Only an explicit "the vendor shows this nowhere we
4278
+ * can call it" removes a row.
4279
+ * - `config_switch === false` alone does NOT remove a row: the flag is
4280
+ * recorded on {@link TraeOfficialChannel} but not used as a verdict, because
4281
+ * its semantics are unmeasured and "disabled" there may describe a function
4282
+ * override rather than the model itself.
4283
+ *
4284
+ * This is deliberately NOT "match the Trae client": the client renders one
4285
+ * function, the plugin serves the union, and issue #46's reporter wants the
4286
+ * dated snapshots the union carries. What changes here is only the class where
4287
+ * the union is provably wrong — twelve retired configs that made up the reported
4288
+ * "tail of old models", plus rows pinned to a channel their own vendor hides
4289
+ * them on (the "code model on a solo channel" risk of issue #52).
4290
+ */
4291
+ function applyOfficialRoster(rows, roster, chatFunctions) {
4292
+ if (roster === void 0) return {
4293
+ rows: [...rows],
4294
+ hidden: [],
4295
+ repinned: []
4296
+ };
4297
+ const kept = [];
4298
+ const hidden = [];
4299
+ const repinned = [];
4300
+ for (const row of rows) {
4301
+ const verdict = officialVerdictOf(row, roster, chatFunctions);
4302
+ if (verdict.kind === "hide") {
4303
+ hidden.push({
4304
+ id: row.id,
4305
+ function: verdict.function
4306
+ });
4307
+ continue;
4308
+ }
4309
+ if (verdict.kind === "repin") {
4310
+ repinned.push({
4311
+ id: row.id,
4312
+ from: verdict.from,
4313
+ to: verdict.to
4314
+ });
4315
+ kept.push({
4316
+ ...row,
4317
+ wireFunction: verdict.to
4318
+ });
4319
+ continue;
4320
+ }
4321
+ kept.push(row);
4322
+ }
4323
+ return {
4324
+ rows: kept,
4325
+ hidden,
4326
+ repinned
4327
+ };
4328
+ }
4329
+ /** How long one successful roster read stays usable, in milliseconds. */
4330
+ const ROSTER_CACHE_MS = 6e5;
4331
+ /** Timeout for the roster read. It is an OPTIONAL signal; it must not stall a refresh. */
4332
+ const ROSTER_TIMEOUT_MS = 2e4;
4333
+ /**
4334
+ * Reads the IDE's roster once per cache window and never throws.
4335
+ *
4336
+ * `undefined` is a first-class answer: it is what a caller sees when the
4337
+ * document could not be read, and {@link applyOfficialRoster} treats it as "no
4338
+ * opinion". Silence on failure is intentional — this is a refinement of a
4339
+ * refresh that already succeeded, so a strict error would turn a usable catalog
4340
+ * into a failed refresh.
4341
+ */
4342
+ var TraeOfficialRosterClient = class {
4343
+ options;
4344
+ fetchImpl;
4345
+ now;
4346
+ cached;
4347
+ /** The last failure, so a repeatedly-broken endpoint is not re-probed on every refresh. */
4348
+ failedAt;
4349
+ constructor(options) {
4350
+ this.options = options;
4351
+ this.fetchImpl = options.fetchImpl ?? fetch;
4352
+ this.now = options.now ?? Date.now;
4353
+ }
4354
+ /** Cached roster, without any network I/O. */
4355
+ cachedRoster() {
4356
+ return this.cached === void 0 ? void 0 : this.cached.roster;
4357
+ }
4358
+ async fetch(signal) {
4359
+ const now = this.now();
4360
+ if (this.cached !== void 0 && now - this.cached.at < ROSTER_CACHE_MS) return this.cached.roster;
4361
+ if (this.failedAt !== void 0 && now - this.failedAt < ROSTER_CACHE_MS) return void 0;
4362
+ try {
4363
+ const [credential, identity] = await Promise.all([this.options.credential(), this.options.identity()]);
4364
+ const region = regionOfCredential(credential);
4365
+ const base = this.options.baseUrl ?? (this.options.route === "enterprise" ? "https://console.enterprise.trae.cn" : REGION_GATEWAYS[region].chat);
4366
+ const response = await this.fetchImpl(traeEndpoint(base, TRAE_MODEL_DETAIL_PATH), {
4367
+ method: "POST",
4368
+ headers: buildTraeCnHeaders(credential, identity, { profile: "model-detail" }),
4369
+ body: JSON.stringify({
4370
+ ...buildTraeModelDetailRequest(""),
4371
+ show_custom_model: true
4372
+ }),
4373
+ signal: signal ?? AbortSignal.timeout(ROSTER_TIMEOUT_MS)
4374
+ });
4375
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
4376
+ const roster = parseTraeOfficialRoster(await response.json());
4377
+ if (roster.size.configs === 0) throw new Error("roster document carried no config_name");
4378
+ this.cached = {
4379
+ at: now,
4380
+ roster
4381
+ };
4382
+ this.failedAt = void 0;
4383
+ return roster;
4384
+ } catch (error) {
4385
+ this.failedAt = now;
4386
+ this.options.log?.("dsh-connect-trae: IDE model roster unavailable, keeping every discovered row", { reason: error instanceof Error ? error.message : String(error) });
4387
+ return;
4388
+ }
4389
+ }
4390
+ };
4391
+ /** Where the client asks for the roster; re-exported so diagnostics do not hard-code it. */
4392
+ const TRAE_OFFICIAL_ROSTER_PATH = TRAE_MODEL_DETAIL_PATH;
4393
+ //#endregion
3878
4394
  //#region src/raw-chat.ts
3879
4395
  const TRAE_RAW_CHAT_V2_PATH = "/api/ide/v2/llm_raw_chat";
3880
4396
  const TRAE_RAW_CHAT_V1_PATH = "/api/ide/v1/llm_raw_chat";
@@ -4138,7 +4654,6 @@ function applyReasoningEffort(body, effort, capability) {
4138
4654
  }
4139
4655
  //#endregion
4140
4656
  //#region src/solo.ts
4141
- const TRAE_SOLO_FUNCTION = "solo_work_lite";
4142
4657
  /**
4143
4658
  * `llm_utils_chat` functions to union per region, in priority order.
4144
4659
  *
@@ -4202,22 +4717,12 @@ const TRAE_SOLO_FUNCTION = "solo_work_lite";
4202
4717
  * A model in NO chat function's roster is still left to the merge: it ships
4203
4718
  * with no wire target and its call fails with a readable upstream message
4204
4719
  * rather than being hidden (see mergeTraeModelSources).
4720
+ *
4721
+ * The list itself lives in {@link TRAE_CHANNEL_PRIORITY} (`model-order.ts`) and
4722
+ * is aliased here, not copied: the card groups its rows by these same names in
4723
+ * this same order.
4205
4724
  */
4206
- const TRAE_DIRECTORY_FUNCTIONS = {
4207
- cn: [
4208
- "solo_work_remote",
4209
- TRAE_SOLO_FUNCTION,
4210
- "solo_agent_remote",
4211
- "chat_v3",
4212
- "solo_coder"
4213
- ],
4214
- ai: [
4215
- "solo_work_remote",
4216
- TRAE_SOLO_FUNCTION,
4217
- "solo_agent_remote",
4218
- "chat_v3"
4219
- ]
4220
- };
4725
+ const TRAE_DIRECTORY_FUNCTIONS = TRAE_CHANNEL_PRIORITY;
4221
4726
  const TRAE_SOLO_CHAT_PATH = "/api/agent/v3/llm_utils_chat";
4222
4727
  const TRAE_SOLO_MODELS_PATH = "/api/ide/v1/get_detail_param";
4223
4728
  function classify(status) {
@@ -8822,24 +9327,28 @@ async function writeTraePoolProbes(panel, updates) {
8822
9327
  * member, and leaving it in the file would resurrect a stale `rate-limited` row if
8823
9328
  * the account were added back later. Written through the same lock/atomic path so a
8824
9329
  * removal cannot interleave with a batch.
9330
+ *
9331
+ * The READ also sits inside the lock, like {@link writeTraePoolProbes}'s: a
9332
+ * delete is a read-modify-write, and a read taken outside the lock snapshots a
9333
+ * moment that a concurrent batch's write can then be older than — the delete
9334
+ * would write that stale snapshot back and silently discard the measurements
9335
+ * the batch had just learned.
8825
9336
  */
8826
9337
  async function dropTraePoolProbe(panel, accountId) {
8827
- const existing = await readTraePoolProbes(panel);
8828
- if (existing[accountId] === void 0) return;
8829
9338
  const path = traePoolStorePath(panel);
8830
- const probes = {};
8831
- for (const [id, probe] of Object.entries(existing)) if (id !== accountId) probes[id] = probe;
8832
- try {
8833
- await withFileLock(path, async () => {
8834
- await writeFileAtomic(path, `${JSON.stringify({
8835
- version: STORE_FORMAT_VERSION,
8836
- probes
8837
- }, null, 2)}\n`, {
8838
- mode: 384,
8839
- dirMode: 448
8840
- });
9339
+ await withFileLock(path, async () => {
9340
+ const existing = await readTraePoolProbes(panel);
9341
+ if (existing[accountId] === void 0) return;
9342
+ const probes = {};
9343
+ for (const [id, probe] of Object.entries(existing)) if (id !== accountId) probes[id] = probe;
9344
+ await writeFileAtomic(path, `${JSON.stringify({
9345
+ version: STORE_FORMAT_VERSION,
9346
+ probes
9347
+ }, null, 2)}\n`, {
9348
+ mode: 384,
9349
+ dirMode: 448
8841
9350
  });
8842
- } catch {}
9351
+ }).catch(() => {});
8843
9352
  }
8844
9353
  //#endregion
8845
9354
  //#region src/model-config.ts
@@ -8898,48 +9407,6 @@ function parseTraeModelExtraConfigLogLine(line) {
8898
9407
  }
8899
9408
  }
8900
9409
  //#endregion
8901
- //#region src/model-detail.ts
8902
- const TRAE_MODEL_DETAIL_PATH = "/api/ide/v1/batch_get_detail_param";
8903
- const TRAE_MODEL_DETAIL_FUNCTIONS = [
8904
- "ui_builder_v2",
8905
- "solo_coder",
8906
- "chat_v3",
8907
- "solo_builder",
8908
- "builder_v3",
8909
- "builder",
8910
- "chat",
8911
- "inline_chat",
8912
- "git_ai",
8913
- "custom_agent_generation",
8914
- "utils",
8915
- "code_reviewer",
8916
- "code_review_summary",
8917
- "solo_agent",
8918
- "solo_agent_remote",
8919
- "solo_work_remote",
8920
- "solo_agent_lite",
8921
- "solo_work_lite",
8922
- "solo_design_lite",
8923
- "solo_design_remote",
8924
- "multimodal",
8925
- "system_diagnosis"
8926
- ];
8927
- /** Build the exact request shape observed in successful current Trae CN logs. */
8928
- function buildTraeModelDetailRequest(configName = "", custom = false) {
8929
- return {
8930
- functions: [...TRAE_MODEL_DETAIL_FUNCTIONS],
8931
- agent_type: configName === "" ? "" : "solo_agent",
8932
- current_config_info: {
8933
- config_name: configName,
8934
- is_custom_model: custom
8935
- },
8936
- mode_type: 0,
8937
- access_type: 0,
8938
- ab_force_vids: "",
8939
- ab_autotest_advanced_mode: 0
8940
- };
8941
- }
8942
- //#endregion
8943
9410
  //#region src/redact.ts
8944
9411
  /**
8945
9412
  * Redaction helpers for diagnostics that a user may paste into a public issue.
@@ -9421,6 +9888,24 @@ function apply(ctx, config) {
9421
9888
  credential: () => store.resolve(),
9422
9889
  log: (message, detail) => ctx.logger.warn(message, detail)
9423
9890
  });
9891
+ /**
9892
+ * The IDE's own roster: which models the vendor's picker shows, per
9893
+ * function (issue #52).
9894
+ *
9895
+ * It is a REFINEMENT of a successful discovery, never a dependency: every
9896
+ * failure path in {@link TraeOfficialRosterClient.fetch} collapses to
9897
+ * `undefined`, and `applyOfficialRoster` then keeps every row. That is why
9898
+ * this can be wired in without a setting — it only acts where every chat
9899
+ * function hides a config (retire it) or where the vendor shows it on a
9900
+ * DIFFERENT chat function than the one we guessed (re-pin). Both are the
9901
+ * upstream's own answers, and both leave anything ambiguous alone.
9902
+ */
9903
+ const officialRoster = new TraeOfficialRosterClient({
9904
+ credential: () => store.resolve(),
9905
+ identity,
9906
+ ...panel === "enterprise" ? { route: "enterprise" } : {},
9907
+ log: (message, detail) => ctx.logger.warn(message, detail)
9908
+ });
9424
9909
  const wireResolver = (displayId) => wire.byId.get(displayId) ?? wire.byName.get(displayId.trim().toLowerCase());
9425
9910
  /**
9426
9911
  * One client + bridge per provider id.
@@ -9572,25 +10057,52 @@ function apply(ctx, config) {
9572
10057
  };
9573
10058
  },
9574
10059
  discoverModels: async (signal) => {
9575
- const merged = mergeTraeModelSources(await withDirectoryRetry(() => remoteCatalog.fetchModels(signal)), await solo.fetchModels(signal));
10060
+ const remote = await withDirectoryRetry(() => remoteCatalog.fetchModels(signal));
10061
+ const [wireModels, roster] = await Promise.all([solo.fetchModels(signal), officialRoster.fetch(signal)]);
10062
+ const merged = mergeTraeModelSources(remote, wireModels);
9576
10063
  const callable = withChatCallableFunctions(merged, TRAE_DIRECTORY_FUNCTIONS[region]);
9577
10064
  const chatFunctions = TRAE_DIRECTORY_FUNCTIONS[region];
9578
10065
  for (const model of merged) {
9579
10066
  if (model.wireFunction === void 0 || chatFunctions.includes(model.wireFunction)) continue;
9580
10067
  ctx.logger.warn(`model directory row is pinned to a non-chat function and will be called through the default chat function instead: ${model.id} (pinned to function ${model.wireFunction})`);
9581
10068
  }
10069
+ /**
10070
+ * Retire what no chat function offers any more, and re-pin what the
10071
+ * vendor offers under a DIFFERENT channel (issue #52).
10072
+ *
10073
+ * Runs AFTER `withChatCallableFunctions` on purpose: that step decides
10074
+ * the `wireFunction` this filter judges the row on. Rows the IDE roster
10075
+ * does not mention, and every row when the roster could not be read at
10076
+ * all, survive untouched — see `applyOfficialRoster`.
10077
+ */
10078
+ const official = applyOfficialRoster(callable, roster, chatFunctions);
10079
+ /**
10080
+ * Fix the ORDER once, here, so it is a property of the catalog rather
10081
+ * than of the screen that happens to draw it (issue #52, P0).
10082
+ *
10083
+ * The merge walks twelve directory groups and `solo-remote.ts` records
10084
+ * that the group order varies between calls, so the same account could
10085
+ * get two different lists from two refreshes. Sorting by (channel
10086
+ * priority, name) makes the saved `lastCatalog` — and therefore the DSH
10087
+ * model picker, which renders in catalog order — identical across
10088
+ * refreshes. The card re-applies the same function for catalogs saved
10089
+ * before this existed.
10090
+ */
10091
+ const visible = orderTraeModels(official.rows, region);
10092
+ if (official.hidden.length > 0) ctx.logger.warn(`hiding ${official.hidden.length} model(s) no chat function offers any more (issue #52)`, official.hidden);
10093
+ if (official.repinned.length > 0) ctx.logger.warn(`re-pinning ${official.repinned.length} model(s) to the function their vendor shows them on (issue #52)`, official.repinned);
9582
10094
  wire.callableKeys.clear();
9583
- for (const model of callable) {
10095
+ for (const model of visible) {
9584
10096
  wire.callableKeys.add(model.id.trim().toLowerCase());
9585
10097
  wire.callableKeys.add(model.name.trim().toLowerCase());
9586
10098
  }
9587
- wire.resolved = callable.length > 0;
9588
- const wireTargets = wireTargetsOf(callable);
10099
+ wire.resolved = visible.length > 0;
10100
+ const wireTargets = wireTargetsOf(visible);
9589
10101
  wire.byId = wireTargets.byId;
9590
10102
  wire.byName = wireTargets.byName;
9591
10103
  latestDiscovery.length = 0;
9592
- latestDiscovery.push(...callable);
9593
- return callable;
10104
+ latestDiscovery.push(...visible);
10105
+ return visible;
9594
10106
  },
9595
10107
  /**
9596
10108
  * One-click availability test. The ids come from the card, so each one is
@@ -10294,4 +10806,4 @@ function apply(ctx, config) {
10294
10806
  });
10295
10807
  }
10296
10808
  //#endregion
10297
- export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, MAX_PROBE_INPUT_TOKENS, REGION_GATEWAYS, SseDecoder, TRAE_ACCOUNT_STATUS_PATH, TRAE_AI_PROVIDER, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_ENTERPRISE_PROVIDER, TRAE_MODELS_TEST_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PANELS, TRAE_PAY_BASE, TRAE_PROBE_TIMEOUT_MS, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_PROVIDER_IDS, TRAE_PROVIDER_ID_DISPLAY_NAMES, TRAE_PROVIDER_ROUTES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STATE_DB_FILENAME, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, TraeCatalog, TraeCredentialStore, TraeDelegatingUpstreamClient, TraeFallbackUpstreamClient, TraeGatedUpstreamClient, TraeRawCapabilityController, TraeRawCapabilityState, TraeRawChatUpstreamClient, TraeSoloBridge, TraeSoloRemoteCatalogClient, TraeSoloUpstreamClient, TraeUsageClient, UnconfiguredTraeUpstreamClient, adaptLegacyPiAiContext, adaptModernPiAiContext, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, asVolatile, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyProbeError, classifyProbeResult, classifyTraeRawChatFailure, configuredAccountsOf, createTraeAdapter, createTraeRawGateway, createTraeShim, creditPosition, decidePiAiGeneration, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, describeIdShape, describeNameShape, describeTraeFailure, discoveredCatalog, displayNameOfTraeProvider, fallbackModelsFor, generationOf, hashTraeRawChatArg, identityHeaders, inject, isEnterpriseAccount, isPromotableWireModel, legacyTraeOwnAuthPath, maskUserPath, mergeTraeModelSources, name, nextRegionCreditPosition, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeToolSchema, normalizeTraeToolSchemas, normalizeTraeVersionCode, panelOfKey, panelOfProvider, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, piAiRuntimeInfo, pickTraeStorageIdentity, prepareSoloBody, probeBody, probeModelsSequentially, probeOneModel, probeTraeRawChatCapability, providerOfPanel, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionCreditPositionOf, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfPanel, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, routeOfTraeProvider, sanitizeCatalog, settingsNamespaceOf, traeEndpoint, traeInputModalities, traeModelDisplayName, traeOwnAuthPath, traeRawChatExtraInfo, traeStateDatabaseCandidates, traeStorageCandidates, traeWebUsage, traeWindowsAppNames, unwrapVolatile, unwrapVolatileDeep, wireTargetsOf, withContextAdaptation, withTraeRegion };
10809
+ export { Config, FALLBACK_TRAE_MODELS, FALLBACK_TRAE_MODELS_AI, MAX_PROBE_INPUT_TOKENS, REGION_GATEWAYS, SseDecoder, TRAE_ACCOUNT_STATUS_PATH, TRAE_AI_PROVIDER, TRAE_CHANNEL_PRIORITY, TRAE_CHECKIN_PATH, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_COMMON_FUNCTIONS, TRAE_ENTERPRISE_PROVIDER, TRAE_MODELS_TEST_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_OFFICIAL_ROSTER_PATH, TRAE_PANELS, TRAE_PAY_BASE, TRAE_PROBE_TIMEOUT_MS, TRAE_PROVIDER, TRAE_PROVIDERS, TRAE_PROVIDER_DISPLAY_NAMES, TRAE_PROVIDER_IDS, TRAE_PROVIDER_ID_DISPLAY_NAMES, TRAE_PROVIDER_ROUTES, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_REGIONS, TRAE_REGION_PARAM, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_STATE_DB_FILENAME, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, TraeCatalog, TraeCredentialStore, TraeDelegatingUpstreamClient, TraeFallbackUpstreamClient, TraeGatedUpstreamClient, TraeOfficialRosterClient, TraeRawCapabilityController, TraeRawCapabilityState, TraeRawChatUpstreamClient, TraeSoloBridge, TraeSoloRemoteCatalogClient, TraeSoloUpstreamClient, TraeUsageClient, UnconfiguredTraeUpstreamClient, adaptLegacyPiAiContext, adaptModernPiAiContext, apply, applyContextBudgets, applyImageSelection, applyOfficialRoster, applyReasoningEffort, asVolatile, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyProbeError, classifyProbeResult, classifyTraeRawChatFailure, configuredAccountsOf, createTraeAdapter, createTraeRawGateway, createTraeShim, creditPosition, decidePiAiGeneration, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, describeIdShape, describeNameShape, describeTraeFailure, discoveredCatalog, displayNameOfTraeProvider, fallbackModelsFor, filterTraeModels, generationOf, groupTraeModels, hashTraeRawChatArg, identityHeaders, inject, isEnterpriseAccount, isPromotableWireModel, legacyTraeOwnAuthPath, maskUserPath, mergeTraeModelSources, name, nextRegionCreditPosition, nextRegionEnabled, nextRegionSlots, normalizeTraeCredential, normalizeTraeToolSchema, normalizeTraeToolSchemas, normalizeTraeVersionCode, officialVerdictOf, orderTraeModels, panelOfKey, panelOfProvider, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeOfficialRoster, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, piAiRuntimeInfo, pickTraeStorageIdentity, prepareSoloBody, probeBody, probeModelsSequentially, probeOneModel, probeTraeRawChatCapability, providerOfPanel, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeCliIdentity, readTraeIdentity, refreshTraeCredential, regionCreditPositionOf, regionEnabled, regionEnabledOf, regionOfCredential, regionOfEdition, regionOfHost, regionOfPanel, regionOfTraeProvider, regionOfTraeStatusUrl, regionOfUserRegion, regionStateOf, registerTraeUsageRoute, resolveTraeIdentity, resolveTraeRawRuntime, routeOfTraeProvider, sanitizeCatalog, scopeTraeModels, settingsNamespaceOf, traeEndpoint, traeInputModalities, traeModelCallChannel, traeModelDisplayName, traeModelInView, traeModelMatchesQuery, traeOwnAuthPath, traeRawChatExtraInfo, traeStateDatabaseCandidates, traeStorageCandidates, traeWebUsage, traeWindowsAppNames, unwrapVolatile, unwrapVolatileDeep, wireTargetsOf, withChatCallableFunctions, withContextAdaptation, withTraeRegion };