@mars-sea/dsh-commandcode-provider 0.10.4 → 0.10.5
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 +15 -0
- package/README.md +24 -4
- package/README.zh-CN.md +24 -4
- package/lib/client.js +1 -1
- package/lib/index.d.ts +48 -3
- package/lib/index.js +150 -27
- package/lib/index.js.map +1 -1
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.10.5] - 2026-09-11
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **The terminal settings page lists every model as a checkbox.** The TUI's model allowlist was a text field expecting comma-separated catalog ids typed from memory; it now renders the whole catalog under plan-tier headings (Go → GOAT → Pro → Provider), free models first, each row carrying its plan/deal/context summary as the footer hint. Everything reads as checked until something is excluded, matching the adapter's "an unset allowlist shows every model" rule, and a model this build does not recognize (retired upstream, or added after this release) still gets a row of its own instead of becoming impossible to switch from the terminal.
|
|
11
|
+
- **`Config.modelVisibility`, a per-model override layer beside `visibleModels`.** The TUI cannot add an id to the array it edits, so each checkbox records `modelVisibility.<id>` (true listed / false hidden) instead; an id absent from the map keeps following `visibleModels`, and the subscription-tier filter still applies on top. A hand-edited document carrying a non-boolean entry falls back to the array rather than hiding the model.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- **The terminal model checkboxes now save the model you toggled.** dsh-TUI keys a staged edit by the field's PATH, so the whole list of checkboxes sharing one `visibleModels` path shared one draft: each of them parsed that same draft on save, every write op addressed the same path, and only the last field's op survived — silently replacing the allowlist with what the last catalog model's row evaluated to instead of the row the user had switched. Each checkbox now addresses a path of its own, and a toggle writes only when it disagrees with the inherited state, so switching a model back leaves no residue in the document.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- **Per-release compatibility now covers dsh `0.1.5-rc.2`.** `dsh.compatibility.dshReleases` declares it `compatible` alongside `0.1.2-rc.1`, `0.1.3-alpha.1`, `0.1.3-alpha.2`, `0.1.5-alpha.1` and `0.1.5-rc.1`, so the DSH STORE can list the plugin for the current Harness release. Verified on a dsh-TUI profile running that engine: the `/settings` → Command Code page, the model checkbox list, and the picker's catalog all behave as they do on 0.1.5-rc.1. An older release keeps its own record rather than being replaced, so a user who has not moved to the newest engine still sees the listing.
|
|
18
|
+
|
|
19
|
+
### Documentation
|
|
20
|
+
- **The install and update instructions now say which profile receives the plugin and how to escape pnpm's release-age gate** (both READMEs). A `dsh-tui` profile owns its own plugin list, so the web install does not reach the terminal: the terminal UI needs its own `dsh plugin --profile dsh-tui add`, and it now shows an exact version because a bare package name — or `@latest` — is silently resolved to the previous release for the first 24 hours after a publish (pnpm 11's `minimumReleaseAge`) and the install still exits successfully, which is how a fresh terminal install can end up with no Command Code settings page and no `commandcode` models at all.
|
|
21
|
+
|
|
7
22
|
## [0.10.4] - 2026-09-11
|
|
8
23
|
|
|
9
24
|
### Added
|
package/README.md
CHANGED
|
@@ -47,7 +47,15 @@ Pick the release line that matches your DeepSeek Harness version:
|
|
|
47
47
|
dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.9.1
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
> The `latest` tag
|
|
50
|
+
> The `latest` tag points at the current 0.1.2-line plugin release. Users on the old 0.5.0-era Harness line must pin `@0.9.1` explicitly.
|
|
51
|
+
|
|
52
|
+
**pnpm 11 holds back new releases.** Its `minimumReleaseAge` defaults to 1440 minutes, so a version published less than a day ago is skipped and `@latest` resolves to the *previous* release — silently, with a success exit code. To install a release from the last 24 hours, name it exactly:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.10.5
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The same applies to every profile you install into, including the terminal UI below.
|
|
51
59
|
|
|
52
60
|
Fresh pnpm 10 marketplace generations are supported directly. Do not add a separate `@deepseek-ai/dsh-invariants` dependency; the plugin declares it as a Host peer so the active dsh profile remains the owner of Harness packages.
|
|
53
61
|
|
|
@@ -60,6 +68,14 @@ dsh plugin --profile web update @mars-sea/dsh-commandcode-provider@latest #
|
|
|
60
68
|
dsh plugin --profile web update @mars-sea/dsh-commandcode-provider@0.9.1 # older dsh (0.5.0 line, unmaintained)
|
|
61
69
|
```
|
|
62
70
|
|
|
71
|
+
Each profile updates separately — the terminal UI owns its own plugin list (see below):
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
dsh plugin --profile dsh-tui update @mars-sea/dsh-commandcode-provider@0.10.5
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
To move to a version published less than 24 hours ago, name it exactly as in Install above; pnpm 11's age gate resolves `@latest` to the previous release instead.
|
|
78
|
+
|
|
63
79
|
Then restart the web app.
|
|
64
80
|
|
|
65
81
|
## Getting an API key
|
|
@@ -81,12 +97,14 @@ After restart, enter your API key in **Settings → Command Code** and save; **S
|
|
|
81
97
|
|
|
82
98
|
## Terminal UI (dsh-TUI)
|
|
83
99
|
|
|
84
|
-
The plugin also works under a terminal front door
|
|
100
|
+
The plugin also works under a terminal front door. **Each dsh profile owns its own plugin list**, so the web install above does not reach the terminal — add the plugin to the `dsh-tui` profile as well:
|
|
85
101
|
|
|
86
102
|
```sh
|
|
87
|
-
dsh plugin --profile dsh-tui add @mars-sea/dsh-commandcode-provider
|
|
103
|
+
dsh plugin --profile dsh-tui add @mars-sea/dsh-commandcode-provider@0.10.5
|
|
88
104
|
```
|
|
89
105
|
|
|
106
|
+
Pin the exact version here. For the first 24 hours after a release, a bare package name (or `@latest`) is silently resolved to the previous one: the install succeeds, but the profile gets the older build — which is how a fresh terminal install ends up with no **`/settings` → Command Code** page and no `commandcode` models at all.
|
|
107
|
+
|
|
90
108
|
Then pick the provider in the model selector, or name it directly:
|
|
91
109
|
|
|
92
110
|
```text
|
|
@@ -95,7 +113,9 @@ Then pick the provider in the model selector, or name it directly:
|
|
|
95
113
|
|
|
96
114
|
`/model` lists every registered provider, with Command Code's live catalog and its plan/deal/context annotations. The `/commandcode` usage dashboard works there too.
|
|
97
115
|
|
|
98
|
-
**Entering the key.** The TUI has no web Models page, so the plugin declares its own page in the TUI settings screen — **`/settings` → Command Code** — with the API key, the API base, the out-of-plan model filter, the
|
|
116
|
+
**Entering the key.** The TUI has no web Models page, so the plugin declares its own page in the TUI settings screen — **`/settings` → Command Code** — with the API key, the API base, the out-of-plan model filter, the active account, and the command language. The key field is write-only: it shows whether a key is configured and writes what you type to the credential store, never to a settings document. The same page is the only place a TUI-only user needs to visit.
|
|
117
|
+
|
|
118
|
+
**Picking models.** That page lists the whole catalog as checkboxes, grouped by plan tier (Go → GOAT → Pro → Provider) with the free models first, so nothing has to be typed from memory. Everything starts checked, because an unset allowlist means "show every model" — uncheck the ones you do not want in the picker. The choice is stored as per-model overrides next to the `visibleModels` list the web page edits, so both surfaces can be used interchangeably and a plain `visibleModels` written by hand still works.
|
|
99
119
|
|
|
100
120
|
Alternatively, set the key outside the TUI — any of these work, in this order of precedence:
|
|
101
121
|
|
package/README.zh-CN.md
CHANGED
|
@@ -45,7 +45,15 @@
|
|
|
45
45
|
dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.9.1
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
> `latest` tag
|
|
48
|
+
> `latest` tag 指向当前 0.1.2 线的插件版本。旧 0.5.0 时代的 Harness 用户必须显式钉住 `@0.9.1`。
|
|
49
|
+
|
|
50
|
+
**pnpm 11 会拦下刚发布的新版本。** 它的 `minimumReleaseAge` 默认为 1440 分钟,发布不足一天的版本会被跳过,`@latest` 解析到**上一个**版本 —— 而且是静默的,命令照样以成功退出。想装 24 小时内发布的版本,必须写精确版本号:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
dsh plugin --profile web add @mars-sea/dsh-commandcode-provider@0.10.5
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
这一点对每个 profile 都成立,包括下面的终端界面。
|
|
49
57
|
|
|
50
58
|
插件可直接在 pnpm 10 的全新插件市场 generation 中安装。不要另行添加 `@deepseek-ai/dsh-invariants` dependency;插件已将其声明为 Host peer,Harness 包仍由当前 dsh profile 统一管理。
|
|
51
59
|
|
|
@@ -58,6 +66,14 @@ dsh plugin --profile web update @mars-sea/dsh-commandcode-provider@latest #
|
|
|
58
66
|
dsh plugin --profile web update @mars-sea/dsh-commandcode-provider@0.9.1 # 更早的 dsh(0.5.0 线,不再维护)
|
|
59
67
|
```
|
|
60
68
|
|
|
69
|
+
每个 profile 各自更新 —— 终端界面有独立的插件列表(见下文):
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
dsh plugin --profile dsh-tui update @mars-sea/dsh-commandcode-provider@0.10.5
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
要更新到发布不足 24 小时的版本,和上面的安装一样写精确版本号;pnpm 11 的年龄门禁会把 `@latest` 解析成上一个版本。
|
|
76
|
+
|
|
61
77
|
然后重启 Web 应用。
|
|
62
78
|
|
|
63
79
|
## 获取 API key
|
|
@@ -79,12 +95,14 @@ cmd login # macOS/Linux;Windows 原生版:cmdc login
|
|
|
79
95
|
|
|
80
96
|
## 终端界面(dsh-TUI)
|
|
81
97
|
|
|
82
|
-
|
|
98
|
+
插件同样支持终端前端。**每个 dsh profile 有独立的插件列表**,所以上面那条 Web 安装命令不会装到终端里 —— 还要把插件装进 `dsh-tui` profile:
|
|
83
99
|
|
|
84
100
|
```sh
|
|
85
|
-
dsh plugin --profile dsh-tui add @mars-sea/dsh-commandcode-provider
|
|
101
|
+
dsh plugin --profile dsh-tui add @mars-sea/dsh-commandcode-provider@0.10.5
|
|
86
102
|
```
|
|
87
103
|
|
|
104
|
+
这里请写精确版本号。新版本发布后的 24 小时内,只写包名(或 `@latest`)会被静默解析到上一个版本 —— 安装命令照样成功,但 profile 里拿到的是旧版本,结果就是全新的终端安装里既没有 **`/settings` → Command Code** 页面,也看不到任何 `commandcode` 模型。
|
|
105
|
+
|
|
88
106
|
之后在模型选择器里选,或者直接指定:
|
|
89
107
|
|
|
90
108
|
```text
|
|
@@ -93,7 +111,9 @@ dsh plugin --profile dsh-tui add @mars-sea/dsh-commandcode-provider
|
|
|
93
111
|
|
|
94
112
|
`/model` 会列出所有已注册的 provider,Command Code 的实时目录连同套餐/优惠/上下文标注一起出现,`/commandcode` 用量面板在终端里同样可用。
|
|
95
113
|
|
|
96
|
-
**填写 API key。** 终端没有网页版 Models 页面,所以插件会在终端设置页里声明自己的页面 —— **`/settings` → Command Code** —— 包含 API key、API
|
|
114
|
+
**填写 API key。** 终端没有网页版 Models 页面,所以插件会在终端设置页里声明自己的页面 —— **`/settings` → Command Code** —— 包含 API key、API 地址、隐藏套餐外模型、当前账号和命令语言。key 字段是只写的:它只显示"是否已配置",输入的内容写进凭据库,不会写进任何 settings 文档。只用终端的用户只需要访问这一个页面。
|
|
115
|
+
|
|
116
|
+
**选择模型。** 同一页面把整个模型目录列成勾选框,按套餐档位分组(Go → GOAT → Pro → Provider),免费模型排在最前,不需要手打任何模型 id。默认全部勾选——未设置白名单就等于"显示全部模型";把不想在选择器里看到的取消勾选即可。选择会以「单模型覆盖」的形式保存在网页端编辑的 `visibleModels` 列表旁边,两个界面可以混用,手写的 `visibleModels` 也照常生效。
|
|
97
117
|
|
|
98
118
|
也可以在终端外配置 key,以下三种方式按优先级生效:
|
|
99
119
|
|
package/lib/client.js
CHANGED
|
@@ -2051,7 +2051,7 @@ window.__ModuleLoader__.load({
|
|
|
2051
2051
|
}
|
|
2052
2052
|
//#endregion
|
|
2053
2053
|
//#region package.json
|
|
2054
|
-
var version = "0.10.
|
|
2054
|
+
var version = "0.10.5";
|
|
2055
2055
|
var repository = {
|
|
2056
2056
|
"type": "git",
|
|
2057
2057
|
"url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
|
package/lib/index.d.ts
CHANGED
|
@@ -52,6 +52,16 @@ interface CommandCodeConnectionOptions {
|
|
|
52
52
|
* catalog regardless so the page can always offer every model.
|
|
53
53
|
*/
|
|
54
54
|
visibleModels?: string[] | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Per-model visibility overrides, keyed by catalog id. Written by the
|
|
57
|
+
* terminal settings page, whose checkbox list gives every model its own
|
|
58
|
+
* boolean field: a field writes one value at one path, so membership in
|
|
59
|
+
* {@link visibleModels} cannot be expressed from there, while a flag can.
|
|
60
|
+
* An id present here decides that model on its own (true = listed, false =
|
|
61
|
+
* hidden); an id absent here follows {@link visibleModels} exactly as it did
|
|
62
|
+
* before, so composition configs and the web page are unaffected.
|
|
63
|
+
*/
|
|
64
|
+
modelVisibility?: Readonly<Record<string, boolean>> | undefined;
|
|
55
65
|
/**
|
|
56
66
|
* Optional protocol hint. `'auto'` (default) uses billing/cache plus
|
|
57
67
|
* Provider API fallback; `'cli'` forces `/alpha/generate`; `'openai'`
|
|
@@ -1277,6 +1287,17 @@ interface TuiSettingsSectionsService {
|
|
|
1277
1287
|
declare const ACTIVE_ACCOUNT_AUTO = "auto";
|
|
1278
1288
|
/** The selector value meaning "no language override — follow the shell locale". */
|
|
1279
1289
|
declare const LANG_AUTO = "auto";
|
|
1290
|
+
/** One catalog model offered as a checkbox. */
|
|
1291
|
+
interface TuiModelChoice {
|
|
1292
|
+
/** Catalog model id, e.g. `deepseek/deepseek-v4-pro`. */
|
|
1293
|
+
readonly id: string;
|
|
1294
|
+
/** Minimum plan tier key from `KNOWN_PLANS`. */
|
|
1295
|
+
readonly tier: string;
|
|
1296
|
+
/** Whether the model is currently free, so it leads its group. */
|
|
1297
|
+
readonly free: boolean;
|
|
1298
|
+
/** Footer hint for the focused row: plan tier · deal · peak · `Image`. */
|
|
1299
|
+
readonly hint: string;
|
|
1300
|
+
}
|
|
1280
1301
|
/** Everything the section needs from the plugin entry. */
|
|
1281
1302
|
interface CommandCodeTuiSettingsDeps {
|
|
1282
1303
|
/** The plugin's settings namespace (`llm-commandcode`). */
|
|
@@ -1298,6 +1319,21 @@ interface CommandCodeTuiSettingsDeps {
|
|
|
1298
1319
|
id: string;
|
|
1299
1320
|
label: string;
|
|
1300
1321
|
}[];
|
|
1322
|
+
/**
|
|
1323
|
+
* The stored model allowlist, read LIVE at save time. A checkbox judges its
|
|
1324
|
+
* inherited state against this, so it must be read when the write runs, not
|
|
1325
|
+
* captured when the section was registered. An empty list means "every model
|
|
1326
|
+
* is visible" (the adapter's rule).
|
|
1327
|
+
*/
|
|
1328
|
+
visibleModels: () => readonly string[];
|
|
1329
|
+
/**
|
|
1330
|
+
* The per-model override map the checkboxes write, read per registration so
|
|
1331
|
+
* ids this build's catalog does not know still get a row of their own. Same
|
|
1332
|
+
* live-read rule as {@link visibleModels}.
|
|
1333
|
+
*/
|
|
1334
|
+
modelVisibility?: () => Readonly<Record<string, boolean>> | undefined;
|
|
1335
|
+
/** The models to offer as checkboxes; defaults to the static snapshot. */
|
|
1336
|
+
modelChoices?: () => readonly TuiModelChoice[];
|
|
1301
1337
|
}
|
|
1302
1338
|
/**
|
|
1303
1339
|
* Build the section descriptor. Pure, so tests can pin the exact fields
|
|
@@ -1308,9 +1344,9 @@ interface CommandCodeTuiSettingsDeps {
|
|
|
1308
1344
|
* cannot express "unset" — cycling only ever lands on a declared option, so a
|
|
1309
1345
|
* `select` would strand the user on a pinned value with no way back to
|
|
1310
1346
|
* automatic. The `auto` sentinel plus a `parse` that clears the path keeps the
|
|
1311
|
-
* unset state reachable.
|
|
1312
|
-
* (
|
|
1313
|
-
*
|
|
1347
|
+
* unset state reachable. `filterModelsByPlan` formats its EFFECTIVE default
|
|
1348
|
+
* (unset means true at the adapter), so a fresh install reads true instead of
|
|
1349
|
+
* the screen's "(empty)".
|
|
1314
1350
|
*/
|
|
1315
1351
|
declare function buildCommandCodeTuiSection(deps: CommandCodeTuiSettingsDeps): TuiSettingsSection;
|
|
1316
1352
|
/**
|
|
@@ -1367,6 +1403,15 @@ interface Config {
|
|
|
1367
1403
|
* filter card; applies after the subscription-tier filter.
|
|
1368
1404
|
*/
|
|
1369
1405
|
visibleModels?: string[];
|
|
1406
|
+
/**
|
|
1407
|
+
* Per-model visibility overrides from the terminal settings page's checkbox
|
|
1408
|
+
* list, keyed by catalog id (`true` = listed, `false` = hidden). An id here
|
|
1409
|
+
* decides that model on its own; an id absent here follows `visibleModels`.
|
|
1410
|
+
* dsh-TUI keys a staged edit by the field's path, so the checkboxes need one
|
|
1411
|
+
* path per model — a map — because a boolean field cannot express "this id
|
|
1412
|
+
* is a member of the array".
|
|
1413
|
+
*/
|
|
1414
|
+
modelVisibility?: Record<string, boolean>;
|
|
1370
1415
|
/**
|
|
1371
1416
|
* Extra accounts for multi-account rotation. The top-level
|
|
1372
1417
|
* `apiKey`/`apiKeyEnv` (plus the CLI auth file) always form the first
|
package/lib/index.js
CHANGED
|
@@ -1875,7 +1875,12 @@ var CommandCodeAdapter = class extends LlmAdapter {
|
|
|
1875
1875
|
const access = this.deps.options().filterModelsByPlan === false ? void 0 : await this.loadBillingAccess();
|
|
1876
1876
|
const visible = this.deps.options().visibleModels;
|
|
1877
1877
|
const allow = Array.isArray(visible) && visible.length > 0 ? new Set(visible.filter((id) => typeof id === "string" && id !== "")) : void 0;
|
|
1878
|
-
|
|
1878
|
+
const overrides = this.deps.options().modelVisibility;
|
|
1879
|
+
return catalog.filter((model) => modelVisibleInPlan(model.id, access)).filter((model) => {
|
|
1880
|
+
const override = overrides?.[model.id];
|
|
1881
|
+
if (typeof override === "boolean") return override;
|
|
1882
|
+
return allow === void 0 || allow.has(model.id);
|
|
1883
|
+
}).map(toInfo).sort(compareByPlan);
|
|
1879
1884
|
}
|
|
1880
1885
|
async resolveModel(provider, model, signal) {
|
|
1881
1886
|
const entry = this.catalog.find((m) => m.id === model) ?? (await this.loadCatalog(signal)).find((m) => m.id === model);
|
|
@@ -3786,6 +3791,57 @@ var CommandCodeSearchProvider = class {
|
|
|
3786
3791
|
const ACTIVE_ACCOUNT_AUTO = "auto";
|
|
3787
3792
|
/** The selector value meaning "no language override — follow the shell locale". */
|
|
3788
3793
|
const LANG_AUTO = "auto";
|
|
3794
|
+
/** Tier display names, mirroring the web dropdown's headings (`model-select.ts`). */
|
|
3795
|
+
const TIER_TITLES = {
|
|
3796
|
+
go: "Go",
|
|
3797
|
+
goat: "GOAT",
|
|
3798
|
+
pro: "Pro",
|
|
3799
|
+
provider: "Provider",
|
|
3800
|
+
max: "Max"
|
|
3801
|
+
};
|
|
3802
|
+
/** Tiers in picker order; a model outside this set joins the "Other" group. */
|
|
3803
|
+
const TIER_ORDER = [
|
|
3804
|
+
"go",
|
|
3805
|
+
"goat",
|
|
3806
|
+
"pro",
|
|
3807
|
+
"provider",
|
|
3808
|
+
"max"
|
|
3809
|
+
];
|
|
3810
|
+
/** The group holding models this build's catalog does not know. */
|
|
3811
|
+
const OTHER_GROUP_ID = "models-other";
|
|
3812
|
+
/**
|
|
3813
|
+
* Every model this build knows, in checkbox order: plan tier (Go first), then
|
|
3814
|
+
* free before paid inside a tier, then by id.
|
|
3815
|
+
*
|
|
3816
|
+
* The list is the static capability snapshot rather than a live catalog read
|
|
3817
|
+
* on purpose — a settings page must draw synchronously, and the snapshot is
|
|
3818
|
+
* synced from the same upstream table the picker's tier headings come from.
|
|
3819
|
+
* A model added upstream after this build still reaches the user: an empty
|
|
3820
|
+
* allowlist shows everything, and a model named in the allowlist but absent
|
|
3821
|
+
* here is rendered by the "Other" group instead of disappearing.
|
|
3822
|
+
*/
|
|
3823
|
+
function commandCodeTuiModelChoices() {
|
|
3824
|
+
return Object.keys(KNOWN_PLANS).map((id) => ({
|
|
3825
|
+
id,
|
|
3826
|
+
tier: KNOWN_PLANS[id] ?? "",
|
|
3827
|
+
free: isFreeModel(id),
|
|
3828
|
+
hint: capabilityDescription(id)
|
|
3829
|
+
})).sort((a, b) => {
|
|
3830
|
+
const tierDelta = tierRank(a.tier) - tierRank(b.tier);
|
|
3831
|
+
if (tierDelta !== 0) return tierDelta;
|
|
3832
|
+
if (a.free !== b.free) return a.free ? -1 : 1;
|
|
3833
|
+
return a.id.localeCompare(b.id);
|
|
3834
|
+
});
|
|
3835
|
+
}
|
|
3836
|
+
/** Sort rank of a tier key; unknown tiers trail every known one. */
|
|
3837
|
+
function tierRank(tier) {
|
|
3838
|
+
const rank = TIER_ORDER.indexOf(tier);
|
|
3839
|
+
return rank === -1 ? TIER_ORDER.length : rank;
|
|
3840
|
+
}
|
|
3841
|
+
/** The stored allowlist as a clean id list (non-strings and blanks dropped). */
|
|
3842
|
+
function storedIds(value) {
|
|
3843
|
+
return Array.isArray(value) ? value.filter((id) => typeof id === "string" && id !== "") : [];
|
|
3844
|
+
}
|
|
3789
3845
|
/**
|
|
3790
3846
|
* Build the section descriptor. Pure, so tests can pin the exact fields
|
|
3791
3847
|
* without a dsh-TUI host.
|
|
@@ -3795,13 +3851,65 @@ const LANG_AUTO = "auto";
|
|
|
3795
3851
|
* cannot express "unset" — cycling only ever lands on a declared option, so a
|
|
3796
3852
|
* `select` would strand the user on a pinned value with no way back to
|
|
3797
3853
|
* automatic. The `auto` sentinel plus a `parse` that clears the path keeps the
|
|
3798
|
-
* unset state reachable.
|
|
3799
|
-
* (
|
|
3800
|
-
*
|
|
3854
|
+
* unset state reachable. `filterModelsByPlan` formats its EFFECTIVE default
|
|
3855
|
+
* (unset means true at the adapter), so a fresh install reads true instead of
|
|
3856
|
+
* the screen's "(empty)".
|
|
3801
3857
|
*/
|
|
3802
3858
|
function buildCommandCodeTuiSection(deps) {
|
|
3803
3859
|
const ref = deps.apiKeyRef();
|
|
3804
3860
|
const slots = deps.accountSlots();
|
|
3861
|
+
const choices = (deps.modelChoices ?? commandCodeTuiModelChoices)();
|
|
3862
|
+
const catalogIds = choices.map((choice) => choice.id);
|
|
3863
|
+
const known = new Set(catalogIds);
|
|
3864
|
+
const stored = storedIds(deps.visibleModels());
|
|
3865
|
+
const overrides = deps.modelVisibility?.() ?? {};
|
|
3866
|
+
const extras = [.../* @__PURE__ */ new Set([...stored, ...Object.keys(overrides)])].filter((id) => !known.has(id));
|
|
3867
|
+
const tierGroups = TIER_ORDER.filter((tier) => choices.some((choice) => choice.tier === tier)).map((tier) => ({
|
|
3868
|
+
id: `models-${tier}`,
|
|
3869
|
+
title: `${TIER_TITLES[tier] ?? tier} models`,
|
|
3870
|
+
descriptions: { zh: `${TIER_TITLES[tier] ?? tier} 模型` }
|
|
3871
|
+
}));
|
|
3872
|
+
const unranked = choices.filter((choice) => tierRank(choice.tier) === TIER_ORDER.length);
|
|
3873
|
+
const otherGroup = unranked.length > 0 || extras.length > 0 ? [{
|
|
3874
|
+
id: OTHER_GROUP_ID,
|
|
3875
|
+
title: "Other models",
|
|
3876
|
+
descriptions: { zh: "其他模型" }
|
|
3877
|
+
}] : [];
|
|
3878
|
+
/**
|
|
3879
|
+
* One checkbox: a boolean at a path of its OWN (`modelVisibility.<id>`).
|
|
3880
|
+
*
|
|
3881
|
+
* The path must be unique per model. dsh-TUI keys a staged draft by the
|
|
3882
|
+
* field's path (`fieldKey`), so N checkboxes sharing `visibleModels` share
|
|
3883
|
+
* ONE draft: every one of them then parses that same draft on save, all N
|
|
3884
|
+
* write ops address the same path, and only the LAST field's op survives —
|
|
3885
|
+
* which silently rewrote the allowlist from the last catalog model instead
|
|
3886
|
+
* of the one that was toggled. A per-model key is what makes a checkbox
|
|
3887
|
+
* express one model's state.
|
|
3888
|
+
*
|
|
3889
|
+
* An override equal to what the array already says is written as a CLEAR, so
|
|
3890
|
+
* toggling a model back to its inherited state leaves no residue and the
|
|
3891
|
+
* document stays minimal.
|
|
3892
|
+
*/
|
|
3893
|
+
const modelField = (id, hint, group) => ({
|
|
3894
|
+
path: ["modelVisibility", id],
|
|
3895
|
+
group,
|
|
3896
|
+
kind: "boolean",
|
|
3897
|
+
label: id,
|
|
3898
|
+
...hint === "" ? {} : { hint },
|
|
3899
|
+
format: (value) => {
|
|
3900
|
+
if (typeof value === "boolean") return String(value);
|
|
3901
|
+
const listed = storedIds(deps.visibleModels());
|
|
3902
|
+
return String(listed.length === 0 || listed.includes(id));
|
|
3903
|
+
},
|
|
3904
|
+
parse: (text) => {
|
|
3905
|
+
const on = text.trim() === "true";
|
|
3906
|
+
const listed = storedIds(deps.visibleModels());
|
|
3907
|
+
return on === (listed.length === 0 || listed.includes(id)) ? { kind: "clear" } : {
|
|
3908
|
+
kind: "set",
|
|
3909
|
+
value: on
|
|
3910
|
+
};
|
|
3911
|
+
}
|
|
3912
|
+
});
|
|
3805
3913
|
return {
|
|
3806
3914
|
ns: deps.ns,
|
|
3807
3915
|
title: deps.title ?? "Command Code",
|
|
@@ -3817,6 +3925,8 @@ function buildCommandCodeTuiSection(deps) {
|
|
|
3817
3925
|
title: "Models",
|
|
3818
3926
|
descriptions: { zh: "模型" }
|
|
3819
3927
|
},
|
|
3928
|
+
...tierGroups,
|
|
3929
|
+
...otherGroup,
|
|
3820
3930
|
{
|
|
3821
3931
|
id: "advanced",
|
|
3822
3932
|
title: "Advanced",
|
|
@@ -3865,23 +3975,9 @@ function buildCommandCodeTuiSection(deps) {
|
|
|
3865
3975
|
value: text.trim() === "true"
|
|
3866
3976
|
})
|
|
3867
3977
|
},
|
|
3868
|
-
{
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
kind: "text",
|
|
3872
|
-
label: "Visible models",
|
|
3873
|
-
descriptions: { zh: "显示的模型" },
|
|
3874
|
-
hint: "Comma-separated catalog ids; empty shows every model.",
|
|
3875
|
-
hintDescriptions: { zh: "以逗号分隔的模型 id;留空表示显示全部模型。" },
|
|
3876
|
-
format: (value) => (Array.isArray(value) ? value.filter((id) => typeof id === "string" && id !== "") : []).join(", "),
|
|
3877
|
-
parse: (text) => {
|
|
3878
|
-
const ids = text.split(",").map((id) => id.trim()).filter((id) => id !== "");
|
|
3879
|
-
return ids.length === 0 ? { kind: "clear" } : {
|
|
3880
|
-
kind: "set",
|
|
3881
|
-
value: ids
|
|
3882
|
-
};
|
|
3883
|
-
}
|
|
3884
|
-
},
|
|
3978
|
+
...choices.filter((choice) => tierRank(choice.tier) !== TIER_ORDER.length).map((choice) => modelField(choice.id, choice.hint, `models-${choice.tier}`)),
|
|
3979
|
+
...unranked.map((choice) => modelField(choice.id, choice.hint, OTHER_GROUP_ID)),
|
|
3980
|
+
...extras.map((id) => modelField(id, capabilityDescription(id), OTHER_GROUP_ID)),
|
|
3885
3981
|
{
|
|
3886
3982
|
path: ["activeAccount"],
|
|
3887
3983
|
group: "advanced",
|
|
@@ -3968,15 +4064,18 @@ function tuiSettingsService(ctx) {
|
|
|
3968
4064
|
}
|
|
3969
4065
|
/**
|
|
3970
4066
|
* Identity of everything in the section that can change at runtime: the
|
|
3971
|
-
* credential reference behind the API-key field
|
|
3972
|
-
*
|
|
3973
|
-
*
|
|
4067
|
+
* credential reference behind the API-key field, the account slots behind the
|
|
4068
|
+
* active-account selector, and the stored-but-unknown allowlist entries that
|
|
4069
|
+
* get a checkbox of their own. Re-registration is skipped while this matches,
|
|
4070
|
+
* so ordinary settings writes never churn the screen's section list — the
|
|
4071
|
+
* checkboxes themselves read their state live and need no re-declaration.
|
|
3974
4072
|
*/
|
|
3975
4073
|
function sectionSignature(section) {
|
|
3976
4074
|
const active = section.fields.find((field) => field.path.join(".") === "activeAccount");
|
|
3977
4075
|
return JSON.stringify({
|
|
3978
4076
|
secret: section.fields.find((field) => field.secret !== void 0)?.secret?.ref ?? "",
|
|
3979
|
-
options: active?.options?.map((option) => option.value) ?? []
|
|
4077
|
+
options: active?.options?.map((option) => option.value) ?? [],
|
|
4078
|
+
other: section.fields.filter((field) => field.group === OTHER_GROUP_ID).map((field) => field.label)
|
|
3980
4079
|
});
|
|
3981
4080
|
}
|
|
3982
4081
|
/**
|
|
@@ -4065,6 +4164,15 @@ const Config = z.object({
|
|
|
4065
4164
|
streamIdleTimeoutMs: z.number().min(1).max(MAX_TIMER_DELAY_MS),
|
|
4066
4165
|
filterModelsByPlan: z.boolean(),
|
|
4067
4166
|
visibleModels: z.array(z.string()),
|
|
4167
|
+
/**
|
|
4168
|
+
* Per-model visibility overrides for the terminal settings page's checkbox
|
|
4169
|
+
* list, keyed by catalog id. dsh-TUI addresses a staged edit by its field
|
|
4170
|
+
* PATH, so two checkboxes sharing one path would overwrite each other's
|
|
4171
|
+
* draft and the section's last model would decide every write; a map gives
|
|
4172
|
+
* each checkbox a path of its own. An id listed here wins over
|
|
4173
|
+
* {@link visibleModels}; ids absent here keep following it.
|
|
4174
|
+
*/
|
|
4175
|
+
modelVisibility: z.dict(z.boolean()),
|
|
4068
4176
|
webSearch: z.boolean().default(true),
|
|
4069
4177
|
accounts: z.array(z.object({
|
|
4070
4178
|
label: z.string(),
|
|
@@ -4094,9 +4202,22 @@ function resolveAdapterOptions(config) {
|
|
|
4094
4202
|
requestTimeoutMs: config.requestTimeoutMs ?? 6e4,
|
|
4095
4203
|
streamIdleTimeoutMs: config.streamIdleTimeoutMs ?? 3e5,
|
|
4096
4204
|
filterModelsByPlan: config.filterModelsByPlan ?? true,
|
|
4097
|
-
visibleModels: Array.isArray(config.visibleModels) ? config.visibleModels.filter((id) => typeof id === "string" && id !== "") : void 0
|
|
4205
|
+
visibleModels: Array.isArray(config.visibleModels) ? config.visibleModels.filter((id) => typeof id === "string" && id !== "") : void 0,
|
|
4206
|
+
modelVisibility: readModelVisibility(config.modelVisibility)
|
|
4098
4207
|
};
|
|
4099
4208
|
}
|
|
4209
|
+
/**
|
|
4210
|
+
* Per-model visibility overrides, cleaned for the adapter. Programmatic
|
|
4211
|
+
* construction may bypass Schemastery normalization, so a non-object or a
|
|
4212
|
+
* non-boolean entry is dropped here instead of reaching the picker filter.
|
|
4213
|
+
* @param raw - The `modelVisibility` value from any config source.
|
|
4214
|
+
* @returns A frozen id → boolean map, or undefined when nothing is set.
|
|
4215
|
+
*/
|
|
4216
|
+
function readModelVisibility(raw) {
|
|
4217
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return void 0;
|
|
4218
|
+
const entries = Object.entries(raw).filter((entry) => entry[0] !== "" && typeof entry[1] === "boolean");
|
|
4219
|
+
return entries.length === 0 ? void 0 : Object.fromEntries(entries);
|
|
4220
|
+
}
|
|
4100
4221
|
function apply(ctx, config) {
|
|
4101
4222
|
let current = () => config;
|
|
4102
4223
|
let lastRaw;
|
|
@@ -4268,7 +4389,9 @@ function apply(ctx, config) {
|
|
|
4268
4389
|
accountSlots: () => slots().map((slot) => ({
|
|
4269
4390
|
id: slot.id,
|
|
4270
4391
|
label: slot.label
|
|
4271
|
-
}))
|
|
4392
|
+
})),
|
|
4393
|
+
visibleModels: () => options().visibleModels ?? [],
|
|
4394
|
+
modelVisibility: () => options().modelVisibility
|
|
4272
4395
|
});
|
|
4273
4396
|
tuiCtx.effect(() => () => {
|
|
4274
4397
|
refreshTuiSettings = void 0;
|