dsh-connect-trae 1.0.1 → 1.1.0

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
@@ -14,6 +14,161 @@ import { createServer } from "node:http";
14
14
  import { Readable } from "node:stream";
15
15
  import { execFile } from "node:child_process";
16
16
  import { promisify } from "node:util";
17
+ //#region src/catalog.ts
18
+ /** Bootstrap catalog: identity only where current Trae metadata has not been fetched yet. */
19
+ const FALLBACK_TRAE_MODELS = [
20
+ {
21
+ id: "auto",
22
+ name: "Auto"
23
+ },
24
+ {
25
+ id: "DeepSeek-V4-Flash",
26
+ name: "DeepSeek-V4-Flash"
27
+ },
28
+ {
29
+ id: "DeepSeek-V4-Pro",
30
+ name: "DeepSeek-V4-Pro"
31
+ },
32
+ {
33
+ id: "glm-5.2",
34
+ name: "GLM-5.2"
35
+ },
36
+ {
37
+ id: "kimi-k2.6",
38
+ name: "Kimi-K2.6"
39
+ }
40
+ ];
41
+ /** Exact DSH modalities for one catalog entry; absent metadata is text-only. */
42
+ function traeInputModalities(model) {
43
+ return [...model.input ?? ["text"]];
44
+ }
45
+ /** Apply the user's explicit image opt-ins; upstream and saved row hints are ignored. */
46
+ function applyImageSelection(models, selected) {
47
+ return models.map((model) => ({
48
+ ...model,
49
+ input: selected.has(model.id) ? ["text", "image"] : ["text"]
50
+ }));
51
+ }
52
+ /** Normalise a display name for cross-source joining. */
53
+ function displayKey(name) {
54
+ return name.trim().toLowerCase();
55
+ }
56
+ /**
57
+ * Merge the two Trae model sources into one authoritative catalog.
58
+ *
59
+ * `remote` (the solo.trae.cn `/models` directory) is the authoritative model
60
+ * skeleton: it supplies the display id, display name, context windows, credit
61
+ * multiplier, reasoning and multimodal flags. `wire` (from `get_detail_param`)
62
+ * supplies the real `llm_utils_chat` `config_name` — the only id the chat
63
+ * endpoint actually accepts. A remote row is only callable when it maps to a
64
+ * wire `config_name`, so a remote row with no wire match is DROPPED (it would
65
+ * otherwise be sent as an invalid `config_name` and rejected with 4001
66
+ * "param is invalid"). Verified 2026-08-30: the Remote directory advertises
67
+ * `Doubao-Seed-Code` and `glm-5.3`, neither of which is a current `config_name`;
68
+ * both fail every request, so they must not be exposed.
69
+ *
70
+ * Joining is two-tier, in priority order:
71
+ * 1. `wire.id` (the `config_name`) equals the remote id — the model's display
72
+ * id is already its wire id (the common case: glm-5.2, DeepSeek-V4-Flash,
73
+ * kimi-k3, …).
74
+ * 2. `wire.name` (the `display_name`) equals the remote display name — for
75
+ * models whose display id differs from the wire id across Trae versions.
76
+ * When the matched wire `config_name` differs from the remote id it is recorded
77
+ * as `wireConfigName`; otherwise it is left undefined (id is already the wire id).
78
+ */
79
+ function mergeTraeModelSources(remote, wire) {
80
+ const wireByName = /* @__PURE__ */ new Map();
81
+ const wireById = /* @__PURE__ */ new Map();
82
+ for (const model of wire) {
83
+ wireByName.set(displayKey(model.name), model);
84
+ wireById.set(displayKey(model.id), model);
85
+ }
86
+ const result = [];
87
+ for (const model of remote) {
88
+ const wireModel = wireById.get(displayKey(model.id)) ?? wireByName.get(displayKey(model.name));
89
+ if (wireModel === void 0) continue;
90
+ result.push({
91
+ id: model.id,
92
+ name: model.name,
93
+ ...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
94
+ ...model.maxContextWindow === void 0 ? {} : { maxContextWindow: model.maxContextWindow },
95
+ ...model.creditMultiplier === void 0 ? {} : { creditMultiplier: model.creditMultiplier },
96
+ input: ["text"],
97
+ reasoningSupported: model.reasoningSupported,
98
+ ...model.reasoning === void 0 ? {} : {
99
+ reasoning: model.reasoning,
100
+ reasoningEfforts: Object.fromEntries(model.reasoning.supported.map((effort) => [effort, effort === "low" ? "light" : effort === "xhigh" ? "extra_high" : "high"]))
101
+ },
102
+ ...wireModel.id !== "" && wireModel.id !== model.id ? { wireConfigName: wireModel.id } : {}
103
+ });
104
+ }
105
+ return result;
106
+ }
107
+ /**
108
+ * Apply the saved local budget. Trae advertises two windows per model (dev and
109
+ * Max), so the budget may only switch a model to its own advertised Max value —
110
+ * never to a fabricated number. Everything else keeps the dev window.
111
+ */
112
+ function applyContextBudgets(catalog, budgets = {}) {
113
+ return catalog.map((model) => ({
114
+ ...model,
115
+ ...model.maxContextWindow !== void 0 && budgets[model.id] === model.maxContextWindow ? { contextWindow: model.maxContextWindow } : {}
116
+ }));
117
+ }
118
+ /** Convert Trae metadata into text-only model rows; image support is user-owned configuration. */
119
+ function discoveredCatalog(models) {
120
+ const result = [];
121
+ for (const model of models) result.push({
122
+ id: model.id,
123
+ name: model.name,
124
+ ...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
125
+ ...model.maxContextWindow === void 0 ? {} : { maxContextWindow: model.maxContextWindow },
126
+ input: ["text"],
127
+ ...model.creditMultiplier === void 0 ? {} : { creditMultiplier: model.creditMultiplier },
128
+ reasoningSupported: model.reasoningSupported,
129
+ ...model.reasoning === void 0 ? {} : {
130
+ reasoning: model.reasoning,
131
+ reasoningEfforts: Object.fromEntries(model.reasoning.supported.map((effort) => [effort, effort === "low" ? "light" : effort === "xhigh" ? "extra_high" : "high"]))
132
+ }
133
+ });
134
+ return result;
135
+ }
136
+ /**
137
+ * Drop rows saved by older releases that generated `@1m` variant models, so a
138
+ * stale configuration cannot resurrect a variant the runtime no longer builds.
139
+ */
140
+ function sanitizeCatalog(catalog) {
141
+ return catalog.filter((model) => {
142
+ if (model.id.endsWith("@1m")) return false;
143
+ const legacy = model;
144
+ return legacy.baseModelId === void 0 && legacy.maxContext !== true;
145
+ });
146
+ }
147
+ /**
148
+ * Derive the runtime catalog from the last refreshed Trae directory plus the
149
+ * user's explicit selection and context budgets. An empty selection falls back
150
+ * to the whole directory: a plugin that has never been configured must still
151
+ * serve models rather than nothing. This is the single source of truth for
152
+ * what DSH actually exposes, so saving only the selection and budgets is
153
+ * enough to rebuild it after a restart.
154
+ */
155
+ function deriveCatalog(catalog, enabled, budgets = {}) {
156
+ return applyContextBudgets(enabled.size === 0 ? catalog : catalog.filter((model) => enabled.has(model.id)), budgets);
157
+ }
158
+ var TraeCatalog = class {
159
+ models = FALLBACK_TRAE_MODELS;
160
+ current() {
161
+ return this.models;
162
+ }
163
+ set(models) {
164
+ if (models.length === 0) throw new Error("trae model catalog cannot be empty");
165
+ this.models = models.map((model) => ({
166
+ ...model,
167
+ ...model.input === void 0 ? {} : { input: [...model.input] }
168
+ }));
169
+ }
170
+ };
171
+ //#endregion
17
172
  //#region src/adapter.ts
18
173
  const TRAE_PROVIDER = "trae";
19
174
  const TRAE_STREAM_IDLE_TIMEOUT_MS = 3e5;
@@ -47,7 +202,7 @@ function toPiModel(info, baseUrl) {
47
202
  api: "openai-completions",
48
203
  provider: TRAE_PROVIDER,
49
204
  baseUrl,
50
- input: [...info.input ?? ["text"]],
205
+ input: traeInputModalities(info),
51
206
  cost: {
52
207
  input: 0,
53
208
  output: 0,
@@ -528,7 +683,6 @@ var TraeCredentialStore = class {
528
683
  storagePathOverride;
529
684
  edition;
530
685
  accountId;
531
- preferIds = [];
532
686
  ownPath;
533
687
  refresh;
534
688
  refreshMarginMs;
@@ -551,16 +705,6 @@ var TraeCredentialStore = class {
551
705
  this.accountId = accountId;
552
706
  this.inflight = void 0;
553
707
  }
554
- /**
555
- * Preferred account ordering used when no account is explicitly selected.
556
- * The connector can only bill the `llm_utils_chat` channel against general
557
- * credits, so accounts with zero general credits should not become the
558
- * default or they fail with 4008 / empty responses.
559
- */
560
- setPreferAccountIds(ids) {
561
- this.preferIds = [...ids];
562
- this.inflight = void 0;
563
- }
564
708
  candidates() {
565
709
  if (this.storagePathOverride !== void 0) return [{
566
710
  edition: this.edition === "auto" ? "cn" : this.edition,
@@ -569,13 +713,13 @@ var TraeCredentialStore = class {
569
713
  const all = traeStorageCandidates();
570
714
  return this.edition === "auto" ? all.filter((candidate) => candidate.edition === "cn" || candidate.edition === "solo") : all.filter((candidate) => candidate.edition === this.edition && (candidate.edition === "cn" || candidate.edition === "solo"));
571
715
  }
572
- /** First credential matching the preferred ordering, else the first live one. */
716
+ /**
717
+ * Deterministic default when no account is explicitly selected: the first
718
+ * discovered account. This is NOT credit-seeking — it never reorders accounts
719
+ * to find one with general credits. The plugin bills exactly the account the
720
+ * user selected, or the first account when nothing has been selected yet.
721
+ */
573
722
  preferred(credentials) {
574
- if (credentials.length === 0) return void 0;
575
- for (const id of this.preferIds) {
576
- const match = credentials.find((credential) => traeAccountId(credential) === id);
577
- if (match !== void 0) return match;
578
- }
579
723
  return credentials[0];
580
724
  }
581
725
  async accounts() {
@@ -594,7 +738,7 @@ var TraeCredentialStore = class {
594
738
  async current() {
595
739
  const credentials = await this.readAll();
596
740
  if (this.accountId === void 0) return this.preferred(credentials);
597
- return credentials.find((credential) => traeAccountId(credential) === this.accountId) ?? this.preferred(credentials);
741
+ return credentials.find((credential) => traeAccountId(credential) === this.accountId);
598
742
  }
599
743
  async resolve() {
600
744
  const credential = await this.current();
@@ -685,104 +829,6 @@ var TraeCredentialStore = class {
685
829
  }
686
830
  };
687
831
  //#endregion
688
- //#region src/catalog.ts
689
- /** Bootstrap catalog: identity only where current Trae metadata has not been fetched yet. */
690
- const FALLBACK_TRAE_MODELS = [
691
- {
692
- id: "auto",
693
- name: "Auto"
694
- },
695
- {
696
- id: "DeepSeek-V4-Flash",
697
- name: "DeepSeek-V4-Flash"
698
- },
699
- {
700
- id: "DeepSeek-V4-Pro",
701
- name: "DeepSeek-V4-Pro"
702
- },
703
- {
704
- id: "Doubao-Seed-Code",
705
- name: "Doubao-Seed-Code",
706
- input: ["text", "image"]
707
- },
708
- {
709
- id: "kimi-k2.6",
710
- name: "Kimi-K2.6",
711
- input: ["text", "image"]
712
- }
713
- ];
714
- /** Convert verified Trae API metadata into normal models and optional 1M variants. */
715
- function discoveredCatalog(models, enabled1m = /* @__PURE__ */ new Set()) {
716
- const result = [];
717
- for (const model of models) {
718
- const common = {
719
- input: model.multimodal ? ["text", "image"] : ["text"],
720
- ...model.creditMultiplier === void 0 ? {} : { creditMultiplier: model.creditMultiplier },
721
- reasoningSupported: model.reasoningSupported,
722
- ...model.reasoning === void 0 ? {} : {
723
- reasoning: model.reasoning,
724
- reasoningEfforts: Object.fromEntries(model.reasoning.supported.map((effort) => [effort, effort === "low" ? "light" : effort === "xhigh" ? "extra_high" : "high"]))
725
- }
726
- };
727
- result.push({
728
- id: model.id,
729
- name: model.name,
730
- ...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
731
- ...model.maxContextWindow === void 0 ? {} : { maxContextWindow: model.maxContextWindow },
732
- ...common
733
- });
734
- if (model.maxContextWindow !== void 0 && enabled1m.has(model.id)) result.push({
735
- id: `${model.id}@1m`,
736
- name: `${model.name} 1M`,
737
- contextWindow: model.maxContextWindow,
738
- baseModelId: model.id,
739
- maxContext: true,
740
- ...common
741
- });
742
- }
743
- return result;
744
- }
745
- /**
746
- * Derive the runtime catalog from the last refreshed Trae directory plus the
747
- * user's explicit selection. Ordinary models whose `id` (Trae `name`) is
748
- * enabled survive; each 1M-capable enabled model produces a `@1m` variant.
749
- * This is the single source of truth for what DSH actually exposes, so saving
750
- * only the selection is enough to rebuild it after a restart.
751
- */
752
- function deriveCatalog(catalog, enabled, enabled1m) {
753
- const result = [];
754
- for (const model of catalog) {
755
- if (!enabled.has(model.id)) continue;
756
- result.push(model);
757
- if (enabled1m.has(model.id) && model.maxContextWindow !== void 0) result.push({
758
- id: `${model.id}@1m`,
759
- name: `${model.name} 1M`,
760
- contextWindow: model.maxContextWindow,
761
- baseModelId: model.id,
762
- maxContext: true,
763
- ...model.input === void 0 ? {} : { input: [...model.input] },
764
- ...model.creditMultiplier === void 0 ? {} : { creditMultiplier: model.creditMultiplier },
765
- ...model.reasoningSupported === void 0 ? {} : { reasoningSupported: model.reasoningSupported },
766
- ...model.reasoning === void 0 ? {} : { reasoning: model.reasoning },
767
- ...model.reasoningEfforts === void 0 ? {} : { reasoningEfforts: model.reasoningEfforts }
768
- });
769
- }
770
- return result;
771
- }
772
- var TraeCatalog = class {
773
- models = FALLBACK_TRAE_MODELS;
774
- current() {
775
- return this.models;
776
- }
777
- set(models) {
778
- if (models.length === 0) throw new Error("trae model catalog cannot be empty");
779
- this.models = models.map((model) => ({
780
- ...model,
781
- ...model.input === void 0 ? {} : { input: [...model.input] }
782
- }));
783
- }
784
- };
785
- //#endregion
786
832
  //#region src/refresh.ts
787
833
  const CLIENT_ID_BY_EDITION = {
788
834
  cn: "ono9krqynydwx5",
@@ -1200,7 +1246,7 @@ function applyReasoningEffort(body, effort, capability) {
1200
1246
  const TRAE_SOLO_FUNCTION = "solo_work_lite";
1201
1247
  const TRAE_SOLO_CHAT_PATH = "/api/agent/v3/llm_utils_chat";
1202
1248
  const TRAE_SOLO_MODELS_PATH = "/api/ide/v1/get_detail_param";
1203
- function classify$1(status) {
1249
+ function classify(status) {
1204
1250
  if (status === 401 || status === 403) return "authentication";
1205
1251
  if (status === 402) return "hard_credit";
1206
1252
  if (status === 429) return "soft_rate";
@@ -1208,13 +1254,21 @@ function classify$1(status) {
1208
1254
  if (status >= 500) return "server";
1209
1255
  return "client";
1210
1256
  }
1257
+ function finitePositive$1(value) {
1258
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : void 0;
1259
+ }
1211
1260
  function prepareSoloBody(source, defaultModel = "glm-5.2") {
1212
- const body = JSON.parse(source);
1213
- const model = typeof body["model"] === "string" && body["model"].trim() !== "" ? body["model"].trim() : defaultModel;
1214
- body["model"] = model;
1215
- body["config_name"] = model;
1216
- body["function"] = TRAE_SOLO_FUNCTION;
1217
- body["stream"] = true;
1261
+ const input = JSON.parse(source);
1262
+ const model = typeof input["model"] === "string" && input["model"].trim() !== "" ? input["model"].trim() : defaultModel;
1263
+ const body = {
1264
+ ...Array.isArray(input["messages"]) ? { messages: input["messages"] } : {},
1265
+ model,
1266
+ config_name: model,
1267
+ function: TRAE_SOLO_FUNCTION,
1268
+ stream: true,
1269
+ ...Array.isArray(input["tools"]) ? { tools: input["tools"] } : {},
1270
+ ...typeof input["reasoning_effort"] === "string" ? { reasoning_effort: input["reasoning_effort"] } : {}
1271
+ };
1218
1272
  if (Array.isArray(body["messages"])) for (const raw of body["messages"]) {
1219
1273
  if (typeof raw !== "object" || raw === null) continue;
1220
1274
  const message = raw;
@@ -1283,8 +1337,11 @@ var TraeSoloUpstreamClient = class {
1283
1337
  const display = typeof config["display_config"] === "object" && config["display_config"] !== null ? config["display_config"] : {};
1284
1338
  const details = Array.isArray(config["model_detail_list"]) ? config["model_detail_list"] : [];
1285
1339
  const detail = typeof details[0] === "object" && details[0] !== null ? details[0] : {};
1286
- const contextWindow = typeof detail["max_input_tokens"] === "number" ? detail["max_input_tokens"] : typeof config["max_input_tokens"] === "number" ? config["max_input_tokens"] : void 0;
1287
- const maxTokens = typeof detail["max_output_tokens"] === "number" ? detail["max_output_tokens"] : typeof config["max_output_tokens"] === "number" ? config["max_output_tokens"] : void 0;
1340
+ const contextTokens = typeof config["context_window_tokens"] === "object" && config["context_window_tokens"] !== null ? config["context_window_tokens"] : {};
1341
+ const promptMaxTokens = finitePositive$1(detail["prompt_max_tokens"]);
1342
+ const devTokens = finitePositive$1(contextTokens["dev"]);
1343
+ const contextWindow = promptMaxTokens ?? devTokens;
1344
+ const maxTokens = finitePositive$1(detail["max_tokens"]);
1288
1345
  const reasoning = parseReasoningCapability({
1289
1346
  ...config,
1290
1347
  ...detail
@@ -1335,10 +1392,17 @@ var TraeSoloUpstreamClient = class {
1335
1392
  response
1336
1393
  };
1337
1394
  const text = (await response.text()).slice(0, 1024);
1395
+ this.options.log?.("dsh-connect-trae: llm_utils_chat rejected", {
1396
+ status: response.status,
1397
+ model: JSON.parse(prepared)["model"],
1398
+ configName: JSON.parse(prepared)["config_name"],
1399
+ reasoningEffort: JSON.parse(prepared)["reasoning_effort"],
1400
+ body: text
1401
+ });
1338
1402
  return {
1339
1403
  ok: false,
1340
1404
  status: response.status,
1341
- kind: classify$1(response.status),
1405
+ kind: classify(response.status),
1342
1406
  message: text || `Trae SOLO returned HTTP ${response.status}`
1343
1407
  };
1344
1408
  }
@@ -1485,7 +1549,8 @@ function bridgeTraeSoloStream(response, model) {
1485
1549
  const encoder = new TextEncoder();
1486
1550
  const sse = new SseDecoder();
1487
1551
  let sawToolCalls = false;
1488
- let emittedDone = false;
1552
+ let emittedFinishReason = false;
1553
+ let upstreamEnded = false;
1489
1554
  let upstreamError;
1490
1555
  let usage;
1491
1556
  const chunk = (delta, finishReason = null) => encoder.encode(`data: ${JSON.stringify({
@@ -1526,13 +1591,16 @@ function bridgeTraeSoloStream(response, model) {
1526
1591
  ...decoded.outputTokens === void 0 ? {} : { completion_tokens: decoded.outputTokens },
1527
1592
  ...decoded.totalTokens === void 0 ? {} : { total_tokens: decoded.totalTokens }
1528
1593
  };
1529
- else if (decoded.type === "done" && !emittedDone) {
1530
- emittedDone = true;
1531
- if (upstreamError !== void 0) {
1532
- controller.error(upstreamError);
1533
- return;
1594
+ else if (decoded.type === "done") {
1595
+ upstreamEnded = true;
1596
+ if (!emittedFinishReason) {
1597
+ emittedFinishReason = true;
1598
+ if (upstreamError !== void 0) {
1599
+ controller.error(upstreamError);
1600
+ return;
1601
+ }
1602
+ controller.enqueue(chunk({}, sawToolCalls ? "tool_calls" : decoded.finishReason || "stop"));
1534
1603
  }
1535
- controller.enqueue(chunk({}, sawToolCalls ? "tool_calls" : decoded.finishReason));
1536
1604
  }
1537
1605
  };
1538
1606
  try {
@@ -1542,11 +1610,14 @@ function bridgeTraeSoloStream(response, model) {
1542
1610
  for (const event of sse.push(decoder.decode(next.value, { stream: true }))) consume(event);
1543
1611
  }
1544
1612
  for (const event of sse.finish()) consume(event);
1545
- if (upstreamError !== void 0 && !emittedDone) {
1613
+ if (upstreamError !== void 0 && !upstreamEnded) {
1546
1614
  controller.error(upstreamError);
1547
1615
  return;
1548
1616
  }
1549
- if (!emittedDone) controller.enqueue(chunk({}, sawToolCalls ? "tool_calls" : "stop"));
1617
+ if (!emittedFinishReason) {
1618
+ emittedFinishReason = true;
1619
+ controller.enqueue(chunk({}, sawToolCalls ? "tool_calls" : "stop"));
1620
+ }
1550
1621
  controller.enqueue(encoder.encode("data: [DONE]\n\n"));
1551
1622
  controller.close();
1552
1623
  } catch (error) {
@@ -1567,14 +1638,34 @@ function bridgeTraeSoloStream(response, model) {
1567
1638
  /** Native SOLO client wrapper used by the loopback OpenAI adapter. */
1568
1639
  var TraeSoloBridge = class {
1569
1640
  upstream;
1570
- constructor(upstream) {
1641
+ catalog;
1642
+ wireResolver;
1643
+ constructor(upstream, catalog, wireResolver) {
1571
1644
  this.upstream = upstream;
1645
+ this.catalog = catalog;
1646
+ this.wireResolver = wireResolver;
1572
1647
  }
1573
1648
  async chatStream(bodyJson, signal) {
1574
1649
  let model = "glm-5.2";
1650
+ let prepared = bodyJson;
1575
1651
  try {
1576
1652
  const input = JSON.parse(bodyJson);
1577
- if (typeof input["model"] === "string" && input["model"] !== "") model = input["model"].endsWith("@1m") ? input["model"].slice(0, -3) : input["model"];
1653
+ if (typeof input["model"] === "string" && input["model"] !== "") model = input["model"];
1654
+ const entry = this.catalog?.current().find((item) => item.id === model);
1655
+ const wireModel = entry?.wireConfigName ?? this.wireResolver?.(model) ?? this.wireResolver?.(entry?.name ?? "") ?? model;
1656
+ if (wireModel !== input["model"]) {
1657
+ input["model"] = wireModel;
1658
+ prepared = JSON.stringify(input);
1659
+ }
1660
+ if (typeof input["reasoning_effort"] === "string") {
1661
+ const efforts = entry?.reasoningEfforts;
1662
+ const requested = input["reasoning_effort"];
1663
+ const mapped = efforts?.[requested];
1664
+ const allowed = efforts === void 0 ? [] : Object.values(efforts).filter((value) => typeof value === "string");
1665
+ if (typeof mapped === "string") input["reasoning_effort"] = mapped;
1666
+ else if (!allowed.includes(requested)) delete input["reasoning_effort"];
1667
+ prepared = JSON.stringify(input);
1668
+ }
1578
1669
  } catch {
1579
1670
  return {
1580
1671
  ok: false,
@@ -1583,7 +1674,7 @@ var TraeSoloBridge = class {
1583
1674
  message: "invalid JSON request"
1584
1675
  };
1585
1676
  }
1586
- const result = await this.upstream.chatStream(bodyJson, signal);
1677
+ const result = await this.upstream.chatStream(prepared, signal);
1587
1678
  if (!result.ok) return result;
1588
1679
  return {
1589
1680
  ok: true,
@@ -1650,147 +1741,20 @@ function parseTraeRemoteModel(value) {
1650
1741
  //#endregion
1651
1742
  //#region src/solo-remote.ts
1652
1743
  const TRAE_SOLO_REMOTE_BASE = "https://solo.trae.cn/api/remote/v1";
1653
- const TRAE_SOLO_REMOTE_MODELS = [
1654
- {
1655
- name: "DeepSeek-V4-Flash",
1656
- displayName: "DeepSeek-V4-Flash",
1657
- multimodal: false
1658
- },
1659
- {
1660
- name: "DeepSeek-V4-Pro",
1661
- displayName: "DeepSeek-V4-Pro",
1662
- multimodal: false
1663
- },
1664
- {
1665
- name: "Doubao_1_6",
1666
- displayName: "Doubao-Seed-Code",
1667
- multimodal: true
1668
- },
1669
- {
1670
- name: "Doubao-Seed-2.0-Code",
1671
- displayName: "Doubao-Seed-2.0-Code",
1672
- multimodal: true
1673
- },
1674
- {
1675
- name: "kimi-k2.6",
1676
- displayName: "Kimi-K2.6",
1677
- multimodal: true
1678
- },
1679
- {
1680
- name: "kimi-k2.5",
1681
- displayName: "Kimi-K2.5",
1682
- multimodal: true
1683
- },
1684
- {
1685
- name: "qwen-3.6-plus",
1686
- displayName: "Qwen3.6-Plus",
1687
- multimodal: true
1688
- },
1689
- {
1690
- name: "qwen-3.5",
1691
- displayName: "Qwen3.5",
1692
- multimodal: true
1693
- },
1694
- {
1695
- name: "glm-5.1",
1696
- displayName: "GLM-5.1",
1697
- multimodal: false
1698
- },
1699
- {
1700
- name: "glm-5",
1701
- displayName: "GLM-5",
1702
- multimodal: false
1703
- },
1704
- {
1705
- name: "glm-5v-turbo",
1706
- displayName: "GLM-5V-Turbo",
1707
- multimodal: true
1708
- },
1709
- {
1710
- name: "minimax-m2.7",
1711
- displayName: "MiniMax-M2.7",
1712
- multimodal: false
1713
- },
1714
- {
1715
- name: "minimax-m2.5",
1716
- displayName: "MiniMax-M2.5",
1717
- multimodal: false
1718
- }
1719
- ];
1720
1744
  /**
1721
- * Preserve DSH's complete text conversation in the one query accepted by the
1722
- * SOLO Remote create-session endpoint. System instructions (including DSH's
1723
- * workspace/cwd context), assistant history, tool-result text and the newest
1724
- * user request stay ordered instead of collapsing to the last user message.
1745
+ * Read-only model catalog client for the SOLO Web API.
1746
+ *
1747
+ * This deliberately has no chat/session method: the Remote session protocol
1748
+ * only exposes a final answer and cannot preserve DSH's structured tool loop.
1725
1749
  */
1726
- function serializeTraeConversation(messages) {
1727
- if (messages.length === 1 && messages[0]?.role === "user") return messages[0].content;
1728
- return messages.map((message) => `<dsh-message role="${message.role}">\n${message.content}\n</dsh-message>`).join("\n\n");
1729
- }
1730
- function decodeUserId(token) {
1731
- try {
1732
- return JSON.parse(Buffer.from(token.split(".")[1] ?? "", "base64").toString("utf8")).data?.id ?? "0";
1733
- } catch {
1734
- return "0";
1735
- }
1736
- }
1737
- function generateWebId() {
1738
- return String(Math.floor(Math.random() * 9e15) + 0x38d7ea4c68000);
1739
- }
1740
- function buildCommonParams(userId, webId, sessionId) {
1741
- return JSON.stringify({
1742
- language: "zh-cn",
1743
- app_language: "en",
1744
- quality: "stable",
1745
- app_version: "1.0.0.1300",
1746
- user_identity: "Free",
1747
- is_freshman: "0",
1748
- scope: "marscode",
1749
- tenant: "marscode",
1750
- region: "CN",
1751
- aiRegion: "CN",
1752
- solo_chat_mode: "code",
1753
- is_privacy_mode: 1,
1754
- privacy_mode: "on",
1755
- ...sessionId === void 0 ? {} : { biz_session_id: sessionId },
1756
- web_id: webId,
1757
- biz_user_id: userId,
1758
- user_unique_id: userId
1759
- });
1760
- }
1761
- function extractContent(raw) {
1762
- try {
1763
- const parsed = JSON.parse(raw);
1764
- if (Array.isArray(parsed)) return parsed.filter((c) => typeof c === "object" && c !== null && c.type === "text").map((c) => c.text_content ?? "").join("");
1765
- if (typeof parsed === "object" && parsed !== null && Array.isArray(parsed.messages)) {
1766
- const parts = [];
1767
- for (const msg of parsed.messages) {
1768
- if (typeof msg !== "object" || msg === null) continue;
1769
- const pi = msg.plan_item;
1770
- if (typeof pi !== "object" || pi === null) continue;
1771
- const tc = pi.tool_call_info;
1772
- if (typeof tc !== "object" || tc === null) continue;
1773
- const tool = tc;
1774
- if (tool.name === "finish" && tool.params?.summary) parts.push(tool.params.summary);
1775
- else if (tool.result?.data?.summary && tool.name !== "finish") parts.push(tool.result.data.summary);
1776
- }
1777
- return parts.join("\n\n");
1778
- }
1779
- } catch {}
1780
- return raw;
1781
- }
1782
- var TraeSoloRemoteClient = class {
1750
+ var TraeSoloRemoteCatalogClient = class {
1783
1751
  options;
1784
1752
  fetchImpl;
1785
1753
  baseUrl;
1786
- pollIntervalMs;
1787
- timeoutMs;
1788
1754
  constructor(options) {
1789
1755
  this.options = options;
1790
1756
  this.fetchImpl = options.fetchImpl ?? fetch;
1791
1757
  this.baseUrl = options.baseUrl ?? "https://solo.trae.cn/api/remote/v1";
1792
- this.pollIntervalMs = options.pollIntervalMs ?? 2e3;
1793
- this.timeoutMs = options.timeoutMs ?? 12e4;
1794
1758
  }
1795
1759
  async headers() {
1796
1760
  return {
@@ -1803,77 +1767,6 @@ var TraeSoloRemoteClient = class {
1803
1767
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
1804
1768
  };
1805
1769
  }
1806
- async chat(messages, model, signal) {
1807
- const headers = await this.headers();
1808
- const token = (await this.options.credential()).accessToken;
1809
- const userId = decodeUserId(token);
1810
- const webId = generateWebId();
1811
- const modelInfo = TRAE_SOLO_REMOTE_MODELS.find((m) => m.name.toLowerCase() === model.toLowerCase()) ?? {
1812
- name: model,
1813
- displayName: model,
1814
- multimodal: false
1815
- };
1816
- const conversation = serializeTraeConversation(messages);
1817
- const body = {
1818
- mode: "code",
1819
- environment_id: "default",
1820
- env: "remote",
1821
- auto_create_project: false,
1822
- origin: "web",
1823
- initial_message: {
1824
- chat_session_id: "",
1825
- content: [],
1826
- query: JSON.stringify([{
1827
- type: "text",
1828
- data: { content: conversation }
1829
- }]),
1830
- model_name: modelInfo.name,
1831
- agent_type: "solo_agent_remote",
1832
- model_selection_strategy: "manual",
1833
- custom_model: {
1834
- name: modelInfo.name,
1835
- multimodal: modelInfo.multimodal,
1836
- is_default: false,
1837
- display_name: modelInfo.displayName,
1838
- config_name: modelInfo.name,
1839
- config_source: 1,
1840
- provider: "",
1841
- ak: "",
1842
- sk: "",
1843
- base_url: "",
1844
- auth_type: 0,
1845
- use_remote_service: true
1846
- },
1847
- common_params: buildCommonParams(userId, webId)
1848
- }
1849
- };
1850
- const createResponse = await this.fetchImpl(`${this.baseUrl}/chat_sessions`, {
1851
- method: "POST",
1852
- headers,
1853
- body: JSON.stringify(body),
1854
- signal: signal ?? AbortSignal.timeout(3e4)
1855
- });
1856
- const createJson = await createResponse.json();
1857
- if (!createResponse.ok || createJson.code !== 0 || !createJson.data?.chat_session_id) throw new Error(`SOLO remote session creation failed (http ${createResponse.status}): ${createJson.message ?? "unknown error"}`);
1858
- const sessionId = createJson.data.chat_session_id;
1859
- const start = Date.now();
1860
- while (Date.now() - start < this.timeoutMs) {
1861
- if (signal?.aborted) throw new Error("SOLO remote polling aborted");
1862
- await new Promise((resolve) => setTimeout(resolve, this.pollIntervalMs));
1863
- const messageResponse = await this.fetchImpl(`${this.baseUrl}/chat_sessions/${sessionId}/messages?page_size=50`, {
1864
- headers,
1865
- ...signal === void 0 ? {} : { signal }
1866
- });
1867
- if (!messageResponse.ok) continue;
1868
- const assistant = ((await messageResponse.json()).data?.items ?? []).find((item) => item.role === "assistant");
1869
- if (assistant && assistant.status !== "in_progress" && assistant.status !== "failed") return {
1870
- content: extractContent(assistant.content ?? ""),
1871
- sessionId
1872
- };
1873
- if (assistant?.status === "failed") throw new Error("SOLO remote assistant message failed");
1874
- }
1875
- throw new Error("SOLO remote polling timed out");
1876
- }
1877
1770
  async fetchModels(signal) {
1878
1771
  const headers = await this.headers();
1879
1772
  const response = await this.fetchImpl(`${this.baseUrl}/models?functions=solo_agent_remote,solo_work_remote`, {
@@ -1891,6 +1784,7 @@ var TraeSoloRemoteClient = class {
1891
1784
  seen.add(model.id);
1892
1785
  models.push(model);
1893
1786
  }
1787
+ if (models.length === 0) throw new Error("SOLO remote models response contained no models");
1894
1788
  return models;
1895
1789
  }
1896
1790
  };
@@ -2239,7 +2133,7 @@ function rawCapabilityDiagnostic(enabled, capability, checkedAtMs) {
2239
2133
  //#endregion
2240
2134
  //#region src/fallback-upstream.ts
2241
2135
  /**
2242
- * Raw Chat first, SOLO Remote second. Fallback is intentionally narrow:
2136
+ * Optional primary route first, native SOLO tool-call route second. Fallback is intentionally narrow:
2243
2137
  * only definitive pre-stream schema/route incompatibilities may retry the same
2244
2138
  * user request. Authentication, credit, rate, cancellation, transport/server
2245
2139
  * failures, and every successful Response are never replayed.
@@ -2606,7 +2500,6 @@ async function traeWebUsage(deps) {
2606
2500
  ...model.input === void 0 ? {} : { input: [...model.input] }
2607
2501
  })),
2608
2502
  enabledModelIds: [...deps.enabledModelIds()],
2609
- enabled1mModelIds: [...deps.enabled1mModelIds()],
2610
2503
  ...deps.rawDiagnostic === void 0 ? {} : { rawChat: deps.rawDiagnostic() }
2611
2504
  };
2612
2505
  try {
@@ -2797,125 +2690,6 @@ function buildTraeFusionRawChatEnvelope(arg, config) {
2797
2690
  };
2798
2691
  }
2799
2692
  //#endregion
2800
- //#region src/solo-remote-bridge.ts
2801
- function classify(status) {
2802
- if (status === 401 || status === 403) return "authentication";
2803
- if (status === 402) return "hard_credit";
2804
- if (status === 429) return "soft_rate";
2805
- if (status === 404) return "not_found";
2806
- if (status >= 500) return "server";
2807
- return "client";
2808
- }
2809
- /**
2810
- * Bridges the SOLO remote polling-based API into the shim's stream interface.
2811
- * The remote API uses create-session + poll-messages rather than raw SSE,
2812
- * so this bridge converts the final result into a synthetic OpenAI SSE stream.
2813
- */
2814
- var TraeSoloRemoteBridge = class {
2815
- remote;
2816
- constructor(remote) {
2817
- this.remote = remote;
2818
- }
2819
- async chatStream(bodyJson, signal) {
2820
- let parsed;
2821
- try {
2822
- parsed = JSON.parse(bodyJson);
2823
- } catch {
2824
- return {
2825
- ok: false,
2826
- status: 400,
2827
- kind: "client",
2828
- message: "invalid JSON request"
2829
- };
2830
- }
2831
- if (!Array.isArray(parsed.messages) || parsed.messages.length === 0) return {
2832
- ok: false,
2833
- status: 400,
2834
- kind: "client",
2835
- message: "messages are required"
2836
- };
2837
- const messages = parsed.messages.flatMap((message) => {
2838
- if (typeof message.role !== "string") return [];
2839
- if (typeof message.content === "string") return [{
2840
- role: message.role,
2841
- content: message.content
2842
- }];
2843
- if (!Array.isArray(message.content)) return [];
2844
- const content = message.content.flatMap((part) => {
2845
- if (typeof part !== "object" || part === null) return [];
2846
- const item = part;
2847
- const text = typeof item.text === "string" ? item.text : typeof item.content === "string" ? item.content : void 0;
2848
- return text === void 0 ? [] : [text];
2849
- }).join("\n");
2850
- return content === "" ? [] : [{
2851
- role: message.role,
2852
- content
2853
- }];
2854
- });
2855
- if (messages.length === 0) return {
2856
- ok: false,
2857
- status: 400,
2858
- kind: "client",
2859
- message: "text messages are required"
2860
- };
2861
- const model = typeof parsed.model === "string" && parsed.model !== "" ? parsed.model : "DeepSeek-V4-Flash";
2862
- const upstreamModel = model.endsWith("@1m") ? model.slice(0, -3) : model;
2863
- try {
2864
- const chunks = buildOpenAISseChunks((await this.remote.chat(messages, upstreamModel, signal)).content, model);
2865
- const encoder = new TextEncoder();
2866
- const stream = new ReadableStream({ start(controller) {
2867
- for (const chunk of chunks) controller.enqueue(encoder.encode(chunk));
2868
- controller.close();
2869
- } });
2870
- return {
2871
- ok: true,
2872
- response: new Response(stream, {
2873
- status: 200,
2874
- headers: { "Content-Type": "text/event-stream" }
2875
- })
2876
- };
2877
- } catch (error) {
2878
- const message = error instanceof Error ? error.message : String(error);
2879
- const status = /timed out/i.test(message) ? 504 : /session creation failed/i.test(message) ? 502 : 502;
2880
- return {
2881
- ok: false,
2882
- status,
2883
- kind: classify(status),
2884
- message: message.slice(0, 400)
2885
- };
2886
- }
2887
- }
2888
- };
2889
- function buildOpenAISseChunks(content, model) {
2890
- const id = `chatcmpl-${randomUUID().replaceAll("-", "").slice(0, 24)}`;
2891
- const created = Math.floor(Date.now() / 1e3);
2892
- const chunks = [];
2893
- if (content !== "") chunks.push(`data: ${JSON.stringify({
2894
- id,
2895
- object: "chat.completion.chunk",
2896
- created,
2897
- model,
2898
- choices: [{
2899
- index: 0,
2900
- delta: { content },
2901
- finish_reason: null
2902
- }]
2903
- })}\n\n`);
2904
- chunks.push(`data: ${JSON.stringify({
2905
- id,
2906
- object: "chat.completion.chunk",
2907
- created,
2908
- model,
2909
- choices: [{
2910
- index: 0,
2911
- delta: {},
2912
- finish_reason: "stop"
2913
- }]
2914
- })}\n\n`);
2915
- chunks.push("data: [DONE]\n\n");
2916
- return chunks;
2917
- }
2918
- //#endregion
2919
2693
  //#region src/upstream.ts
2920
2694
  /**
2921
2695
  * Safe first-stage upstream. Real Trae traffic stays disabled until one
@@ -2955,18 +2729,26 @@ const Config = z.object({
2955
2729
  accountId: z.string().description("Selected local Trae account id (never a token)"),
2956
2730
  lastCatalog: z.array(modelConfig).description("Last refreshed Trae raw model directory shown by the plugin card"),
2957
2731
  enabledModelIds: z.array(z.string()).default([]).description("Trae model ids the user enabled"),
2958
- enabled1mModels: z.array(z.string()).default([]).description("Trae model ids whose 1M variants are enabled"),
2732
+ contextBudgets: z.dict(z.number().step(1).min(1)).default({}).description("Local DSH context budget per Trae model"),
2733
+ imageModelIds: z.array(z.string()).default([]).description("Trae model ids the user explicitly enabled for image input"),
2959
2734
  models: z.array(modelConfig).description("Legacy generated Trae model list")
2960
2735
  });
2961
2736
  function apply(ctx, config) {
2962
2737
  const catalog = new TraeCatalog();
2963
2738
  const enabledSet = (value) => new Set(value.enabledModelIds ?? []);
2964
- const enabled1mSet = (value) => new Set(value.enabled1mModels ?? []);
2965
- const configuredModels = (value) => {
2966
- if (value.lastCatalog?.length) return deriveCatalog(value.lastCatalog, enabledSet(value), enabled1mSet(value));
2967
- return value.models?.length ? value.models : FALLBACK_TRAE_MODELS;
2739
+ const imageSet = (value) => new Set(value.imageModelIds ?? []);
2740
+ const callableKeys = /* @__PURE__ */ new Set();
2741
+ const dropDeadModels = (rows) => {
2742
+ if (callableKeys.size === 0) return rows;
2743
+ return rows.filter((model) => callableKeys.has(model.id.trim().toLowerCase()) || callableKeys.has(model.name.trim().toLowerCase()));
2744
+ };
2745
+ const derive = (value, raw) => {
2746
+ const selectedImages = imageSet(value);
2747
+ const derived = deriveCatalog(applyImageSelection(sanitizeCatalog(dropDeadModels(raw)), selectedImages), enabledSet(value), value.contextBudgets ?? {});
2748
+ return derived.length > 0 ? derived : applyImageSelection(dropDeadModels(FALLBACK_TRAE_MODELS), selectedImages);
2968
2749
  };
2969
- const displayModels = (value) => value.lastCatalog?.length ? value.lastCatalog : value.models?.length ? value.models : FALLBACK_TRAE_MODELS;
2750
+ const configuredModels = (value) => value.lastCatalog?.length ? derive(value, value.lastCatalog) : value.models?.length ? derive(value, value.models) : applyImageSelection(dropDeadModels(FALLBACK_TRAE_MODELS), imageSet(value));
2751
+ const displayModels = (value) => value.lastCatalog?.length ? dropDeadModels(sanitizeCatalog(value.lastCatalog)) : value.models?.length ? dropDeadModels(sanitizeCatalog(value.models)) : dropDeadModels(FALLBACK_TRAE_MODELS);
2970
2752
  const store = new TraeCredentialStore({
2971
2753
  ...config.authFile === void 0 ? {} : { storagePath: config.authFile },
2972
2754
  edition: config.edition ?? "auto",
@@ -2984,10 +2766,14 @@ function apply(ctx, config) {
2984
2766
  const solo = new TraeSoloUpstreamClient({
2985
2767
  credential: () => store.resolve(),
2986
2768
  identity,
2987
- baseUrl: "https://trae-api-cn.mchost.guru"
2769
+ baseUrl: "https://trae-api-cn.mchost.guru",
2770
+ log: (message, detail) => ctx.logger.warn(message, detail)
2988
2771
  });
2989
- const remote = new TraeSoloRemoteClient({ credential: () => store.resolve() });
2990
- const upstream = new TraeSoloBridge(solo);
2772
+ const remoteCatalog = new TraeSoloRemoteCatalogClient({ credential: () => store.resolve() });
2773
+ const wireById = /* @__PURE__ */ new Map();
2774
+ const wireByName = /* @__PURE__ */ new Map();
2775
+ const wireResolver = (displayId) => wireById.get(displayId) ?? wireByName.get(displayId.trim().toLowerCase());
2776
+ const upstream = new TraeSoloBridge(solo, catalog, wireResolver);
2991
2777
  const delegating = new TraeDelegatingUpstreamClient(upstream);
2992
2778
  const shim = createTraeShim({
2993
2779
  catalog,
@@ -3027,41 +2813,32 @@ function apply(ctx, config) {
3027
2813
  ctx.logger.warn("dsh-connect-trae: Raw gateway unavailable; continuing with native SOLO tool-call channel", error);
3028
2814
  }
3029
2815
  })();
3030
- const selectPreferredGeneralAccount = async () => {
3031
- try {
3032
- const accounts = await store.accounts();
3033
- const scored = [];
3034
- for (const account of accounts) try {
3035
- store.selectAccount(account.id);
3036
- const credential = await store.resolve();
3037
- const snapshot = await new TraeUsageClient({ credential: async () => credential }).snapshot();
3038
- const general = Math.round(snapshot.packs.filter((pack) => pack.availableEndpoint === 0).reduce((sum, pack) => sum + Math.max(0, (pack.creditsLimit ?? 0) - (pack.consumedCredits ?? 0)), 0) * 1e4) / 1e4;
3039
- scored.push({
3040
- id: account.id,
3041
- general
3042
- });
3043
- } catch (error) {
3044
- ctx.logger.warn(`dsh-connect-trae: account ${account.id} unavailable for default selection`, error);
3045
- }
3046
- scored.sort((a, b) => b.general - a.general);
3047
- store.setPreferAccountIds(scored.filter((item) => item.general > 0).map((item) => item.id));
3048
- store.setSource(config.authFile, config.edition ?? "auto", config.accountId);
3049
- } catch (error) {
3050
- ctx.logger.warn("dsh-connect-trae: default account selection failed; continuing", error);
3051
- }
3052
- };
3053
2816
  const usageClient = new TraeUsageClient({ credential: () => store.resolve() });
3054
2817
  let current = () => config;
3055
2818
  let invalidateAdapter = () => {};
3056
2819
  const discoverModels = async (signal) => {
3057
- return discoveredCatalog(await remote.fetchModels(signal), /* @__PURE__ */ new Set());
2820
+ const [remote, wire] = await Promise.all([remoteCatalog.fetchModels(signal), solo.fetchModels(signal)]);
2821
+ const merged = mergeTraeModelSources(remote, wire);
2822
+ callableKeys.clear();
2823
+ for (const model of merged) {
2824
+ callableKeys.add(model.id.trim().toLowerCase());
2825
+ callableKeys.add(model.name.trim().toLowerCase());
2826
+ }
2827
+ wireById.clear();
2828
+ wireByName.clear();
2829
+ for (const model of merged) if (model.wireConfigName !== void 0) {
2830
+ wireById.set(model.id, model.wireConfigName);
2831
+ wireByName.set(model.name.trim().toLowerCase(), model.wireConfigName);
2832
+ }
2833
+ const next = applyImageSelection(merged, imageSet(current()));
2834
+ catalog.set(next);
2835
+ return merged;
3058
2836
  };
3059
2837
  ctx.inject(["webServer"], (webCtx) => registerTraeUsageRoute(webCtx, {
3060
2838
  store,
3061
2839
  client: usageClient,
3062
2840
  displayModels: () => displayModels(current()),
3063
2841
  enabledModelIds: () => current().enabledModelIds ?? [],
3064
- enabled1mModelIds: () => current().enabled1mModels ?? [],
3065
2842
  discoverModels,
3066
2843
  rawDiagnostic: () => rawDiagnostic()
3067
2844
  }));
@@ -3083,7 +2860,11 @@ function apply(ctx, config) {
3083
2860
  });
3084
2861
  shim.ready.then(async () => {
3085
2862
  if (stopped) return;
3086
- await selectPreferredGeneralAccount();
2863
+ try {
2864
+ await discoverModels();
2865
+ } catch (error) {
2866
+ ctx.logger.warn("dsh-connect-trae: wire-id resolution failed at startup; falling back to display ids", error);
2867
+ }
3087
2868
  catalog.set(configuredModels(current()));
3088
2869
  const trae = createTraeAdapter({
3089
2870
  shim,
@@ -3099,11 +2880,12 @@ function apply(ctx, config) {
3099
2880
  releaseAdapter = ctx.llm.registerAdapter([TRAE_PROVIDER], trae.adapter);
3100
2881
  ctx.llm.registerModelDiscovery(TRAE_SETTINGS_NS, async (request) => {
3101
2882
  if (request.provider !== "trae") return [];
3102
- return (await discoverModels(request.signal)).map((model) => ({
2883
+ return applyImageSelection(await discoverModels(request.signal), imageSet(current())).map((model) => ({
3103
2884
  id: model.id,
3104
2885
  name: model.name,
3105
2886
  ...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
3106
- ...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens }
2887
+ ...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens },
2888
+ inputModalities: traeInputModalities(model)
3107
2889
  }));
3108
2890
  });
3109
2891
  releaseDirectory = ctx.llm.registerConfigurableProviders([{
@@ -3133,4 +2915,4 @@ function apply(ctx, config) {
3133
2915
  });
3134
2916
  }
3135
2917
  //#endregion
3136
- export { Config, FALLBACK_TRAE_MODELS, SseDecoder, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROVIDER, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, TRAE_SOLO_REMOTE_MODELS, TRAE_STREAM_IDLE_TIMEOUT_MS, TRAE_USAGE_PATH, TRAE_VERSION_CODE_FALLBACK, TraeCatalog, TraeCredentialStore, TraeDelegatingUpstreamClient, TraeFallbackUpstreamClient, TraeGatedUpstreamClient, TraeRawCapabilityController, TraeRawCapabilityState, TraeRawChatUpstreamClient, TraeSoloBridge, TraeSoloRemoteBridge, TraeSoloRemoteClient, TraeSoloUpstreamClient, TraeUsageClient, UnconfiguredTraeUpstreamClient, apply, applyReasoningEffort, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, discoveredCatalog, hashTraeRawChatArg, identityHeaders, inject, name, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, prepareSoloBody, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeIdentity, refreshTraeCredential, registerTraeUsageRoute, resolveTraeRawRuntime, traeEndpoint, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage };
2918
+ export { Config, FALLBACK_TRAE_MODELS, SseDecoder, TRAE_CN_AGENT_TASK_PATH, TRAE_CN_TITLE_PATH, TRAE_MODEL_DETAIL_FUNCTIONS, TRAE_MODEL_DETAIL_PATH, TRAE_PAY_BASE, TRAE_PROVIDER, TRAE_RAW_CHAT_V1_PATH, TRAE_RAW_CHAT_V2_PATH, TRAE_REASONING_EFFORTS, TRAE_SETTINGS_NS, TRAE_SOLO_CHAT_PATH, TRAE_SOLO_FUNCTION, TRAE_SOLO_MODELS_PATH, TRAE_SOLO_REMOTE_BASE, 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, apply, applyContextBudgets, applyImageSelection, applyReasoningEffort, bridgeTraeSoloStream, buildTraeAgentTaskBody, buildTraeCnHeaders, buildTraeFusionRawChatEnvelope, buildTraeModelDetailRequest, buildTraeRawChatDraft, buildTraeRawChatRuntimeConfig, classifyTraeRawChatFailure, createTraeAdapter, createTraeRawGateway, createTraeShim, decodeRawChatChunk, decodeTraeEvent, decryptTraeStorageValue, deriveCatalog, discoveredCatalog, hashTraeRawChatArg, identityHeaders, inject, mergeTraeModelSources, name, normalizeTraeCredential, normalizeTraeVersionCode, parseObservedModelConfig, parseReasoningCapability, parseTraeAuthValue, parseTraeCachedModel, parseTraeModelExtraConfigLogLine, parseTraeRawChatBehaviorConfig, parseTraeRemoteModel, parseTraeStorageDocument, prepareSoloBody, probeTraeRawChatCapability, rawCapabilityDiagnostic, rawCapabilityFingerprint, readTraeCachedModel, readTraeIdentity, refreshTraeCredential, registerTraeUsageRoute, resolveTraeRawRuntime, sanitizeCatalog, traeEndpoint, traeInputModalities, traeOwnAuthPath, traeRawChatExtraInfo, traeStorageCandidates, traeWebUsage };