dsh-connect-trae 1.0.0 → 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,5 +1,44 @@
1
1
  # Changelog
2
2
 
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/` 依旧不进入发布包。
27
+
28
+ ## 1.0.1 (2026-08-31)
29
+
30
+ ### Changes
31
+
32
+ - 将 Cordis 插件运行 ID 从 `llm-trae` 统一调整为 `dsh-connect-trae`,与 npm 包名保持一致。已有 1.0.0 本地安装升级后如保留旧配置条目,需移除旧的 `llm-trae` 实例,避免重复加载。
33
+
34
+ ### Windows support
35
+
36
+ - `src/identity.ts` 现按平台读取 `product.json`:macOS 保持原路径,新增 Windows 路径 `<LOCALAPPDATA>\Programs\<AppName>\resources\app\product.json`(缺失时回退 `<home>\AppData\Local`),Windows 上 `appVersion` 不再恒为 `undefined`,会随请求头发送 `x-app-version` / `x-ide-version`。
37
+ - `src/identity.ts` 的 `osVersion` 在 Windows 上由 `win32 <release>` 规范化为 `Windows <release>`。
38
+ - `src/identity.ts` / `src/model-cache.ts` 路径推导参数化(`platform` / `home` / `env` 可选注入,默认取真实环境),便于跨平台测试;所有平台判断统一使用同一平台源,避免 `x-device-type` 与 `x-os-version` 自相矛盾。
39
+ - `src/model-cache.ts` 的 `state.vscdb` 路径按平台推导;该模块依赖 `sqlite3` 命令行,Windows 默认未安装,Raw Chat 探测会失败并安全回退(Raw Chat 默认关闭,不影响主流程),详见 README「Windows 说明」。
40
+ - 新增 Windows / Linux / `sg` / `solo-sg` 的 identity 测试用例;修复 `tests/identity.spec.ts` 中依赖测试机平台类型的断言(现显式注入平台,Windows 真机与 CI 均可通过)。
41
+
3
42
  ## 1.0.0 (2026-08-30)
4
43
 
5
44
  ### Features
package/README.en.md CHANGED
@@ -1,16 +1,29 @@
1
- # dsh-connect-trae
1
+ <p align="center">
2
+ <img src="docs/assets/dsh-connect-trae-usage-card.png" width="860" alt="dsh-connect-trae settings panel" />
3
+ </p>
2
4
 
3
- [English](README.en.md) | 中文
5
+ <h1 align="center">dsh-connect-trae</h1>
4
6
 
5
- A [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bundle plugin that connects locally signed-in Trae CN models to the DSH model picker. Trae generates structured tool calls while DSH executes its own local tools, with a read-only Work/general credits and model-management panel.
7
+ <p align="center"><b>Connect locally signed-in Trae models to DeepSeek Harness with local DSH tools and a read-only credits overview.</b></p>
6
8
 
7
- ## Preview
9
+ <p align="center">
10
+ <a href="README.md">中文</a> ·
11
+ <a href="#install">Install</a> ·
12
+ <a href="#how-it-works">How it works</a> ·
13
+ <a href="CHANGELOG.md">Changelog</a> ·
14
+ <a href="https://github.com/dingminhua/dsh-connect-trae/issues">Issues</a>
15
+ </p>
8
16
 
9
17
  <p align="center">
10
- <img src="docs/assets/dsh-connect-trae-usage-card.png" alt="DSH Connect Trae plugin card showing account selection, Work and general credits, and model management" width="900">
18
+ <a href="https://www.npmjs.com/package/dsh-connect-trae"><img src="https://img.shields.io/npm/v/dsh-connect-trae?style=flat-square&label=npm&color=cb3837" alt="npm version"></a>
19
+ <a href="https://www.npmjs.com/package/dsh-connect-trae"><img src="https://img.shields.io/npm/dm/dsh-connect-trae?style=flat-square&label=downloads" alt="npm downloads"></a>
20
+ <a href="https://github.com/dingminhua/dsh-connect-trae/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/dingminhua/dsh-connect-trae/ci.yml?branch=main&style=flat-square&label=tests" alt="test status"></a>
21
+ <a href="LICENSE"><img src="https://img.shields.io/github/license/dingminhua/dsh-connect-trae?style=flat-square" alt="MIT license"></a>
22
+ <a href="https://github.com/dingminhua/dsh-connect-trae/stargazers"><img src="https://img.shields.io/github/stars/dingminhua/dsh-connect-trae?style=flat-square" alt="GitHub stars"></a>
23
+ <a href="https://dshfind.com/plugins/dingminhua/dsh-connect-trae"><img src="https://dshfind.com/api/badge/dingminhua/dsh-connect-trae" alt="dshfind plugin"></a>
11
24
  </p>
12
25
 
13
- <p align="center"><sub>Settings → Plugins → DSH Connect Trae: switch local Trae accounts, view Work/general credits, and manage the Trae models enabled in DSH.</sub></p>
26
+ A [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bundle plugin that connects locally signed-in Trae CN models to the DSH model picker. Trae generates structured tool calls while DSH executes its own local tools, with a read-only Work/general credits and model-management panel.
14
27
 
15
28
  ## Features
16
29
 
@@ -50,6 +63,13 @@ npm install dsh-connect-trae
50
63
 
51
64
  Restart the DSH process after install/update/uninstall.
52
65
 
66
+ ## Windows notes
67
+
68
+ - **Account data directory**: the plugin reads `%APPDATA%\Trae CN` / `%APPDATA%\TRAE SOLO CN` → `User\globalStorage\storage.json` (unrelated to the install directory). The folder names match macOS, so no extra config is needed; if the name does not match, point the `authFile` + `edition` plugin options at the exact path.
69
+ - **App version headers**: the plugin reads `appVersion` from `<LOCALAPPDATA>\Programs\<AppName>\resources\app\product.json` and sends `x-app-version` / `x-ide-version`; if unreadable, those headers are omitted (same as the previous behavior).
70
+ - **Raw Chat probing (known limitation)**: `model-cache` depends on the `sqlite3` command-line tool, which is not installed by default on Windows; Raw Chat capability probing fails and falls back safely. Raw Chat is off by default and does not affect the main flow.
71
+ - See `docs/WINDOWS_TOKEN_PROBE.md` for troubleshooting.
72
+
53
73
  ## Development
54
74
 
55
75
  ```sh
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <b>dsh-connect-trae</b>
2
+ <img src="docs/assets/dsh-connect-trae-usage-card.png" width="860" alt="dsh-connect-trae settings panel" />
3
3
  </p>
4
4
 
5
5
  <h1 align="center">dsh-connect-trae</h1>
@@ -16,19 +16,15 @@
16
16
 
17
17
  <p align="center">
18
18
  <a href="https://www.npmjs.com/package/dsh-connect-trae"><img src="https://img.shields.io/npm/v/dsh-connect-trae?style=flat-square&label=npm&color=cb3837" alt="npm version"></a>
19
+ <a href="https://www.npmjs.com/package/dsh-connect-trae"><img src="https://img.shields.io/npm/dm/dsh-connect-trae?style=flat-square&label=downloads" alt="npm downloads"></a>
20
+ <a href="https://github.com/dingminhua/dsh-connect-trae/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/dingminhua/dsh-connect-trae/ci.yml?branch=main&style=flat-square&label=tests" alt="test status"></a>
19
21
  <a href="LICENSE"><img src="https://img.shields.io/github/license/dingminhua/dsh-connect-trae?style=flat-square" alt="MIT license"></a>
22
+ <a href="https://github.com/dingminhua/dsh-connect-trae/stargazers"><img src="https://img.shields.io/github/stars/dingminhua/dsh-connect-trae?style=flat-square" alt="GitHub stars"></a>
23
+ <a href="https://dshfind.com/plugins/dingminhua/dsh-connect-trae"><img src="https://dshfind.com/api/badge/dingminhua/dsh-connect-trae" alt="dshfind plugin"></a>
20
24
  </p>
21
25
 
22
26
  一个独立的 [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) bundle 插件。它把本机已登录的 Trae 中国区账号接到 DSH 的模型选择器:模型负责生成结构化工具调用,`bash` / `read` / `write` / `edit` 等工具由 DSH 本地执行;同时提供**只读**的 Work/通用积分与模型管理界面。
23
27
 
24
- ## 效果预览
25
-
26
- <p align="center">
27
- <img src="docs/assets/dsh-connect-trae-usage-card.png" alt="DSH Connect Trae 插件卡片:Trae 账号、Work 与通用积分以及模型管理" width="900">
28
- </p>
29
-
30
- <p align="center"><sub>设置 → 插件配置 → DSH Connect Trae:切换本机 Trae 账号、查看 Work/通用积分,并管理 DSH 中启用的 Trae 模型。</sub></p>
31
-
32
28
  ## 功能特性
33
29
 
34
30
  - **Trae 模型接入** —— 把本机登录的 Trae 模型注册为 DSH 的 `trae` provider,模型选择器出现 `DeepSeek-V4-Flash`、`DeepSeek-V4-Pro` 等。
@@ -69,6 +65,13 @@ npm install dsh-connect-trae
69
65
 
70
66
  安装、更新或卸载 bundle 后,需要重启对应的 DSH 进程。
71
67
 
68
+ ## Windows 说明
69
+
70
+ - **账号数据目录**:插件读取 `%APPDATA%\Trae CN` / `%APPDATA%\TRAE SOLO CN` 下的 `User\globalStorage\storage.json`(与安装目录无关)。目录名与 macOS 一致,无需额外配置;若目录名对不上,可用插件配置项 `authFile` + `edition` 直接指定完整路径。
71
+ - **应用版本头**:插件从安装目录 `<LOCALAPPDATA>\Programs\<AppName>\resources\app\product.json` 读取 `appVersion`,随请求发送 `x-app-version` / `x-ide-version`;读不到时这些头不发送(与旧版行为一致)。
72
+ - **Raw Chat 探测(已知限制)**:`model-cache` 依赖 `sqlite3` 命令行,Windows 默认未安装,Raw Chat 能力探测会失败并安全回退。Raw Chat 默认关闭,不影响主流程。
73
+ - 排查指引见 `docs/WINDOWS_TOKEN_PROBE.md`。
74
+
72
75
  ## 开发
73
76
 
74
77
  ```sh
package/cordis.patch.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  # Register the Trae provider without changing the profile's default model.
2
2
  - insert:
3
- - id: llm-trae
3
+ - id: dsh-connect-trae
4
4
  name: dsh-connect-trae
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