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/CHANGELOG.md CHANGED
@@ -1,6 +1,29 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 1.1.0 (2026-08-30)
4
+
5
+ ### Fixes
6
+
7
+ - 保持 `llm_utils_chat` / `solo_work_lite` 为模型调用主路径,继续保留 Trae 原生结构化 `tool_calls`,避免模型无法调用 DSH 本地 `read` / `write` / `bash` 工具。
8
+ - 删除只提取最终文本、无法返回 DSH `tool_calls` 的 `TraeSoloRemoteBridge` 及 Remote 会话调用逻辑;保留独立、只读的 `TraeSoloRemoteCatalogClient` 用于刷新完整模型能力目录,类型上不提供聊天方法,防止再次误接为模型调用主路径。
9
+ - 收紧 SOLO 请求为实证字段 allowlist,过滤会触发 `param is invalid` 的 OpenAI 可选字段;推理档位完成 DSH canonical → Trae wire 转换。
10
+ - 修复 `get_detail_param` 解析器字段名:上下文窗口改读 `model_detail_list[].prompt_max_tokens`(回退 `context_window_tokens.dev`)、最大输出改读 `model_detail_list[].max_tokens`。原代码读不存在的 `max_input_tokens` / `max_output_tokens`,导致每个模型的 contextWindow / maxTokens 恒为 undefined。
11
+ - 模型发现合并时剔除「Remote 目录有、但 `get_detail_param` 无对应 `config_name`」的不可调用模型(`Doubao-Seed-Code`、`glm-5.3` 均属此类,发往 `llm_utils_chat` 必返回 `4001 param is invalid`);`FALLBACK_TRAE_MODELS` 移除已下线的 `Doubao-Seed-Code`。
12
+ - 账号选择改为严格绑定用户显式选择:删除启动时按通用积分自动挑选账号的逻辑,未选号时仅用第一个发现的账号;账号失效时不再静默切换到其他账号,而是报「未登录」让用户重新选择,避免账单落到用户未选择的账号上。
13
+
14
+ ### Changes
15
+
16
+ - 模型管理体验与 `dsh-connect-workbuddy` 对齐:一个上游模型只对应一个 DSH 模型 id,移除运行时 `@1m` 变体,改用逐模型 `contextBudgets` 在 Trae 公布的默认 / Max 窗口之间选择。
17
+ - 未勾选任何模型时按完整目录提供模型,避免初次配置或旧设置迁移后 provider 目录为空;旧配置中的 `@1m` 行会被自动过滤。
18
+ - 模型卡片改为右侧上下文预算单选或固定窗口值,并统一显示多模态标记与原始推理档位 `low / high / xhigh`。
19
+ - 模型发现草稿补充与适配器目录一致的 `inputModalities`;当前 DSH 版本可能在核心规范化阶段丢弃该扩展字段,但正式 PiAiAdapter 模型元数据与图片请求链路保持完整。
20
+ - Trae SOLO 请求改为按已验证字段构造 `llm_utils_chat` envelope,过滤 OpenAI 可选参数以避免 `param is invalid`;同时将 DSH 推理档位映射为模型声明的 Trae wire value,并继续规范化 `developer` 为 `system`。
21
+ - 模型发现以 Remote `/models` 目录为骨架,用 `get_detail_param` 补充 `wireConfigName`(`llm_utils_chat` 真正接受的 `config_name`),按 `config_name == id` 优先、`display_name == name` 次之两级 join;join 不到 wire 行的 remote 模型(不可调用)会被剔除,修复 Seed-Code 等模型因展示名与 wire id 不一致导致的 `param is invalid`。
22
+
23
+ ### Docs
24
+
25
+ - 更新 README 顶部插件卡片截图(账号选择、积分卡片与模型列表的当前界面)。
26
+ - 移除仓库内开发期的一次性探测脚本(`scripts/probe-*.mjs`、`scripts/inspect-raw-chat-log.mjs`)及其只读脚本内容断言测试,保持仓库与发布包聚焦产品代码;`lib/`、`tests/` 与 `node_modules/` 依旧不进入发布包。
4
27
 
5
28
  ## 1.0.1 (2026-08-31)
6
29
 
package/lib/client.js CHANGED
@@ -101,10 +101,13 @@ window.__ModuleLoader__.load({
101
101
  .dsm-trae-model-copy{display:flex;align-items:baseline;gap:8px;min-width:0}
102
102
  .dsm-trae-model-name{display:inline-flex;align-items:baseline;gap:7px;color:var(--dsw-alias-label-primary,#e6e6e6);font-size:13px;font-weight:500;line-height:19px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
103
103
  .dsm-trae-model-name-rate{color:var(--dsw-alias-label-tertiary,#999);font-size:11px;font-weight:400;line-height:16px;flex:none}
104
- .dsm-trae-model-id{color:var(--dsw-alias-label-tertiary,#999);font-size:11px;line-height:16px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
105
104
  .dsm-trae-model-meta{display:flex;align-items:center;gap:7px 12px;flex-wrap:wrap;color:var(--dsw-alias-label-tertiary,#999);font-size:11px;line-height:16px}
106
- .dsm-trae-model-toggle{display:inline-flex;align-items:center;gap:5px;color:var(--dsw-alias-label-secondary,#b8b8b8);cursor:pointer;font-size:13px;line-height:19px;font-weight:500}
107
- .dsm-trae-model-toggle input{margin:0;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
105
+ .dsm-trae-model-options{display:flex;align-items:center;justify-content:flex-end;gap:12px;flex:none}
106
+ .dsm-trae-model-image{display:inline-flex;align-items:center;gap:4px;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:11px;line-height:16px;cursor:pointer}
107
+ .dsm-trae-model-image input{margin:0;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
108
+ .dsm-trae-context-budget{display:flex;align-items:center;justify-content:flex-end;gap:12px;flex:none;margin:0;padding:0;border:0;color:var(--dsw-alias-label-secondary,#c6c9d0);font-size:11px;line-height:16px}
109
+ .dsm-trae-context-budget label{display:inline-flex;align-items:center;gap:4px;cursor:pointer}
110
+ .dsm-trae-context-budget input{margin:0;accent-color:var(--dsw-alias-brand-primary,#5686fe)}
108
111
  .dsm-trae-model-capability-note{margin:0;color:var(--dsw-alias-label-tertiary,#999);font-size:12px;line-height:18px}
109
112
  .dsm-trae-model-actions{display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid var(--dsw-alias-border-l2,#36373b);padding-top:12px}
110
113
  .dsm-trae-model-actions-buttons{display:flex;align-items:center;justify-content:flex-end;gap:8px}
@@ -137,7 +140,7 @@ window.__ModuleLoader__.load({
137
140
  function formatNumber(value) {
138
141
  return new Intl.NumberFormat(void 0, {
139
142
  minimumFractionDigits: 0,
140
- maximumFractionDigits: 2
143
+ maximumFractionDigits: 0
141
144
  }).format(value);
142
145
  }
143
146
  function formatDateTime(value) {
@@ -155,11 +158,6 @@ window.__ModuleLoader__.load({
155
158
  if (value >= 1e3 && value % 1e3 === 0) return `${value / 1e3}K`;
156
159
  return formatNumber(value);
157
160
  }
158
- const EFFORT_LABELS = {
159
- low: "轻",
160
- high: "高",
161
- xhigh: "极高"
162
- };
163
161
  function dotStyle(status) {
164
162
  return { background: status === "signed-in" ? "var(--dsw-alias-state-success-primary, #22a06b)" : status === "error" ? "var(--dsw-alias-state-error-primary, #d92d20)" : "var(--dsw-alias-label-dimmed, #9aa0a6)" };
165
163
  }
@@ -175,7 +173,8 @@ window.__ModuleLoader__.load({
175
173
  const [settingsRevision, setSettingsRevision] = (0, react.useState)(0);
176
174
  const [draftModels, setDraftModels] = (0, react.useState)(void 0);
177
175
  const [draftEnabledIds, setDraftEnabledIds] = (0, react.useState)(void 0);
178
- const [draft1mIds, setDraft1mIds] = (0, react.useState)(void 0);
176
+ const [draftImageIds, setDraftImageIds] = (0, react.useState)(void 0);
177
+ const [draftContextBudgets, setDraftContextBudgets] = (0, react.useState)(void 0);
179
178
  const [saving, setSaving] = (0, react.useState)(false);
180
179
  const [switchingAccount, setSwitchingAccount] = (0, react.useState)(false);
181
180
  const mounted = (0, react.useRef)(true);
@@ -269,15 +268,22 @@ window.__ModuleLoader__.load({
269
268
  });
270
269
  const body = await response.json();
271
270
  if (!response.ok || !Array.isArray(body.models)) throw new Error(`HTTP ${response.status}`);
272
- const fresh = body.models.filter((model) => model.maxContext !== true);
273
- const oldEnabled = status.status === "signed-in" ? new Set(status.enabledModelIds) : /* @__PURE__ */ new Set();
274
- const old1m = status.status === "signed-in" ? new Set(status.enabled1mModelIds) : /* @__PURE__ */ new Set();
271
+ const fresh = body.models;
275
272
  const freshIds = new Set(fresh.map((model) => model.id));
276
- const stillEnabled = [...oldEnabled].filter((id) => freshIds.has(id));
277
- const still1m = [...old1m].filter((id) => freshIds.has(id));
278
- setDraftModels(fresh);
273
+ const stillEnabled = [...activeEnabledIds].filter((id) => freshIds.has(id));
274
+ const stillImages = [...activeImageIds].filter((id) => freshIds.has(id));
275
+ const stillBudgets = {};
276
+ for (const id of freshIds) {
277
+ const budget = activeContextBudgets[id];
278
+ if (typeof budget === "number") stillBudgets[id] = budget;
279
+ }
280
+ setDraftModels(fresh.map((model) => ({
281
+ ...model,
282
+ input: ["text"]
283
+ })));
279
284
  setDraftEnabledIds(new Set(stillEnabled));
280
- setDraft1mIds(new Set(still1m));
285
+ setDraftImageIds(new Set(stillImages));
286
+ setDraftContextBudgets(stillBudgets);
281
287
  } catch (error) {
282
288
  if (mounted.current) setStatus({
283
289
  status: "error",
@@ -288,37 +294,50 @@ window.__ModuleLoader__.load({
288
294
  }
289
295
  };
290
296
  const settingsValue = settingsScope?.getSnapshot().value;
291
- new Set(typeof settingsValue === "object" && settingsValue !== null && Array.isArray(settingsValue.enabled1mModels) ? settingsValue.enabled1mModels.filter((value) => typeof value === "string") : []);
297
+ const savedContextBudgets = typeof settingsValue === "object" && settingsValue !== null && typeof settingsValue.contextBudgets === "object" && settingsValue.contextBudgets !== null ? settingsValue.contextBudgets : {};
298
+ const savedImageIds = new Set(typeof settingsValue === "object" && settingsValue !== null && Array.isArray(settingsValue.imageModelIds) ? settingsValue.imageModelIds.filter((id) => typeof id === "string") : []);
292
299
  const visibleModels = draftModels ?? (status.status === "signed-in" ? status.models : []);
293
300
  const savedEnabledIds = status.status === "signed-in" ? new Set(status.enabledModelIds) : /* @__PURE__ */ new Set();
294
- const saved1mIds = status.status === "signed-in" ? new Set(status.enabled1mModelIds) : /* @__PURE__ */ new Set();
295
301
  const activeEnabledIds = draftEnabledIds ?? savedEnabledIds;
296
- const active1mIds = draft1mIds ?? saved1mIds;
297
- const dirty = draftModels !== void 0 || draftEnabledIds !== void 0 || draft1mIds !== void 0;
302
+ const activeImageIds = draftImageIds ?? savedImageIds;
303
+ const activeContextBudgets = draftContextBudgets ?? savedContextBudgets;
304
+ const dirty = draftModels !== void 0 || draftEnabledIds !== void 0 || draftImageIds !== void 0 || draftContextBudgets !== void 0;
298
305
  const toggleModel = (modelId) => {
299
306
  const next = new Set(activeEnabledIds);
300
307
  if (!next.delete(modelId)) next.add(modelId);
301
308
  setDraftEnabledIds(next);
302
309
  setDraftModels([...visibleModels]);
303
310
  };
304
- const toggle1m = (modelId) => {
305
- const next = new Set(active1mIds);
311
+ const toggleImage = (modelId) => {
312
+ const next = new Set(activeImageIds);
306
313
  if (!next.delete(modelId)) next.add(modelId);
307
- setDraft1mIds(next);
314
+ setDraftImageIds(next);
315
+ setDraftModels([...visibleModels]);
316
+ };
317
+ const setContextBudget = (modelId, budget) => {
318
+ const next = { ...activeContextBudgets };
319
+ if (budget === void 0) delete next[modelId];
320
+ else next[modelId] = budget;
321
+ setDraftContextBudgets(next);
308
322
  setDraftModels([...visibleModels]);
309
323
  };
310
324
  const discardModels = () => {
311
325
  setDraftModels(void 0);
312
326
  setDraftEnabledIds(void 0);
313
- setDraft1mIds(void 0);
327
+ setDraftImageIds(void 0);
328
+ setDraftContextBudgets(void 0);
314
329
  };
315
330
  const saveModels = async () => {
316
331
  if (settingsScope === void 0) return;
317
332
  setSaving(true);
318
333
  try {
319
- await settingsScope.set("lastCatalog", visibleModels.filter((model) => model.maxContext !== true));
334
+ await settingsScope.set("lastCatalog", visibleModels.map((model) => ({
335
+ ...model,
336
+ input: ["text"]
337
+ })));
320
338
  await settingsScope.set("enabledModelIds", [...activeEnabledIds]);
321
- await settingsScope.set("enabled1mModels", [...active1mIds].filter((id) => activeEnabledIds.has(id)));
339
+ await settingsScope.set("imageModelIds", [...activeImageIds].filter((id) => activeEnabledIds.has(id)));
340
+ await settingsScope.set("contextBudgets", activeContextBudgets);
322
341
  discardModels();
323
342
  await refreshUsage();
324
343
  } finally {
@@ -507,9 +526,9 @@ window.__ModuleLoader__.load({
507
526
  onChange: () => {
508
527
  toggleModel(model.id);
509
528
  }
510
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
529
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
511
530
  className: "dsm-trae-model-copy",
512
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
531
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
513
532
  className: "dsm-trae-model-name",
514
533
  children: [model.name, model.creditMultiplier === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
515
534
  className: "dsm-trae-model-name-rate",
@@ -519,29 +538,48 @@ window.__ModuleLoader__.load({
519
538
  "x)"
520
539
  ]
521
540
  })]
522
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
523
- className: "dsm-trae-model-id",
524
- children: model.id
525
- })]
541
+ })
542
+ })]
543
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
544
+ className: "dsm-trae-model-options",
545
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
546
+ className: "dsm-trae-model-image",
547
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
548
+ type: "checkbox",
549
+ checked: activeImageIds.has(model.id),
550
+ disabled: settingsScope?.getSnapshot().writable !== true || saving,
551
+ onChange: () => {
552
+ toggleImage(model.id);
553
+ }
554
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelImage") })]
555
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
556
+ className: "dsm-trae-context-budget",
557
+ "aria-label": t("row.contextBudget"),
558
+ children: [model.maxContextWindow !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
559
+ type: "radio",
560
+ name: `context-${model.id}`,
561
+ checked: activeContextBudgets[model.id] !== model.maxContextWindow,
562
+ disabled: settingsScope?.getSnapshot().writable !== true || saving,
563
+ onChange: () => {
564
+ setContextBudget(model.id, model.contextWindow);
565
+ }
566
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatCapacity(model.contextWindow, t("row.modelUnknown")) })] }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
567
+ type: "radio",
568
+ name: `context-${model.id}`,
569
+ checked: model.maxContextWindow === void 0 || activeContextBudgets[model.id] === model.maxContextWindow,
570
+ disabled: model.maxContextWindow === void 0 || settingsScope?.getSnapshot().writable !== true || saving,
571
+ onChange: () => {
572
+ setContextBudget(model.id, model.maxContextWindow);
573
+ }
574
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatCapacity(model.maxContextWindow ?? model.contextWindow, t("row.modelUnknown")) })] })]
526
575
  })]
527
- }), model.maxContextWindow === void 0 || model.maxContext === true ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
528
- className: "dsm-trae-model-toggle",
529
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
530
- type: "checkbox",
531
- checked: active1mIds.has(model.id),
532
- disabled: !activeEnabledIds.has(model.id) || settingsScope?.getSnapshot().writable !== true || busy || saving,
533
- onChange: () => {
534
- toggle1m(model.id);
535
- }
536
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelEnable1m") })]
537
576
  })]
538
577
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
539
578
  className: "dsm-trae-model-meta",
540
579
  children: [
541
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelContext", { context: formatCapacity(model.contextWindow, t("row.modelUnknown")) }) }),
580
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelContext", { context: formatCapacity(model.maxContextWindow ?? model.contextWindow, t("row.modelUnknown")) }) }),
542
581
  model.maxTokens === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelOutput", { output: formatCapacity(model.maxTokens, t("row.modelUnknown")) }) }),
543
- model.maxContext === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelMaxContext") }) : null,
544
- model.reasoning === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelReasoning", { efforts: model.reasoning.supported.map((effort) => EFFORT_LABELS[effort] ?? effort).join(" / ") }) })
582
+ model.reasoning === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("row.modelReasoning", { efforts: model.reasoning.supported.join(" / ") }) })
545
583
  ]
546
584
  })]
547
585
  }, model.id))
@@ -640,15 +678,15 @@ window.__ModuleLoader__.load({
640
678
  "row.discard": "Discard changes",
641
679
  "row.save": "Save",
642
680
  "row.saving": "Saving…",
643
- "row.modelContext": "Context {context}",
681
+ "row.modelContext": "Max context {context}",
682
+ "row.contextBudget": "DSH context budget",
644
683
  "row.modelOutput": "Output {output}",
684
+ "row.modelImage": "Image",
645
685
  "row.modelRate": "{rate}x credits",
646
686
  "row.modelReasoning": "Reasoning: {efforts}",
647
687
  "row.modelReasoningSupported": "Reasoning: supported",
648
688
  "row.modelUnknown": "Unknown",
649
- "row.modelMaxContext": "1M variant",
650
- "row.modelEnable1m": "Enable 1M model",
651
- "row.modelCapabilityPending": "Only capabilities advertised by Trae are shown.",
689
+ "row.modelCapabilityPending": "Image input is enabled only for models you select.",
652
690
  "row.cheer": "Star on GitHub"
653
691
  };
654
692
  const zh = {
@@ -687,15 +725,15 @@ window.__ModuleLoader__.load({
687
725
  "row.discard": "放弃修改",
688
726
  "row.save": "保存",
689
727
  "row.saving": "保存中…",
690
- "row.modelContext": "上下文 {context}",
728
+ "row.modelContext": "最大上下文 {context}",
729
+ "row.contextBudget": "DSH 上下文预算",
691
730
  "row.modelOutput": "最大输出 {output}",
731
+ "row.modelImage": "图片",
692
732
  "row.modelRate": "积分 {rate}x",
693
733
  "row.modelReasoning": "推理强度:{efforts}",
694
734
  "row.modelReasoningSupported": "推理:支持",
695
735
  "row.modelUnknown": "未知",
696
- "row.modelMaxContext": "1M 变体",
697
- "row.modelEnable1m": "启用 1M 模型",
698
- "row.modelCapabilityPending": "仅展示 Trae 接口明确公布的模型能力。",
736
+ "row.modelCapabilityPending": "仅对你手动勾选的模型启用图片输入。",
699
737
  "row.cheer": "鼓励一下"
700
738
  };
701
739
  //#endregion
package/lib/index.d.ts CHANGED
@@ -34,24 +34,88 @@ interface TraeDiscoveredModel {
34
34
  declare function parseTraeRemoteModel(value: unknown): TraeDiscoveredModel | undefined;
35
35
  //#endregion
36
36
  //#region src/catalog.d.ts
37
+ type TraeInputModality = 'text' | 'image';
38
+ /**
39
+ * One model the adapter exposes. `contextWindow` is the effective DSH context
40
+ * after the user's budget; `maxContextWindow` is the native Max window Trae
41
+ * advertises (capability display only — never a second model entry).
42
+ */
37
43
  interface TraeModelInfo {
38
44
  id: string;
39
45
  name: string;
40
46
  contextWindow?: number;
41
47
  maxTokens?: number;
42
- input?: ('text' | 'image')[];
48
+ input?: TraeInputModality[];
43
49
  creditMultiplier?: number;
44
50
  reasoningSupported?: boolean;
45
51
  reasoning?: TraeDiscoveredReasoning;
46
52
  reasoningEfforts?: Partial<Record<TraeReasoningEffort, string | null>>;
47
- baseModelId?: string;
48
- maxContext?: boolean;
49
53
  maxContextWindow?: number;
54
+ /** The `config_name` `llm_utils_chat` accepts; absent means `id` is already the wire id. */
55
+ wireConfigName?: string;
50
56
  }
51
57
  /** Bootstrap catalog: identity only where current Trae metadata has not been fetched yet. */
52
58
  declare const FALLBACK_TRAE_MODELS: readonly TraeModelInfo[];
53
- /** Convert verified Trae API metadata into normal models and optional 1M variants. */
54
- declare function discoveredCatalog(models: readonly TraeDiscoveredModel[], enabled1m?: ReadonlySet<string>): TraeModelInfo[];
59
+ /** Exact DSH modalities for one catalog entry; absent metadata is text-only. */
60
+ declare function traeInputModalities(model: Pick<TraeModelInfo, 'input'>): TraeInputModality[];
61
+ /** Apply the user's explicit image opt-ins; upstream and saved row hints are ignored. */
62
+ declare function applyImageSelection(models: readonly TraeModelInfo[], selected: ReadonlySet<string>): TraeModelInfo[];
63
+ /** One row from `get_detail_param`: the authoritative llm_utils_chat wire id + display name. */
64
+ interface TraeWireModel {
65
+ id: string;
66
+ name: string;
67
+ contextWindow?: number;
68
+ maxTokens?: number;
69
+ reasoning?: TraeReasoningCapability;
70
+ }
71
+ /**
72
+ * Merge the two Trae model sources into one authoritative catalog.
73
+ *
74
+ * `remote` (the solo.trae.cn `/models` directory) is the authoritative model
75
+ * skeleton: it supplies the display id, display name, context windows, credit
76
+ * multiplier, reasoning and multimodal flags. `wire` (from `get_detail_param`)
77
+ * supplies the real `llm_utils_chat` `config_name` — the only id the chat
78
+ * endpoint actually accepts. A remote row is only callable when it maps to a
79
+ * wire `config_name`, so a remote row with no wire match is DROPPED (it would
80
+ * otherwise be sent as an invalid `config_name` and rejected with 4001
81
+ * "param is invalid"). Verified 2026-08-30: the Remote directory advertises
82
+ * `Doubao-Seed-Code` and `glm-5.3`, neither of which is a current `config_name`;
83
+ * both fail every request, so they must not be exposed.
84
+ *
85
+ * Joining is two-tier, in priority order:
86
+ * 1. `wire.id` (the `config_name`) equals the remote id — the model's display
87
+ * id is already its wire id (the common case: glm-5.2, DeepSeek-V4-Flash,
88
+ * kimi-k3, …).
89
+ * 2. `wire.name` (the `display_name`) equals the remote display name — for
90
+ * models whose display id differs from the wire id across Trae versions.
91
+ * When the matched wire `config_name` differs from the remote id it is recorded
92
+ * as `wireConfigName`; otherwise it is left undefined (id is already the wire id).
93
+ */
94
+ declare function mergeTraeModelSources(remote: readonly TraeDiscoveredModel[], wire: readonly TraeWireModel[]): TraeModelInfo[];
95
+ /** Local DSH context budget per model; a value may only select an advertised window. */
96
+ type TraeContextBudget = number;
97
+ /**
98
+ * Apply the saved local budget. Trae advertises two windows per model (dev and
99
+ * Max), so the budget may only switch a model to its own advertised Max value —
100
+ * never to a fabricated number. Everything else keeps the dev window.
101
+ */
102
+ declare function applyContextBudgets(catalog: readonly TraeModelInfo[], budgets?: Readonly<Record<string, TraeContextBudget | undefined>>): TraeModelInfo[];
103
+ /** Convert Trae metadata into text-only model rows; image support is user-owned configuration. */
104
+ declare function discoveredCatalog(models: readonly TraeDiscoveredModel[]): TraeModelInfo[];
105
+ /**
106
+ * Drop rows saved by older releases that generated `@1m` variant models, so a
107
+ * stale configuration cannot resurrect a variant the runtime no longer builds.
108
+ */
109
+ declare function sanitizeCatalog(catalog: readonly TraeModelInfo[]): TraeModelInfo[];
110
+ /**
111
+ * Derive the runtime catalog from the last refreshed Trae directory plus the
112
+ * user's explicit selection and context budgets. An empty selection falls back
113
+ * to the whole directory: a plugin that has never been configured must still
114
+ * serve models rather than nothing. This is the single source of truth for
115
+ * what DSH actually exposes, so saving only the selection and budgets is
116
+ * enough to rebuild it after a restart.
117
+ */
118
+ declare function deriveCatalog(catalog: readonly TraeModelInfo[], enabled: ReadonlySet<string>, budgets?: Readonly<Record<string, TraeContextBudget | undefined>>): TraeModelInfo[];
55
119
  declare class TraeCatalog {
56
120
  private models;
57
121
  current(): readonly TraeModelInfo[];
@@ -161,7 +225,6 @@ declare class TraeCredentialStore {
161
225
  private storagePathOverride;
162
226
  private edition;
163
227
  private accountId;
164
- private preferIds;
165
228
  private readonly ownPath;
166
229
  private readonly refresh;
167
230
  private readonly refreshMarginMs;
@@ -169,15 +232,13 @@ declare class TraeCredentialStore {
169
232
  constructor(options: TraeCredentialStoreOptions);
170
233
  setSource(storagePath: string | undefined, edition?: TraeEdition | 'auto', accountId?: string): void;
171
234
  selectAccount(accountId: string | undefined): void;
235
+ candidates(): TraeStorageCandidate[];
172
236
  /**
173
- * Preferred account ordering used when no account is explicitly selected.
174
- * The connector can only bill the `llm_utils_chat` channel against general
175
- * credits, so accounts with zero general credits should not become the
176
- * default or they fail with 4008 / empty responses.
237
+ * Deterministic default when no account is explicitly selected: the first
238
+ * discovered account. This is NOT credit-seeking — it never reorders accounts
239
+ * to find one with general credits. The plugin bills exactly the account the
240
+ * user selected, or the first account when nothing has been selected yet.
177
241
  */
178
- setPreferAccountIds(ids: readonly string[]): void;
179
- candidates(): TraeStorageCandidate[];
180
- /** First credential matching the preferred ordering, else the first live one. */
181
242
  private preferred;
182
243
  accounts(): Promise<TraeAccountChoice[]>;
183
244
  current(): Promise<TraeCredential | undefined>;
@@ -611,7 +672,7 @@ interface TraeFallbackUpstreamOptions {
611
672
  }>) => void;
612
673
  }
613
674
  /**
614
- * Raw Chat first, SOLO Remote second. Fallback is intentionally narrow:
675
+ * Optional primary route first, native SOLO tool-call route second. Fallback is intentionally narrow:
615
676
  * only definitive pre-stream schema/route incompatibilities may retry the same
616
677
  * user request. Authentication, credit, rate, cancellation, transport/server
617
678
  * failures, and every successful Response are never replayed.
@@ -706,6 +767,7 @@ interface TraeSoloClientOptions {
706
767
  identity(): Promise<TraeIdentity>;
707
768
  baseUrl?: string;
708
769
  fetchImpl?: typeof fetch;
770
+ log?: (message: string, detail?: unknown) => void;
709
771
  }
710
772
  declare class TraeSoloUpstreamClient {
711
773
  private readonly options;
@@ -718,58 +780,39 @@ declare class TraeSoloUpstreamClient {
718
780
  //#region src/solo-bridge.d.ts
719
781
  /** Convert Trae's named SSE events into OpenAI chat-completion SSE chunks. */
720
782
  declare function bridgeTraeSoloStream(response: Response, model: string): Response;
783
+ /** Resolves a display model id to the real llm_utils_chat config_name. */
784
+ type TraeWireResolver = (displayId: string) => string | undefined;
721
785
  /** Native SOLO client wrapper used by the loopback OpenAI adapter. */
722
786
  declare class TraeSoloBridge implements TraeUpstreamClient {
723
787
  private readonly upstream;
724
- constructor(upstream: TraeUpstreamClient);
788
+ private readonly catalog?;
789
+ private readonly wireResolver?;
790
+ constructor(upstream: TraeUpstreamClient, catalog?: Pick<TraeCatalog, "current"> | undefined, wireResolver?: TraeWireResolver | undefined);
725
791
  chatStream(bodyJson: string, signal?: AbortSignal): Promise<TraeChatResult>;
726
792
  }
727
793
  //#endregion
728
794
  //#region src/solo-remote.d.ts
729
795
  declare const TRAE_SOLO_REMOTE_BASE = "https://solo.trae.cn/api/remote/v1";
730
- declare const TRAE_SOLO_REMOTE_MODELS: readonly {
731
- name: string;
732
- displayName: string;
733
- multimodal: boolean;
734
- }[];
735
- interface TraeSoloRemoteOptions {
796
+ interface TraeSoloRemoteCatalogOptions {
736
797
  credential(): Promise<TraeCredential>;
737
798
  fetchImpl?: typeof fetch;
738
799
  baseUrl?: string;
739
- pollIntervalMs?: number;
740
- timeoutMs?: number;
741
- }
742
- interface TraeRemoteInputMessage {
743
- role: string;
744
- content: string;
745
800
  }
746
- declare class TraeSoloRemoteClient {
801
+ /**
802
+ * Read-only model catalog client for the SOLO Web API.
803
+ *
804
+ * This deliberately has no chat/session method: the Remote session protocol
805
+ * only exposes a final answer and cannot preserve DSH's structured tool loop.
806
+ */
807
+ declare class TraeSoloRemoteCatalogClient {
747
808
  private readonly options;
748
809
  private readonly fetchImpl;
749
810
  private readonly baseUrl;
750
- private readonly pollIntervalMs;
751
- private readonly timeoutMs;
752
- constructor(options: TraeSoloRemoteOptions);
811
+ constructor(options: TraeSoloRemoteCatalogOptions);
753
812
  private headers;
754
- chat(messages: readonly TraeRemoteInputMessage[], model: string, signal?: AbortSignal): Promise<{
755
- content: string;
756
- sessionId: string;
757
- }>;
758
813
  fetchModels(signal?: AbortSignal): Promise<TraeDiscoveredModel[]>;
759
814
  }
760
815
  //#endregion
761
- //#region src/solo-remote-bridge.d.ts
762
- /**
763
- * Bridges the SOLO remote polling-based API into the shim's stream interface.
764
- * The remote API uses create-session + poll-messages rather than raw SSE,
765
- * so this bridge converts the final result into a synthetic OpenAI SSE stream.
766
- */
767
- declare class TraeSoloRemoteBridge implements TraeUpstreamClient {
768
- private readonly remote;
769
- constructor(remote: TraeSoloRemoteClient);
770
- chatStream(bodyJson: string, signal?: AbortSignal): Promise<TraeChatResult>;
771
- }
772
- //#endregion
773
816
  //#region src/delegating-upstream.d.ts
774
817
  /** Stable shim dependency whose delegate can be replaced after async setup. */
775
818
  declare class TraeDelegatingUpstreamClient implements TraeUpstreamClient {
@@ -897,10 +940,7 @@ interface TraeWebModel {
897
940
  supported: string[];
898
941
  defaultEffort?: string;
899
942
  };
900
- baseModelId?: string;
901
- maxContext?: boolean;
902
943
  maxContextWindow?: number;
903
- maxContextEnabled?: boolean;
904
944
  }
905
945
  /** Daily check-in status rendered by the card. */
906
946
  interface TraeWebCheckin {
@@ -935,7 +975,6 @@ type TraeWebUsage = {
935
975
  accounts: readonly TraeWebAccount[];
936
976
  models: readonly TraeWebModel[];
937
977
  enabledModelIds: readonly string[];
938
- enabled1mModelIds: readonly string[];
939
978
  rawChat?: {
940
979
  state: 'disabled' | 'unchecked' | 'available' | 'protocol-gated' | 'authentication' | 'credit' | 'rate' | 'transport' | 'server';
941
980
  checkedAtMs?: number;
@@ -953,12 +992,10 @@ type TraeWebUsage = {
953
992
  interface TraeUsageRouteOptions {
954
993
  store: TraeCredentialStore;
955
994
  client: TraeUsageClient;
956
- /** The last-refreshed Trae raw directory (no generated @1m variants) for card display. */
995
+ /** The last-refreshed Trae raw directory (one entry per upstream model) for card display. */
957
996
  displayModels(): readonly TraeModelInfo[];
958
- /** The user's ordinary-model selection stored as model id (= Trae name). */
997
+ /** The user's model selection stored as model id (= Trae name). */
959
998
  enabledModelIds(): readonly string[];
960
- /** The user's 1M selection stored as base-model id. */
961
- enabled1mModelIds(): readonly string[];
962
999
  discoverModels?(signal?: AbortSignal): Promise<readonly TraeModelInfo[]>;
963
1000
  rawDiagnostic?(): TraeRawDiagnostic;
964
1001
  }
@@ -984,12 +1021,14 @@ interface Config {
984
1021
  lastCatalog?: TraeModelInfo[];
985
1022
  /** The user's ordinary-model selection, as model id (= Trae name). */
986
1023
  enabledModelIds?: string[];
987
- /** The user's 1M selection, as base-model id. */
988
- enabled1mModels?: string[];
1024
+ /** Local DSH context budget per model; a value may only select an advertised window. */
1025
+ contextBudgets?: Record<string, number>;
1026
+ /** Models the user explicitly enabled for image input; text is always enabled. */
1027
+ imageModelIds?: string[];
989
1028
  /** Legacy generated runtime catalog; kept for backwards compatibility. */
990
1029
  models?: TraeModelInfo[];
991
1030
  }
992
1031
  declare const Config: z<Config>;
993
1032
  declare function apply(ctx: Context, config: Config): void;
994
1033
  //#endregion
995
- export { Config, FALLBACK_TRAE_MODELS, type RawChatDelta, type RawChatMessage, type RawChatTool, SseDecoder, type SseEvent, 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, type TraeActivityRule, type TraeCachedModelConfig, TraeCatalog, type TraeChatResult, type TraeCheckinStatus, type TraeCredential, TraeCredentialStore, TraeDelegatingUpstreamClient, type TraeDiscoveredModel, type TraeDiscoveredReasoning, type TraeEdition, TraeFallbackUpstreamClient, type TraeFallbackUpstreamOptions, type TraeFusionRawChatEnvelope, TraeGatedUpstreamClient, type TraeGatedUpstreamOptions, type TraeIdentity, type TraeModelDetailRequest, type TraeModelInfo, type TraeObservedModelConfig, TraeRawCapabilityController, type TraeRawCapabilityControllerOptions, type TraeRawCapabilitySnapshot, TraeRawCapabilityState, type TraeRawChatBehaviorConfig, type TraeRawChatCapability, type TraeRawChatClientOptions, type TraeRawChatConfig, type TraeRawChatFailureReason, type TraeRawChatRuntimeConfig, TraeRawChatUpstreamClient, type TraeRawDiagnostic, type TraeRawDiagnosticState, type TraeRawGateway, type TraeRawGatewayOptions, type TraeRawResolverResult, type TraeReasoningCapability, type TraeReasoningEffort, type TraeShim, TraeSoloBridge, type TraeSoloClientOptions, TraeSoloRemoteBridge, TraeSoloRemoteClient, type TraeSoloRemoteOptions, TraeSoloUpstreamClient, type TraeStorageCandidate, type TraeStreamEvent, type TraeUpstreamClient, TraeUsageClient, type TraeUsageOptions, type TraeUsagePack, type TraeUsageRouteOptions, type TraeUsageSnapshot, type TraeUsageSummary, type TraeUsageView, type TraeWebActivity, type TraeWebCheckin, type TraeWebCredits, type TraeWebUsage, 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 };
1034
+ export { Config, FALLBACK_TRAE_MODELS, type RawChatDelta, type RawChatMessage, type RawChatTool, SseDecoder, type SseEvent, 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, type TraeActivityRule, type TraeCachedModelConfig, TraeCatalog, type TraeChatResult, type TraeCheckinStatus, type TraeContextBudget, type TraeCredential, TraeCredentialStore, TraeDelegatingUpstreamClient, type TraeDiscoveredModel, type TraeDiscoveredReasoning, type TraeEdition, TraeFallbackUpstreamClient, type TraeFallbackUpstreamOptions, type TraeFusionRawChatEnvelope, TraeGatedUpstreamClient, type TraeGatedUpstreamOptions, type TraeIdentity, type TraeInputModality, type TraeModelDetailRequest, type TraeModelInfo, type TraeObservedModelConfig, TraeRawCapabilityController, type TraeRawCapabilityControllerOptions, type TraeRawCapabilitySnapshot, TraeRawCapabilityState, type TraeRawChatBehaviorConfig, type TraeRawChatCapability, type TraeRawChatClientOptions, type TraeRawChatConfig, type TraeRawChatFailureReason, type TraeRawChatRuntimeConfig, TraeRawChatUpstreamClient, type TraeRawDiagnostic, type TraeRawDiagnosticState, type TraeRawGateway, type TraeRawGatewayOptions, type TraeRawResolverResult, type TraeReasoningCapability, type TraeReasoningEffort, type TraeShim, TraeSoloBridge, type TraeSoloClientOptions, TraeSoloRemoteCatalogClient, type TraeSoloRemoteCatalogOptions, TraeSoloUpstreamClient, type TraeStorageCandidate, type TraeStreamEvent, type TraeUpstreamClient, TraeUsageClient, type TraeUsageOptions, type TraeUsagePack, type TraeUsageRouteOptions, type TraeUsageSnapshot, type TraeUsageSummary, type TraeUsageView, type TraeWebActivity, type TraeWebCheckin, type TraeWebCredits, type TraeWebUsage, type TraeWireModel, 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 };