@deepseek-ai/dsh-client-ui-settings-models 0.2.1-alpha.1 → 0.2.1-alpha.3

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/README.i18n.yaml CHANGED
@@ -15,14 +15,14 @@
15
15
  en: d152484eb41ac6b4
16
16
  zh: 09388d293f9be9cb
17
17
  /deepseek-ai-dsh-client-ui-settings-models/use-this-package:
18
- en: 9ad8f5243c87c5b9
19
- zh: f3b01e25c28ebba2
18
+ en: db5aaf7f702e1f0c
19
+ zh: 409a78c14fb2b6e3
20
20
  /deepseek-ai-dsh-client-ui-settings-models/use-this-package/api-keys:
21
- en: c2f2a5c06ef20d5b
22
- zh: 49a7eb0b66b4ed53
21
+ en: f4817eb2e3537ed9
22
+ zh: e589c193fa85473f
23
23
  /deepseek-ai-dsh-client-ui-settings-models/use-this-package/editing-a-provider:
24
- en: ec0ba62a4e7185b4
25
- zh: 37d1e1e7a3817d12
24
+ en: eac663a25c60adf9
25
+ zh: 24450ef377a79e01
26
26
  /deepseek-ai-dsh-client-ui-settings-models/use-this-package/adding-and-deleting-providers:
27
27
  en: 9d533c65c82cd4a4
28
28
  zh: 0a5da334e127c035
@@ -30,8 +30,8 @@
30
30
  en: 589397a35f82244e
31
31
  zh: 342fbceee2c173a2
32
32
  /deepseek-ai-dsh-client-ui-settings-models/use-this-package/extension-slots:
33
- en: 65e9d8822b67b6c2
34
- zh: 7e83be9defda28ee
33
+ en: 79f8f80bdaba73cd
34
+ zh: f4d7496b293fbf81
35
35
  /deepseek-ai-dsh-client-ui-settings-models/understand-the-implementation:
36
36
  en: a87afbec5e339b36
37
37
  zh: 6348c81af7e650e1
@@ -39,11 +39,11 @@
39
39
  en: 74948a88fe1f9aac
40
40
  zh: 8ad2200fa74c12c9
41
41
  /deepseek-ai-dsh-client-ui-settings-models/understand-the-implementation/concurrency-and-credentials:
42
- en: 670b8c066aed9b7a
43
- zh: 2bedba9a2e0e3fee
42
+ en: 6e04c00818f014cd
43
+ zh: 2d57f8d8944e67ed
44
44
  /deepseek-ai-dsh-client-ui-settings-models/understand-the-implementation/onboarding-coordinator:
45
- en: 46dea666dffe8813
46
- zh: 6f56fdc365fa3642
45
+ en: dc515a31212e477a
46
+ zh: 770ba77cd9a69f73
47
47
  /deepseek-ai-dsh-client-ui-settings-models/further-exploration:
48
48
  en: 20abd8b98b6c20e2
49
49
  zh: 6dfda7955ce5f33f
package/README.md CHANGED
@@ -35,17 +35,25 @@ Open the Models page from the Settings navigation to see every configured provid
35
35
 
36
36
  A provider with a stored catalog error remains visible with its diagnostic and edit/delete actions. Add actions are offered only for registered settings namespaces, so an unavailable namespace cannot leave a button that opens no editor. A rejected save leaves the editor open and displays the Host diagnostic.
37
37
 
38
+ After Apply closes an editor, its saved status appears when the current provider, settings, and credential snapshot is ready. Opening any row's editor, adding a provider, and opening a removal confirmation wait for that refresh. A newly opened Models section, or one retrying after a page-load failure, waits for a ready snapshot before mounting editors. A background refresh preserves an open draft while its editor remains mounted; a failed page load or provider removal can close it.
39
+
38
40
  Host configuration `credentialOnboarding` defaults to `true`. The Electron preload marker suppresses automatic credential onboarding and the Web welcome notice; Models settings and explicit API-key editing remain available. The [account plugin](../ui-settings-account/README.md#desktop-onboarding) owns the Desktop introduction. Other native shells can disable only the credential step with `credentialOnboarding: false`. Host publishes this public boolean through `webserver/index-inject`, and Client validates it before registering dialogs. It is page initialization data, not a durable completion marker.
39
41
 
40
42
  ### API keys
41
43
 
42
- API-key inputs start empty and use `autocomplete="new-password"` to ask browsers not to autofill saved login passwords.
44
+ API-key inputs start empty and use `autocomplete="new-password"` to ask browsers not to autofill saved login passwords. A nonempty draft has the same eye control as masked conversation text: **Show secret** reveals the current input and **Hide secret** masks it. Changing or clearing the input masks it again. Stored credentials are never read into the input.
45
+
46
+ The primary field on an editor card is a single **API key** input — the page never asks for an environment-variable name. A typed key stores write-only through `credentials.set` under the profile's reference, deriving `<ROUTE>_API_KEY` when the profile has none, and the pi-ai profile records that derivation as `apiKeyEnv`, so `cordis.patch.yml` never carries a key value. Leaving a new pi-ai provider's key blank saves a reference-free profile and preserves provider-native authentication (for example the Bedrock credential chain or Vertex ADC). The field's placeholder and help tooltip use the profile's `apiKeyEnv` when it has one; otherwise they use the credential variables of the route's `nativeAuth`, excluding `baseURLEnv` address variables, in lookup order, as the configurable-provider directory reports them. These display hints omit references containing `oauth` (case-insensitively), and describe saved credentials followed by the displayed environment fallbacks. This presentation filter does not change the [credential resolution order](../../../docs/subsystems/credentials.md#resolution), the adapter's complete environment list, or an explicit reference. A filtered explicit reference still has a usable key input with the plain placeholder and no tooltip. A native route that has no key authentication omits the key field and authentication help entirely; an explicit reference still enables the field. A profile without a reference on a route without `nativeAuth`, such as a hand-declared route, shows the plain key placeholder and no tooltip. A row labels API-key state with a green solid dot only when a referenced credential is confirmed configured, and with a red solid dot only when a named reference is confirmed missing. A successful Apply emits a local accessible status message without echoing secret material.
43
47
 
44
- The primary field on an editor card is a single **API key** input — the page never asks for an environment-variable name. A typed key stores write-only through `credentials.set` under the profile's reference, deriving `<ROUTE>_API_KEY` when the profile has none, and the pi-ai profile records that derivation as `apiKeyEnv`, so `cordis.patch.yml` never carries a key value. Leaving a new pi-ai provider's key blank saves a reference-free profile and preserves provider-native authentication (for example the Bedrock credential chain or Vertex ADC). A row labels API-key state with a green solid dot only when a referenced credential is confirmed configured, and with a red solid dot only when a named reference is confirmed missing. A successful Apply emits a local accessible status message without echoing secret material.
48
+ The editor uses redacted `CredentialInfo` to choose the saved-credential placeholder or environment detection, without displaying a credential-source label. Refreshing the snapshot updates an open editor without a separate probe. The hint never receives a credential value or changes field writability.
45
49
 
46
50
  ### Editing a provider
47
51
 
48
- The collapsed 自定义设置 fold carries the curated extras: `baseURL` for both families (the deepseek placeholder shows the public endpoint), each adapter's model catalog, and the **display name** and **API protocol** of a pi-ai route the adapter does not ship. Profile `headers` remain deployment configuration in `cordis.patch.yml` or Cordis config and have no Models-page editor. The Provider ID stays fixed: it is the settings key, the name every other namespace and every logged session references, and the stem of a credential reference the page cannot read back to move. Reasoning effort is deliberately not among the editable fields: it is a per-model capability, so a provider-scoped control could only be set to a value some models reject. Each model row edits `id`, optional display `name`, optional `contextWindow`/`maxTokens`, and input types; unrelated model fields survive edits.
52
+ A blank key field offers a saved credential when one is detected, with no status text below the input; otherwise it offers environment authentication. Redacted `credentials.describe` checks distinguish a detected environment reference, no detected variables, and an unconfirmed read. A missing-variable hint uses the warning color and names the first variable to configure. A detected environment reference uses green. Address configuration variables do not count as credentials. Hidden authentication references still participate in detection; a generic detected hint keeps their names hidden. Detection does not validate credentials, test a connection, or inspect provider-managed files such as AWS profiles and Google application-default credentials. A nonempty input shows no authentication status text. The page reads native variable names as well as named references, in batches of at most 64, and never reads their values into the Client.
53
+
54
+ An endpoint without a fixed catalog default shows the provider's `baseURLEnv` configuration names below the input. A profile endpoint remains the first placeholder choice.
55
+
56
+ The collapsed 自定义设置 fold carries the curated extras: `baseURL` for both families (the placeholder shows the endpoint the profile resolves to, the public endpoint for deepseek, else the `defaultBaseURL` the configurable-provider directory reports for a pi-ai catalog route), each adapter's model catalog, and the **display name** and **API protocol** of a pi-ai route the adapter does not ship. Profile `headers` remain deployment configuration in `cordis.patch.yml` or Cordis config and have no Models-page editor. The Provider ID stays fixed: it is the settings key, the name every other namespace and every logged session references, and the stem of a credential reference the page cannot read back to move. Reasoning effort is deliberately not among the editable fields: it is a per-model capability, so a provider-scoped control could only be set to a value some models reject. Each model row edits `id`, optional display `name`, optional `contextWindow`/`maxTokens`, and input types; unrelated model fields survive edits.
49
57
 
50
58
  The DeepSeek card edits the `llm-deepseek` endpoint, credentials, and model catalog. It uses Messages, with the default endpoint placeholder `https://api.deepseek.com/anthropic`.
51
59
 
@@ -63,7 +71,7 @@ After the versioned notice step completes, the DeepSeek step projects first-run
63
71
 
64
72
  The section declares two seats for plugins distributed outside this repository, typed in [`src/client/slot-contract.ts`](src/client/slot-contract.ts) and exported from `./client`. `settings.models.provider-card` (keyed) renders inside every card that shows a directory row — a saved row's card, its first-run setup posture, and the add-provider draft — dispatched with `entryKey = settingsNs` and owner props carrying the row's `ConfigurableProviderView`, its configured state, and its confirmed api-key credential state, so one registration under an adapter family's namespace receives every card of that family, hand-declared routes included; the hand-declared draft card has no directory row yet and dispatches nothing until saved. `settings.models.footer` (list) renders after the rows and the add controls. A registrant activates through `ctx.slots.inject` with a type-only import of this package's `/client` entry; without registrants both seats render nothing.
65
73
 
66
- The Models page includes **DeepSeek Account** (`deepseek-account`, **DeepSeek 账号** in Chinese). Its editor exposes the shared DeepSeek model catalog without API-key or base-URL inputs, and saves that catalog under the account route's own settings section (`llm-deepseek-account` by default), so an account edit never rewrites the `llm-deepseek` section the official route reads. The account row is hidden when its available model catalog is empty, including before sign-in and after sign-out; it returns when account models become available.
74
+ The Models page includes **DeepSeek Account** (`deepseek-account`, **DeepSeek 账号** in Chinese). The row shows a **Signed in** status and offers no editor: the account listing supplies its models, and [model overrides](../../llm/llm-deepseek-account/README.md#use-this-package) live in `cordis.patch.yml`. The account row is hidden when its available model catalog is empty, including before sign-in and after sign-out; it returns when account models become available.
67
75
 
68
76
  -----
69
77
 
@@ -83,9 +91,11 @@ A typed API key is judged on its own field: after trimming, it must be non-empty
83
91
 
84
92
  Each settings write carries the card's current `revision`, so a concurrent write from another tab or an external `cordis.patch.yml` edit is refused as `settings/conflict`. After settings commit, the card adopts the returned redacted user subtree and revision before storing the credential, so a failed credential stage retries only that stage. Deletion removes a configured, writable credential only when the profile names the page's derived `<ROUTE>_API_KEY` target, then unsets the profile; both operations are idempotent. Once loaded, the page subscribes to forwarded `settings/document-updated`, `credentials/reference-updated`, and `llm/adapters-updated` owner events, plus local `connection/reset`, so external edits converge without polling.
85
93
 
94
+ Concurrent `ModelsSettingsStore.load()` callers resolve after the current refresh publishes its ready or error snapshot, including callers whose reads were superseded. Older responses cannot publish or settle callers; a refresh started synchronously by a snapshot subscriber owns the waiting callers. Expected Remote failures remain snapshot diagnostics, while an unexpected rejection of the current read rejects its waiting callers.
95
+
86
96
  ### Onboarding coordinator
87
97
 
88
- The notice step owns its exact copy in `src/client/locales.ts` and its acknowledgement version in `src/onboarding-copy.ts`; on loopback it compares and writes `ui-settings-general.welcomeNoticeVersion` through the shared configuration form, and only an explicit Continue records the current version. Users who acknowledged an earlier version see the current notice again. A non-loopback browser cannot use that Host-only namespace, so acknowledgement is process-local and the notice returns after reload. The DeepSeek step targets `deepseek-official` in `llm-deepseek` and renders the existing `ProviderEditor` in credential-only mode inside the shared onboarding modal; `credentials.set` stays the only secret write, and no provider settings are changed.
98
+ The notice step owns its exact copy in `src/client/locales.ts` and its acknowledgement version in `src/onboarding-copy.ts`. Authenticated browsers compare and write `ui-settings-general.welcomeNoticeVersion` through the shared configuration form; only an explicit Continue records the current version, and an unavailable namespace or a refused write leaves the notice unacknowledged. Users who acknowledged an earlier version see the current notice again. The DeepSeek step targets `deepseek-official` in `llm-deepseek` and renders the existing `ProviderEditor` in credential-only mode inside the shared onboarding modal; `credentials.set` stays the only secret write, and no provider settings are changed.
89
99
 
90
100
  </details>
91
101
 
package/README.zh.md CHANGED
@@ -35,17 +35,25 @@ kind: "package-reference"
35
35
 
36
36
  存在已存储目录错误的提供商仍显示诊断以及编辑、删除入口。添加操作只面向已注册的 settings 命名空间,因此不可用的命名空间不会留下无法打开编辑器的按钮。保存被拒绝时,编辑器保持打开并展示 Host 诊断。
37
37
 
38
+ 应用设置并关闭编辑器后,当前提供商、settings 与凭据快照就绪时才显示保存状态。打开任一行的编辑器、添加提供商和打开移除确认都会等待该刷新。新打开的 Models 区域或页面加载失败后的重试会等到 ready 快照后才挂载编辑器。后台刷新期间,只要编辑器仍挂载,已打开的草稿就会保留用户的修改;页面加载失败或提供商被移除可能关闭编辑器。
39
+
38
40
  Host 配置 `credentialOnboarding` 默认为 `true`。Electron preload 标记会抑制自动凭证引导和 Web 欢迎须知;模型设置页与显式 API Key 编辑仍然可用。[账号插件](../ui-settings-account/README.zh.md#desktop-onboarding)负责 Desktop 引导。其他原生壳可以通过 `credentialOnboarding: false` 仅禁用凭证步骤。Host 通过 `webserver/index-inject` 发布这个公开的布尔值,Client 在注册弹窗前校验它。它是页面初始化数据,不是持久化的完成标记。
39
41
 
40
42
  ### API 密钥
41
43
 
42
- API 密钥输入框初始为空,并通过 `autocomplete="new-password"` 请求浏览器不要自动填入已保存的登录密码。
44
+ API 密钥输入框初始为空,并通过 `autocomplete="new-password"` 请求浏览器不要自动填入已保存的登录密码。输入非空时,显示与会话密钥遮罩一致的眼睛按钮:**显示密钥**展示当前输入,**隐藏密钥**重新遮住输入。修改或清空输入后会重新遮住密钥。已保存凭据从不读入输入框。
45
+
46
+ 编辑卡片上的主字段是单独一个 **API 密钥**输入框——页面从不询问环境变量名。键入的密钥经 `credentials.set` 以**只写**方式存入 profile 的引用之下,profile 没有引用时便派生 `<ROUTE>_API_KEY`,pi-ai profile 会把这次派生记录为 `apiKeyEnv`,因此 `cordis.patch.yml` 从不携带密钥值。为新的 pi-ai 提供商留空密钥会保存一个不带引用的 profile,从而保留提供商原生认证(例如 Bedrock 凭据链或 Vertex ADC)。profile 有 `apiKeyEnv` 时,字段的占位文字与帮助提示采用该引用;否则按查找顺序采用可配置提供商目录为该路由报告的 `nativeAuth` 凭据变量,并排除 `baseURLEnv` 地址变量。这些显示提示会忽略包含 `oauth` 的引用(不区分大小写),并说明优先使用已保存凭据,其后为显示的环境后备。这项显示过滤不改变[凭据解析顺序](../../../docs/subsystems/credentials.zh.md#resolution)、适配器的完整环境变量列表或显式引用。被过滤的显式引用仍有可用的密钥输入框,使用普通占位文字且不显示帮助提示。没有密钥认证的原生路由不显示密钥字段及认证帮助;显式引用仍会启用该字段。路由没有 `nativeAuth`(例如手动声明的路由)且 profile 没有引用时,显示普通的密钥占位文字,且不显示帮助提示。只有确认引用的凭据已配置时,行才会以绿色实心点标示 API 密钥状态;只有确认具名引用缺失时,才会以红色实心点标示。「应用」成功后会发出本地无障碍状态消息,且绝不回显任何机密内容。
43
47
 
44
- 编辑卡片上的主字段是单独一个 **API 密钥**输入框——页面从不询问环境变量名。键入的密钥经 `credentials.set` 以**只写**方式存入 profile 的引用之下,profile 没有引用时便派生 `<ROUTE>_API_KEY`,pi-ai profile 会把这次派生记录为 `apiKeyEnv`,因此 `cordis.patch.yml` 从不携带密钥值。为新的 pi-ai 提供商留空密钥会保存一个不带引用的 profile,从而保留提供商原生认证(例如 Bedrock 凭据链或 Vertex ADC)。只有确认引用的凭据已配置时,行才会以绿色实心点标示 API 密钥状态;只有确认具名引用缺失时,才会以红色实心点标示。「应用」成功后会发出本地无障碍状态消息,且绝不回显任何机密内容。
48
+ 编辑器使用脱敏 `CredentialInfo` 选择已保存凭据的占位文字或环境检测,不显示凭据来源标签。快照刷新会更新打开的编辑器,无需单独探测。提示从不接收凭据值,也不改变字段的可写性。
45
49
 
46
50
  ### 编辑提供商
47
51
 
48
- 收起的「自定义设置」折叠区承载精选的额外字段:两个家族都有 `baseURL`(deepseek 的占位符显示公共端点)、各适配器自己的模型目录,以及适配器未提供的 pi-ai 路由的**显示名称**与 **API 协议**。Profile `headers` 仍是 `cordis.patch.yml` 或 Cordis 配置中的部署配置,Models 页面不提供编辑器。Provider ID 保持固定:它是 settings 的键、其他每个 namespace 与每一条已记录会话引用的名字,也是页面读不回、因而搬不走的凭据引用词干。推理等级刻意不在可编辑字段之列:它是按模型的能力,提供商级的控件只可能被设成某些模型会拒绝的值。每个模型行可编辑 `id`、可选显示 `name`、可选 `contextWindow`/`maxTokens` 和输入类型;无关的模型字段在编辑后仍会保留。
52
+ 密钥字段留空时,已检测到已保存凭据则提示使用该凭据,输入框下方不显示状态文字;否则提示使用环境认证。脱敏的 `credentials.describe` 检查区分已检测到环境引用、未检测到变量和读取状态未确认。缺少变量时使用黄色提示,并列出应配置的首个变量名。已检测到环境引用时使用绿色。地址配置变量不计作凭据。隐藏的认证引用仍参与检测;使用不含变量名的通用检测提示。检测不验证凭据、不测试连接,也不检查 AWS profile 或 Google 应用默认凭据等提供商管理的文件。输入非空时,不显示认证状态文字。页面读取原生变量名和具名引用,每批最多 64 项,不将变量值读入 Client。
53
+
54
+ 目录未提供固定默认端点时,输入框下方显示提供方的 `baseURLEnv` 配置变量名。Profile 中的端点仍是占位符的首选。
55
+
56
+ 收起的「自定义设置」折叠区承载精选的额外字段:两个家族都有 `baseURL`(占位符显示 profile 解析出的端点,deepseek 即公共端点;没有时显示可配置提供商目录为 pi-ai 内置路由报告的 `defaultBaseURL`)、各适配器自己的模型目录,以及适配器未提供的 pi-ai 路由的**显示名称**与 **API 协议**。Profile `headers` 仍是 `cordis.patch.yml` 或 Cordis 配置中的部署配置,Models 页面不提供编辑器。Provider ID 保持固定:它是 settings 的键、其他每个 namespace 与每一条已记录会话引用的名字,也是页面读不回、因而搬不走的凭据引用词干。推理等级刻意不在可编辑字段之列:它是按模型的能力,提供商级的控件只可能被设成某些模型会拒绝的值。每个模型行可编辑 `id`、可选显示 `name`、可选 `contextWindow`/`maxTokens` 和输入类型;无关的模型字段在编辑后仍会保留。
49
57
 
50
58
  `llm-deepseek` 的 DeepSeek 卡片编辑端点、凭据和模型目录。它使用 Messages,默认端点占位符为 `https://api.deepseek.com/anthropic`。
51
59
 
@@ -63,7 +71,7 @@ API 密钥输入框初始为空,并通过 `autocomplete="new-password"` 请求
63
71
 
64
72
  本分区为仓库外分发的插件声明两个席位,类型定义在 [`src/client/slot-contract.ts`](src/client/slot-contract.ts) 并从 `./client` 导出。`settings.models.provider-card`(keyed)渲染在每张展示目录行的卡片内部——已保存行的卡片、其首次运行 setup 形态、以及「添加提供商」草稿卡——以 `entryKey = settingsNs` 分发,owner props 携带该行的 `ConfigurableProviderView`、其 configured 状态与已确认的 api-key 凭据状态,因此以某适配器家族的 namespace 注册一次即可收到该家族的全部卡片,含手工声明的路由;手工声明的草稿卡尚无目录行,保存之前不分发。`settings.models.footer`(list)渲染在行列表与新增控件之后。注册方通过 `ctx.slots.inject` 激活,并以 type-only import 引入本包 `/client` 入口;没有注册方时两个席位均不渲染任何内容。
65
73
 
66
- Models 页面包含 **DeepSeek 账号**(`deepseek-account`,英文为 **DeepSeek Account**)。其编辑器展示共享的 DeepSeek 模型目录,不提供 API Key 或 Base URL 输入框;目录保存到账号路由自己的设置段(默认为 `llm-deepseek-account`),因此账号侧的编辑不会改写 official 路由读取的 `llm-deepseek` 段。账号可用模型目录为空时隐藏账号行,包括登录前和退登后;账号模型恢复可用时重新显示。
74
+ Models 页面包含 **DeepSeek 账号**(`deepseek-account`,英文为 **DeepSeek Account**)。该行显示 **已登录** 状态,不提供编辑器:模型来自账号的模型列表,[模型覆盖](../../llm/llm-deepseek-account/README.zh.md#use-this-package)写在 `cordis.patch.yml` 中。账号可用模型目录为空时隐藏账号行,包括登录前和退登后;账号模型恢复可用时重新显示。
67
75
 
68
76
  -----
69
77
 
@@ -83,9 +91,11 @@ Models 页面包含 **DeepSeek 账号**(`deepseek-account`,英文为 **DeepS
83
91
 
84
92
  每次 settings 写入都携带卡片当前的 `revision`,因此来自另一个标签页或外部 `cordis.patch.yml` 编辑的并发写入会以 `settings/conflict` 被拒绝。settings 提交后,卡片会在存储凭据前采纳返回的脱敏用户子树与 revision,因此失败的凭据阶段只重试该阶段。删除只会在 profile 指名本页派生的 `<ROUTE>_API_KEY` 目标时移除已配置且可写的凭据,然后 unset 该 profile;两个操作都幂等。加载完成后,页面订阅转发的 `settings/document-updated`、`credentials/reference-updated` 与 `llm/adapters-updated` 属主事件,以及本地 `connection/reset`,因此外部编辑无需轮询即可收敛。
85
93
 
94
+ 并发的 `ModelsSettingsStore.load()` 调用会在当前刷新发布 ready 或 error 快照后 resolve,包括读取已被后续刷新取代的调用。旧响应不能发布快照或完成等待中的调用;快照订阅者同步发起的刷新负责等待中的调用。预期的 Remote 失败仍作为快照诊断展示,当前读取的意外拒绝则会拒绝所有等待中的调用。
95
+
86
96
  ### 引导协调器
87
97
 
88
- 预览版说明在 `src/client/locales.ts` 中持有精确文案,并在 `src/onboarding-copy.ts` 中持有确认版本;回环时它通过既有 settings API 比较并写入 `ui-settings-general.welcomeNoticeVersion`,且只有显式点击「继续」才会记录当前版本。已确认旧版的用户会再次看到当前说明。非回环浏览器无法使用这个仅限宿主的 namespace,因此确认只保留在进程内,刷新后说明会再次出现。DeepSeek 步骤面向 `llm-deepseek` 中的 `deepseek-official`,在共享引导模态框内以仅凭据模式渲染既有 `ProviderEditor`;`credentials.set` 仍是唯一的机密写入,且不改变任何提供商设置。
98
+ 预览版说明在 `src/client/locales.ts` 中持有精确文案,并在 `src/onboarding-copy.ts` 中持有确认版本。认证浏览器通过共享配置表单比较并写入 `ui-settings-general.welcomeNoticeVersion`;只有显式点击「继续」才会记录当前版本;命名空间不可用或写入被拒时,声明保持未确认状态。已确认旧版的用户会再次看到当前说明。DeepSeek 步骤面向 `llm-deepseek` 中的 `deepseek-official`,在共享引导模态框内以仅凭据模式渲染既有 `ProviderEditor`;`credentials.set` 仍是唯一的机密写入,且不改变任何提供商设置。
89
99
 
90
100
  </details>
91
101
 
package/lib/client.js CHANGED
@@ -55,7 +55,7 @@ window.__ModuleLoader__.load({
55
55
  }
56
56
  //#endregion
57
57
  //#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-settings-models/src/client/ModelsSection.module.css.mjs
58
- const css$3 = ".zGbnIq_section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}.zGbnIq_title{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;font-weight:500;line-height:24px}.zGbnIq_intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}.zGbnIq_notice{color:var(--dsw-alias-state-warn-label);margin:0;font-size:12px;line-height:18px}.zGbnIq_savedNotice{color:var(--dsw-alias-state-success-primary);margin:0;font-size:12px;line-height:18px}.zGbnIq_rows{flex-direction:column;gap:8px;margin:12px 0 0;padding:0;list-style:none;display:flex}.zGbnIq_rowCard{border:.5px solid var(--dsw-alias-settings-card-stroke);background:var(--dsw-alias-settings-card-fill);border-radius:var(--dsw-radius-xl);flex-direction:column;gap:12px;padding:12px 14px;display:flex}.zGbnIq_rowHead{align-items:center;gap:10px;display:flex}.zGbnIq_rowIdentity{align-items:center;gap:6px;min-width:0;display:inline-flex}.zGbnIq_rowName{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.zGbnIq_rowTag{border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-xs);color:var(--dsw-alias-label-secondary);flex:none;padding:1px 6px;font-size:11px;line-height:16px}.zGbnIq_credentialDot{box-sizing:border-box;corner-shape:round;border-radius:50%;flex:none;width:8px;height:8px;display:inline-block}.zGbnIq_credentialDotConfigured{background:var(--dsw-alias-state-success-primary)}.zGbnIq_credentialDotMissing{background:var(--dsw-alias-state-error-primary)}.zGbnIq_rowActions{align-items:center;gap:4px;margin-left:auto;display:inline-flex}.zGbnIq_primaryButton,.zGbnIq_secondaryButton,.zGbnIq_addButton{box-sizing:border-box;border-radius:var(--dsw-radius-md);height:36px;font:inherit;cursor:pointer;border:none;justify-content:center;align-items:center;gap:4px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.zGbnIq_primaryButton{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.zGbnIq_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.zGbnIq_secondaryButton,.zGbnIq_addButton{border:.5px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-primary);background:0 0}.zGbnIq_secondaryButton:hover:not(:disabled),.zGbnIq_addButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.zGbnIq_secondaryButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.zGbnIq_dangerButton{box-sizing:border-box;border-radius:var(--dsw-radius-md);height:36px;color:var(--dsw-alias-state-error-primary);font:inherit;cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.zGbnIq_dangerButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.zGbnIq_rowActions .zGbnIq_secondaryButton,.zGbnIq_rowActions .zGbnIq_dangerButton{border-radius:var(--dsw-radius-sm);height:28px;padding:0 10px;font-size:12px;line-height:18px}.zGbnIq_primaryButton:disabled,.zGbnIq_secondaryButton:disabled,.zGbnIq_dangerButton:disabled,.zGbnIq_addButton:disabled,.zGbnIq_linkButton:disabled,.zGbnIq_addModelButton:disabled{opacity:.4;cursor:default}.zGbnIq_primaryButton:focus-visible,.zGbnIq_secondaryButton:focus-visible,.zGbnIq_dangerButton:focus-visible,.zGbnIq_addButton:focus-visible,.zGbnIq_linkButton:focus-visible,.zGbnIq_addModelButton:focus-visible,.zGbnIq_iconButton:focus-visible,.zGbnIq_customizedSummary:focus-visible{box-shadow:0 0 0 2px var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline:none}.zGbnIq_editor{border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-bg-module-platform);flex-direction:column;gap:14px;padding:14px 16px;display:flex}.zGbnIq_editorHeader{align-items:baseline;gap:8px;display:flex}.zGbnIq_editorTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.zGbnIq_editorRoute{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.zGbnIq_field{flex-direction:column;gap:6px;display:flex}.zGbnIq_fieldLabel{color:var(--dsw-alias-label-secondary);align-items:center;gap:10px;font-size:12px;font-weight:500;line-height:18px;display:inline-flex}.zGbnIq_linkButton{box-sizing:border-box;border-radius:var(--dsw-radius-sm);height:28px;color:var(--dsw-alias-label-tertiary);font:inherit;cursor:pointer;background:0 0;border:none;align-items:center;padding:0 10px;font-size:12px;line-height:18px;display:inline-flex}.zGbnIq_linkButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.zGbnIq_advancedHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.zGbnIq_editorActions{justify-content:flex-end;gap:8px;display:flex}.zGbnIq_addBlock{flex-direction:column;gap:12px;display:flex}.zGbnIq_addActions{display:flex}.zGbnIq_addButton{border:1px dashed var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-lg);flex:1 1 0;gap:6px;min-width:180px;height:44px}.zGbnIq_addModes{flex-direction:column;align-items:flex-start;gap:8px;display:flex}.zGbnIq_addPanel{flex-direction:column;gap:14px;display:flex}.zGbnIq_addPanel[hidden]{display:none}.zGbnIq_addCard,.zGbnIq_setupCard{border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-bg-module-platform);flex-direction:column;gap:14px;padding:14px 16px;list-style:none;display:flex}.zGbnIq_addCard .zGbnIq_editor,.zGbnIq_setupCard .zGbnIq_editor{background:0 0;padding:0}.zGbnIq_customized{border-top:.5px solid var(--dsw-alias-border-l2);padding-top:10px}.zGbnIq_customizedSummary{border-radius:var(--dsw-radius-sm);cursor:pointer;width:fit-content;color:var(--dsw-alias-label-secondary);align-items:center;gap:6px;margin-left:-4px;padding:2px 4px;font-size:12px;font-weight:500;line-height:18px;list-style:none;display:flex}.zGbnIq_customizedSummary::-webkit-details-marker{display:none}.zGbnIq_customizedSummary:before{content:\"\";border-bottom:1.5px solid;border-right:1.5px solid;width:5px;height:5px;transition:transform .12s;transform:rotate(-45deg)translate(-1px,-1px)}.zGbnIq_customized[open]>.zGbnIq_customizedSummary:before{transform:rotate(45deg)translate(-1px,-1px)}.zGbnIq_customizedSummary:hover{color:var(--dsw-alias-label-primary)}.zGbnIq_customizedBody{flex-direction:column;gap:12px;padding-top:12px;display:flex}.zGbnIq_modelCatalog{border-top:.5px solid var(--dsw-alias-border-l2);flex-direction:column;gap:10px;padding-top:12px;display:flex}.zGbnIq_modelCatalogHeading{flex-direction:column;gap:2px;display:flex}.zGbnIq_modelCatalogTitle{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px}.zGbnIq_modelCatalogMeta,.zGbnIq_modelEmpty{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.zGbnIq_modelList{flex-direction:column;gap:8px;display:flex}.zGbnIq_modelListHead{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.zGbnIq_modelEntry{border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-lg);padding:6px}.zGbnIq_modelRow{grid-template-columns:minmax(0,1.4fr) minmax(0,1fr) auto auto;align-items:center;gap:6px;display:grid}.zGbnIq_iconButton{box-sizing:border-box;border-radius:var(--dsw-radius-sm);width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;display:inline-flex}.zGbnIq_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.zGbnIq_iconButton:disabled{cursor:default;opacity:.4}.zGbnIq_iconButtonDanger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary)}.zGbnIq_modelAdvanced{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;padding:8px 4px 2px;display:grid}.zGbnIq_modelField{flex-direction:column;gap:4px;min-width:0;display:flex}.zGbnIq_modelInputTypes{border:none;grid-column:1/-1;min-width:0;margin:0;padding:0}.zGbnIq_modelInputTypes legend{margin-bottom:4px;padding:0}.zGbnIq_modelInputChoices{flex-wrap:wrap;align-items:center;gap:16px;min-height:32px;display:flex}.zGbnIq_modelFieldLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.zGbnIq_modelEmpty{border:1px dashed var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-lg);text-align:center;padding:12px}.zGbnIq_addModelButton{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-sm);height:28px;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;background:0 0;align-self:flex-start;align-items:center;gap:4px;padding:0 10px;font-size:12px;line-height:18px;display:inline-flex}.zGbnIq_addModelButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.zGbnIq_input{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);width:100%;height:32px;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);padding:0 10px;font-size:14px;line-height:22px}select.zGbnIq_input{cursor:pointer;max-width:240px}.zGbnIq_input:focus{border-color:var(--dsw-alias-state-business-primary);outline:none}.zGbnIq_input::placeholder{color:var(--dsw-alias-label-dimmed)}.zGbnIq_input:disabled{opacity:.6;cursor:default}.zGbnIq_selectInput{appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%2381858C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");background-position:right 12px center;background-repeat:no-repeat;background-size:12px 12px;padding-right:32px}.zGbnIq_error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:18px}.zGbnIq_deleteDialog{width:min(480px,100%)}.zGbnIq_deleteConfirm:not(:disabled){border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.zGbnIq_deleteConfirm:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.zGbnIq_hiddenLabel{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}@media (prefers-reduced-motion:reduce){.zGbnIq_customizedSummary:before,.zGbnIq_switchThumb{transition:none}}.zGbnIq_fetchDialog{--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);max-width:520px}.zGbnIq_candidateToolbar{align-items:center;gap:8px;margin-bottom:6px;display:flex}.zGbnIq_candidateSearch{flex:240px;min-width:0}.zGbnIq_candidateList{flex-direction:column;gap:2px;max-height:320px;margin:0;padding:0;list-style:none;display:flex;overflow-y:auto}.zGbnIq_candidate{border-radius:var(--dsw-radius-md)}.zGbnIq_candidateLabel{cursor:pointer;align-items:center;gap:8px;padding:6px 8px;display:flex}.zGbnIq_candidateId{min-width:0;font-family:var(--ds-font-family-code);text-overflow:ellipsis;white-space:nowrap;flex:auto;font-size:13px;overflow:hidden}.zGbnIq_candidateEmpty{color:var(--dsw-alias-label-secondary);text-align:center;margin:24px 0;font-size:13px;line-height:20px}";
58
+ const css$3 = ".zGbnIq_section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}.zGbnIq_title{color:var(--dsw-alias-label-primary);margin:0;font-size:16px;font-weight:500;line-height:24px}.zGbnIq_intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:14px;line-height:22px}.zGbnIq_notice{color:var(--dsw-alias-state-warn-label);margin:0;font-size:12px;line-height:18px}.zGbnIq_savedNotice{color:var(--dsw-alias-state-success-primary);margin:0;font-size:12px;line-height:18px}.zGbnIq_rows{flex-direction:column;gap:8px;margin:12px 0 0;padding:0;list-style:none;display:flex}.zGbnIq_rowCard{border:.5px solid var(--dsw-alias-settings-card-stroke);background:var(--dsw-alias-settings-card-fill);border-radius:var(--dsw-radius-xl);flex-direction:column;gap:12px;padding:12px 14px;display:flex}.zGbnIq_rowHead{align-items:center;gap:10px;display:flex}.zGbnIq_rowIdentity{align-items:center;gap:6px;min-width:0;display:inline-flex}.zGbnIq_rowName{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.zGbnIq_rowTag{border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-xs);color:var(--dsw-alias-label-secondary);flex:none;padding:1px 6px;font-size:11px;line-height:16px}.zGbnIq_credentialDot{box-sizing:border-box;corner-shape:round;border-radius:50%;flex:none;width:8px;height:8px;display:inline-block}.zGbnIq_credentialDotConfigured{background:var(--dsw-alias-state-success-primary)}.zGbnIq_credentialDotMissing{background:var(--dsw-alias-state-error-primary)}.zGbnIq_accountStatus{color:var(--dsw-alias-state-success-primary);flex:none;align-items:center;gap:6px;font-size:13px;line-height:20px;display:inline-flex}.zGbnIq_rowActions{align-items:center;gap:4px;margin-left:auto;display:inline-flex}.zGbnIq_primaryButton,.zGbnIq_secondaryButton,.zGbnIq_addButton{box-sizing:border-box;border-radius:var(--dsw-radius-md);height:36px;font:inherit;cursor:pointer;border:none;justify-content:center;align-items:center;gap:4px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.zGbnIq_primaryButton{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.zGbnIq_primaryButton:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover)}.zGbnIq_secondaryButton,.zGbnIq_addButton{border:.5px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-primary);background:0 0}.zGbnIq_secondaryButton:hover:not(:disabled),.zGbnIq_addButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.zGbnIq_secondaryButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-solid)}.zGbnIq_dangerButton{box-sizing:border-box;border-radius:var(--dsw-radius-md);height:36px;color:var(--dsw-alias-state-error-primary);font:inherit;cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.zGbnIq_dangerButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.zGbnIq_rowActions .zGbnIq_secondaryButton,.zGbnIq_rowActions .zGbnIq_dangerButton{border-radius:var(--dsw-radius-sm);height:28px;padding:0 10px;font-size:12px;line-height:18px}.zGbnIq_primaryButton:disabled,.zGbnIq_secondaryButton:disabled,.zGbnIq_dangerButton:disabled,.zGbnIq_addButton:disabled,.zGbnIq_linkButton:disabled,.zGbnIq_addModelButton:disabled{opacity:.4;cursor:default}.zGbnIq_primaryButton:focus-visible,.zGbnIq_secondaryButton:focus-visible,.zGbnIq_dangerButton:focus-visible,.zGbnIq_addButton:focus-visible,.zGbnIq_linkButton:focus-visible,.zGbnIq_addModelButton:focus-visible,.zGbnIq_iconButton:focus-visible,.zGbnIq_customizedSummary:focus-visible{box-shadow:0 0 0 2px var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline:none}.zGbnIq_editor{border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-bg-module-platform);flex-direction:column;gap:14px;padding:14px 16px;display:flex}.zGbnIq_editorHeader{align-items:baseline;gap:8px;display:flex}.zGbnIq_editorTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:500;line-height:22px}.zGbnIq_editorRoute{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.zGbnIq_field{flex-direction:column;gap:6px;display:flex}.zGbnIq_fieldLabel{color:var(--dsw-alias-label-secondary);align-items:center;gap:10px;font-size:12px;font-weight:500;line-height:18px;display:inline-flex}.zGbnIq_helpButton{border:.5px solid var(--dsw-alias-border-l3);corner-shape:round;width:16px;height:16px;color:var(--dsw-alias-label-tertiary);font:inherit;text-align:center;cursor:help;background:0 0;border-radius:50%;padding:0;font-size:11px;line-height:15px}.zGbnIq_helpButton:hover,.zGbnIq_helpButton:focus-visible{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-tertiary)}.zGbnIq_linkButton{box-sizing:border-box;border-radius:var(--dsw-radius-sm);height:28px;color:var(--dsw-alias-label-tertiary);font:inherit;cursor:pointer;background:0 0;border:none;align-items:center;padding:0 10px;font-size:12px;line-height:18px;display:inline-flex}.zGbnIq_linkButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.zGbnIq_advancedHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.zGbnIq_editorActions{justify-content:flex-end;gap:8px;display:flex}.zGbnIq_addBlock{flex-direction:column;gap:12px;display:flex}.zGbnIq_addActions{display:flex}.zGbnIq_addButton{border:1px dashed var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-lg);flex:1 1 0;gap:6px;min-width:180px;height:44px}.zGbnIq_addModes{flex-direction:column;align-items:flex-start;gap:8px;display:flex}.zGbnIq_addPanel{flex-direction:column;gap:14px;display:flex}.zGbnIq_addPanel[hidden]{display:none}.zGbnIq_addCard,.zGbnIq_setupCard{border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-bg-module-platform);flex-direction:column;gap:14px;padding:14px 16px;list-style:none;display:flex}.zGbnIq_addCard .zGbnIq_editor,.zGbnIq_setupCard .zGbnIq_editor{background:0 0;padding:0}.zGbnIq_customized{border-top:.5px solid var(--dsw-alias-border-l2);padding-top:10px}.zGbnIq_customizedSummary{border-radius:var(--dsw-radius-sm);cursor:pointer;width:fit-content;color:var(--dsw-alias-label-secondary);align-items:center;gap:6px;margin-left:-4px;padding:2px 4px;font-size:12px;font-weight:500;line-height:18px;list-style:none;display:flex}.zGbnIq_customizedSummary::-webkit-details-marker{display:none}.zGbnIq_customizedSummary:before{content:\"\";border-bottom:1.5px solid;border-right:1.5px solid;width:5px;height:5px;transition:transform .12s;transform:rotate(-45deg)translate(-1px,-1px)}.zGbnIq_customized[open]>.zGbnIq_customizedSummary:before{transform:rotate(45deg)translate(-1px,-1px)}.zGbnIq_customizedSummary:hover{color:var(--dsw-alias-label-primary)}.zGbnIq_customizedBody{flex-direction:column;gap:12px;padding-top:12px;display:flex}.zGbnIq_modelCatalog{border-top:.5px solid var(--dsw-alias-border-l2);flex-direction:column;gap:10px;padding-top:12px;display:flex}.zGbnIq_modelCatalogHeading{flex-direction:column;gap:2px;display:flex}.zGbnIq_modelCatalogTitle{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px}.zGbnIq_modelCatalogMeta,.zGbnIq_modelEmpty{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.zGbnIq_modelList{flex-direction:column;gap:8px;display:flex}.zGbnIq_modelListHead{justify-content:space-between;align-items:flex-start;gap:12px;display:flex}.zGbnIq_modelEntry{border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-lg);padding:6px}.zGbnIq_modelRow{grid-template-columns:minmax(0,1.4fr) minmax(0,1fr) auto auto;align-items:center;gap:6px;display:grid}.zGbnIq_iconButton{box-sizing:border-box;border-radius:var(--dsw-radius-sm);width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;display:inline-flex}.zGbnIq_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.zGbnIq_iconButton:disabled{cursor:default;opacity:.4}.zGbnIq_iconButtonDanger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary)}.zGbnIq_modelAdvanced{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;padding:8px 4px 2px;display:grid}.zGbnIq_modelField{flex-direction:column;gap:4px;min-width:0;display:flex}.zGbnIq_modelInputTypes{border:none;grid-column:1/-1;min-width:0;margin:0;padding:0}.zGbnIq_modelInputTypes legend{margin-bottom:4px;padding:0}.zGbnIq_modelInputChoices{flex-wrap:wrap;align-items:center;gap:16px;min-height:32px;display:flex}.zGbnIq_modelFieldLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.zGbnIq_modelEmpty{border:1px dashed var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-lg);text-align:center;padding:12px}.zGbnIq_addModelButton{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-sm);height:28px;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;background:0 0;align-self:flex-start;align-items:center;gap:4px;padding:0 10px;font-size:12px;line-height:18px;display:inline-flex}.zGbnIq_addModelButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.zGbnIq_input{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l4);border-radius:var(--dsw-radius-md);width:100%;height:32px;font:inherit;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);padding:0 10px;font-size:14px;line-height:22px}select.zGbnIq_input{cursor:pointer;max-width:240px}.zGbnIq_input:focus{border-color:var(--dsw-alias-state-business-primary);outline:none}.zGbnIq_input::placeholder{color:var(--dsw-alias-label-dimmed)}.zGbnIq_input:disabled{opacity:.6;cursor:default}.zGbnIq_keyInput{position:relative}.zGbnIq_keyInputFilled{padding-right:40px}.zGbnIq_keyToggle{border-radius:var(--dsw-radius-md);width:32px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;padding:0;display:inline-flex;position:absolute;inset:1px 1px 1px auto}.zGbnIq_keyToggle:hover:not(:disabled){color:var(--dsw-alias-label-primary)}.zGbnIq_keyToggle:focus-visible{outline:var(--dsw-focus-ring-width) solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:-2px}.zGbnIq_keyToggle:disabled{opacity:.6;cursor:default}.zGbnIq_selectInput{appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%2381858C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E\");background-position:right 12px center;background-repeat:no-repeat;background-size:12px 12px;padding-right:32px}.zGbnIq_error{color:var(--dsw-alias-state-error-primary);margin:0;font-size:12px;line-height:18px}.zGbnIq_deleteDialog{width:min(480px,100%)}.zGbnIq_deleteConfirm:not(:disabled){border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.zGbnIq_deleteConfirm:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover-danger)}.zGbnIq_hiddenLabel{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}@media (prefers-reduced-motion:reduce){.zGbnIq_customizedSummary:before,.zGbnIq_switchThumb{transition:none}}.zGbnIq_fetchDialog{--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);max-width:520px}.zGbnIq_candidateToolbar{align-items:center;gap:8px;margin-bottom:6px;display:flex}.zGbnIq_candidateSearch{flex:240px;min-width:0}.zGbnIq_candidateList{flex-direction:column;gap:2px;max-height:320px;margin:0;padding:0;list-style:none;display:flex;overflow-y:auto}.zGbnIq_candidate{border-radius:var(--dsw-radius-md)}.zGbnIq_candidateLabel{cursor:pointer;align-items:center;gap:8px;padding:6px 8px;display:flex}.zGbnIq_candidateId{min-width:0;font-family:var(--ds-font-family-code);text-overflow:ellipsis;white-space:nowrap;flex:auto;font-size:13px;overflow:hidden}.zGbnIq_candidateEmpty{color:var(--dsw-alias-label-secondary);text-align:center;margin:24px 0;font-size:13px;line-height:20px}";
59
59
  const tagId$3 = "@deepseek-ai/dsh-client-ui-settings-models/ModelsSection.module.css";
60
60
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
61
61
  const tag = document.createElement("style");
@@ -65,6 +65,7 @@ window.__ModuleLoader__.load({
65
65
  document.head.appendChild(tag);
66
66
  }
67
67
  var ModelsSection_module_css_default = {
68
+ "accountStatus": "zGbnIq_accountStatus",
68
69
  "addActions": "zGbnIq_addActions",
69
70
  "addBlock": "zGbnIq_addBlock",
70
71
  "addButton": "zGbnIq_addButton",
@@ -98,11 +99,15 @@ window.__ModuleLoader__.load({
98
99
  "fetchDialog": "zGbnIq_fetchDialog",
99
100
  "field": "zGbnIq_field",
100
101
  "fieldLabel": "zGbnIq_fieldLabel",
102
+ "helpButton": "zGbnIq_helpButton",
101
103
  "hiddenLabel": "zGbnIq_hiddenLabel",
102
104
  "iconButton": "zGbnIq_iconButton",
103
105
  "iconButtonDanger": "zGbnIq_iconButtonDanger",
104
106
  "input": "zGbnIq_input",
105
107
  "intro": "zGbnIq_intro",
108
+ "keyInput": "zGbnIq_keyInput",
109
+ "keyInputFilled": "zGbnIq_keyInputFilled",
110
+ "keyToggle": "zGbnIq_keyToggle",
106
111
  "linkButton": "zGbnIq_linkButton",
107
112
  "modelAdvanced": "zGbnIq_modelAdvanced",
108
113
  "modelCatalog": "zGbnIq_modelCatalog",
@@ -136,6 +141,55 @@ window.__ModuleLoader__.load({
136
141
  "title": "zGbnIq_title"
137
142
  };
138
143
  //#endregion
144
+ //#region lib/types/client/ApiKeyInput.js
145
+ /** Write-only credential input with the same reveal behavior as masked conversation text. */
146
+ const EYE_CONTOUR = "M1.5 8C2.9 5.25 5.25 3.5 8 3.5C10.75 3.5 13.1 5.25 14.5 8C13.1 10.75 10.75 12.5 8 12.5C5.25 12.5 2.9 10.75 1.5 8Z";
147
+ const EYE_PATH = `${EYE_CONTOUR}M10 8C10 9.1 9.1 10 8 10C6.9 10 6 9.1 6 8C6 6.9 6.9 6 8 6C9.1 6 10 6.9 10 8Z`;
148
+ const EYE_OFF_PATH = `${EYE_CONTOUR}M2.5 2.5L13.5 13.5`;
149
+ /**
150
+ * Reveal only a nonempty draft; a different value starts masked, as in SecretText.
151
+ * @param props - current draft, input attributes, and translated control labels.
152
+ * @returns a password input with an optional reveal button.
153
+ */
154
+ function ApiKeyInput({ value, t, ...props }) {
155
+ const [revealedValue, setRevealedValue] = (0, react.useState)();
156
+ const revealed = value.length > 0 && revealedValue === value;
157
+ return (0, react_jsx_runtime.jsxs)("div", {
158
+ className: ModelsSection_module_css_default["keyInput"],
159
+ children: [(0, react_jsx_runtime.jsx)("input", {
160
+ ...props,
161
+ className: `${ModelsSection_module_css_default["input"]} ${value.length > 0 ? ModelsSection_module_css_default["keyInputFilled"] : ""}`,
162
+ type: revealed ? "text" : "password",
163
+ autoComplete: "new-password",
164
+ value,
165
+ onChange: (event) => {
166
+ setRevealedValue(void 0);
167
+ props.onChange?.(event);
168
+ }
169
+ }), value.length === 0 ? null : (0, react_jsx_runtime.jsx)("button", {
170
+ type: "button",
171
+ className: ModelsSection_module_css_default["keyToggle"],
172
+ "aria-label": t(revealed ? "keyHide" : "keyReveal"),
173
+ "aria-pressed": revealed,
174
+ disabled: props.disabled,
175
+ onClick: () => {
176
+ setRevealedValue(revealed ? void 0 : value);
177
+ },
178
+ children: (0, react_jsx_runtime.jsx)("svg", {
179
+ width: "16",
180
+ height: "16",
181
+ viewBox: "0 0 16 16",
182
+ fill: "none",
183
+ stroke: "currentColor",
184
+ strokeWidth: 1.3,
185
+ strokeLinecap: "round",
186
+ "aria-hidden": "true",
187
+ children: (0, react_jsx_runtime.jsx)("path", { d: revealed ? EYE_OFF_PATH : EYE_PATH })
188
+ })
189
+ })]
190
+ });
191
+ }
192
+ //#endregion
139
193
  //#region lib/types/client/EditorFooter.js
140
194
  /**
141
195
  * Render one provider card's action row.
@@ -914,6 +968,9 @@ window.__ModuleLoader__.load({
914
968
  settingsPath: [...entry.settingsPath],
915
969
  active: active.has(entry.provider),
916
970
  ...entry.declared === void 0 ? {} : { declared: entry.declared },
971
+ ...entry.defaultBaseURL === void 0 ? {} : { defaultBaseURL: entry.defaultBaseURL },
972
+ ...entry.baseURLEnv === void 0 ? {} : { baseURLEnv: entry.baseURLEnv },
973
+ ...entry.nativeAuth === void 0 ? {} : { nativeAuth: entry.nativeAuth },
917
974
  ...entry.error === void 0 ? {} : { error: entry.error }
918
975
  }));
919
976
  for (const provider of registered) {
@@ -981,6 +1038,8 @@ window.__ModuleLoader__.load({
981
1038
  });
982
1039
  /** Latest load wins; an older response never overwrites a newer one. */
983
1040
  generation = 0;
1041
+ /** Callers follow the current refresh's outcome, including callers whose read was superseded. */
1042
+ pendingLoads = /* @__PURE__ */ new Set();
984
1043
  /**
985
1044
  * @param ctx - the page plugin's context, whose `remote.llm` and
986
1045
  * `remote.credentials` namespaces carry the directory and credential reads.
@@ -997,11 +1056,28 @@ window.__ModuleLoader__.load({
997
1056
  * settings answer in parallel, then one batched credential describe over
998
1057
  * every referenced ref. Provider failure or absence of an initial settings
999
1058
  * answer keeps the last good rows and surfaces an error; a failed settings
1000
- * refresh reuses the mirror's held view.
1001
- * @returns nothing; the snapshot carries the outcome.
1059
+ * refresh reuses the mirror's held view. Concurrent callers resolve only
1060
+ * after the current refresh publishes its ready or error snapshot.
1061
+ * @returns resolution after the current snapshot is published.
1062
+ * @throws the current read's unexpected rejection; superseded rejections are ignored.
1002
1063
  */
1003
- async load() {
1064
+ load() {
1065
+ const pending = Promise.withResolvers();
1066
+ this.pendingLoads.add(pending);
1004
1067
  const generation = ++this.generation;
1068
+ this.read(generation).then(() => {
1069
+ this.settleLoads(generation, (load) => {
1070
+ load.resolve();
1071
+ });
1072
+ }, (error) => {
1073
+ this.settleLoads(generation, (load) => {
1074
+ load.reject(error);
1075
+ });
1076
+ });
1077
+ return pending.promise;
1078
+ }
1079
+ /** Publish one joined answer; stale answers leave the current refresh in charge. */
1080
+ async read(generation) {
1005
1081
  this.store.update((s) => {
1006
1082
  s.status = "loading";
1007
1083
  s.error = null;
@@ -1011,17 +1087,20 @@ window.__ModuleLoader__.load({
1011
1087
  this.ctx.remote.llm.listConfigurableProviders(),
1012
1088
  this.describeFace.ensure()
1013
1089
  ]);
1090
+ if (generation !== this.generation) return;
1014
1091
  if (!registered.ok) {
1015
- this.failLoad(generation, registered.error.message);
1092
+ this.failLoad(registered.error.message);
1016
1093
  return;
1017
1094
  }
1018
1095
  if (!declared.ok) {
1019
- this.failLoad(generation, declared.error.message);
1096
+ this.failLoad(declared.error.message);
1020
1097
  return;
1021
1098
  }
1022
1099
  const mirrored = this.describeFace.getSnapshot();
1023
1100
  if (mirrored.view === void 0) {
1024
- this.failLoad(generation, mirrored.error ?? "settings are unavailable in this browser");
1101
+ /* v8 ignore next -- SettingsDescribeFace.ensure settles with a view or the read's error */
1102
+ if (mirrored.error === null) throw new Error("settings describe settled with neither a view nor an error");
1103
+ this.failLoad(mirrored.error);
1025
1104
  return;
1026
1105
  }
1027
1106
  const providers = joinProviderDirectory(registered.value, declared.value);
@@ -1042,13 +1121,16 @@ window.__ModuleLoader__.load({
1042
1121
  const catalog = await this.ctx.remote.session.modelCatalog();
1043
1122
  for (const row of rows) if (row.entry.provider === "deepseek-account") row.accountAvailable = catalog.ok && catalog.value.groups.some((group) => group.id === "deepseek-account" && group.models.length > 0);
1044
1123
  }
1045
- const refs = [...new Set(rows.filter((row) => row.entry.provider !== "deepseek-account").map((row) => row.apiKeyEnv ?? deriveKeyRef(row.entry.provider)))];
1046
- let credentials = {};
1124
+ const refs = [...new Set(rows.filter((row) => row.entry.provider !== "deepseek-account").flatMap((row) => row.apiKeyEnv === void 0 ? [deriveKeyRef(row.entry.provider), ...row.entry.nativeAuth?.env ?? []] : [row.apiKeyEnv]))];
1125
+ const credentials = {};
1047
1126
  let credentialError = null;
1048
- if (refs.length > 0) {
1049
- const response = await this.ctx.remote.credentials.describe(refs);
1050
- if (response.ok) credentials = response.value;
1051
- else credentialError = response.error.message;
1127
+ for (let index = 0; index < refs.length; index += 64) {
1128
+ const response = await this.ctx.remote.credentials.describe(refs.slice(index, index + 64));
1129
+ if (response.ok) Object.assign(credentials, response.value);
1130
+ else {
1131
+ credentialError = response.error.message;
1132
+ break;
1133
+ }
1052
1134
  }
1053
1135
  if (generation !== this.generation) return;
1054
1136
  this.store.update((s) => {
@@ -1063,15 +1145,25 @@ window.__ModuleLoader__.load({
1063
1145
  return {
1064
1146
  ...row,
1065
1147
  ...named === void 0 ? {} : { credential: named },
1066
- ...derived === void 0 ? {} : { derivedCredential: derived }
1148
+ ...derived === void 0 ? {} : { derivedCredential: derived },
1149
+ ...credentialError === null ? { credentialChecks: Object.fromEntries((row.apiKeyEnv === void 0 ? row.entry.nativeAuth?.env ?? [] : [row.apiKeyEnv]).flatMap((ref) => {
1150
+ const info = credentials[ref];
1151
+ return info === void 0 ? [] : [[ref, info]];
1152
+ })) } : {}
1067
1153
  };
1068
1154
  });
1069
1155
  s.namespaces = namespaces;
1070
1156
  });
1071
1157
  }
1072
- /** Publish one load's failure text, unless a newer load already took over. */
1073
- failLoad(generation, message) {
1158
+ /** Settle callers only while this read still owns the current refresh. */
1159
+ settleLoads(generation, settle) {
1074
1160
  if (generation !== this.generation) return;
1161
+ const pending = [...this.pendingLoads];
1162
+ this.pendingLoads.clear();
1163
+ for (const load of pending) settle(load);
1164
+ }
1165
+ /** Publish the current joined read's failure text. */
1166
+ failLoad(message) {
1075
1167
  this.store.update((s) => {
1076
1168
  s.status = "error";
1077
1169
  s.error = message;
@@ -1375,10 +1467,8 @@ window.__ModuleLoader__.load({
1375
1467
  className: ModelsSection_module_css_default["fieldLabel"],
1376
1468
  children: t("keyInput")
1377
1469
  }),
1378
- (0, react_jsx_runtime.jsx)("input", {
1379
- className: ModelsSection_module_css_default["input"],
1380
- type: "password",
1381
- autoComplete: "new-password",
1470
+ (0, react_jsx_runtime.jsx)(ApiKeyInput, {
1471
+ t,
1382
1472
  value: keyDraft,
1383
1473
  placeholder: t("keyPlaceholder"),
1384
1474
  "aria-label": t("keyInput"),
@@ -1510,7 +1600,6 @@ window.__ModuleLoader__.load({
1510
1600
  const { namespace, schema, settingsPath, operations, t } = props;
1511
1601
  const [draft, setDraft] = (0, react.useState)(() => draftAt(schema, namespace, settingsPath));
1512
1602
  const [keyDraft, setKeyDraft] = (0, react.useState)("");
1513
- const [keyState, setKeyState] = (0, react.useState)(void 0);
1514
1603
  const [busy, setBusy] = (0, react.useState)(false);
1515
1604
  const [listBusy, setListBusy] = (0, react.useState)(false);
1516
1605
  const { onBusyChange } = props;
@@ -1532,30 +1621,13 @@ window.__ModuleLoader__.load({
1532
1621
  ]);
1533
1622
  const fallback = schema.getPath(namespace.value, settingsPath);
1534
1623
  const disabled = props.readOnly || busy;
1535
- const accountProvider = props.provider === "deepseek-account";
1536
- const layout = accountProvider ? "deepseek" : layoutOf(namespace.ns);
1624
+ const layout = layoutOf(namespace.ns);
1537
1625
  const keyRef = refFor(schema, namespace, settingsPath, props.provider);
1538
1626
  const protocols = (0, react.useMemo)(() => layout === "pi-ai" ? protocolChoices(namespace, schema) : [], [
1539
1627
  layout,
1540
1628
  namespace,
1541
1629
  schema
1542
1630
  ]);
1543
- (0, react.useEffect)(() => {
1544
- if (accountProvider) return;
1545
- let stale = false;
1546
- setKeyState(void 0);
1547
- operations.describeCredential(keyRef).then((described) => {
1548
- if (stale) return;
1549
- setKeyState(described);
1550
- });
1551
- return () => {
1552
- stale = true;
1553
- };
1554
- }, [
1555
- operations,
1556
- keyRef,
1557
- accountProvider
1558
- ]);
1559
1631
  const stringAt = (source, key) => {
1560
1632
  const value = schema.getPath(source, [key]);
1561
1633
  return typeof value === "string" && value.trim().length > 0 ? value : void 0;
@@ -1637,7 +1709,6 @@ window.__ModuleLoader__.load({
1637
1709
  props.t("settingsPathUnresolvable")
1638
1710
  ]
1639
1711
  });
1640
- const keyLocked = keyState?.writable === false;
1641
1712
  /**
1642
1713
  * The catalog beneath the user layer: what the composition entry pinned, or
1643
1714
  * else the schema default that `resolve` would supply. The effective value
@@ -1661,7 +1732,38 @@ window.__ModuleLoader__.load({
1661
1732
  const defaultContextWindow = schema.getPath(fallback, ["defaultContextWindow"]);
1662
1733
  const defaultMaxTokens = schema.getPath(fallback, ["maxTokens"]);
1663
1734
  const defaultInput = schema.getPath(fallback, ["defaultInput"]);
1664
- const keyPlaceholder = keyLocked ? t("keyEnvLocked") : keyState?.configured === true && props.credentialRequired !== true ? t("keyStored") : family === "pi-ai" ? t("keyPlaceholderNative") : t("keyPlaceholder");
1735
+ const baseURL = stringAt(fallback, "baseURL") ?? props.defaultBaseURL;
1736
+ const endpointEnv = props.baseURLEnv ?? [];
1737
+ const endpointHint = family === "deepseek" ? t("deepSeekEndpointHint") : baseURL === void 0 && endpointEnv.length > 0 ? t("baseUrlEnvironment").replace("{env}", () => endpointEnv.join(", ")) : void 0;
1738
+ const namedRef = stringAt(fallback, "apiKeyEnv");
1739
+ const nativeAuth = namedRef === void 0 ? props.nativeAuth : void 0;
1740
+ const authRefs = namedRef === void 0 ? (nativeAuth?.env ?? []).filter((ref) => !endpointEnv.includes(ref)) : [namedRef];
1741
+ const visibleRefs = authRefs.filter((ref) => !ref.toLowerCase().includes("oauth"));
1742
+ const keyHint = visibleRefs[0];
1743
+ const checkedRefs = authRefs.map((ref) => [ref, props.credentialChecks?.[ref] ?? (ref === namedRef ? props.credential : void 0)]);
1744
+ const detected = checkedRefs.find(([, info]) => info?.configured === true);
1745
+ const detectedRef = detected?.[0];
1746
+ const detectedVisibleRef = detectedRef === void 0 || !visibleRefs.includes(detectedRef) ? void 0 : detectedRef;
1747
+ const missingRefs = checkedRefs.length > 0 && checkedRefs.every(([, info]) => info?.configured === false);
1748
+ const savedCredential = detected?.[1]?.source === "file";
1749
+ const credentialHint = keyDraft.length > 0 || savedCredential || keyHint === void 0 || props.credentialRequired === true ? void 0 : t(detectedRef === void 0 ? missingRefs ? "keyFallbackMissing" : "keyFallbackUnchecked" : detectedVisibleRef === void 0 ? "keyFallbackDetectedHidden" : "keyFallbackDetected").replace("{hint}", () => detectedVisibleRef ?? keyHint);
1750
+ const keyHelp = visibleRefs.length === 0 ? void 0 : t("keyHelp").replace("{env}", () => visibleRefs.join(" / "));
1751
+ const authHelp = keyHelp === void 0 ? null : (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Tooltip, {
1752
+ label: keyHelp,
1753
+ side: "right",
1754
+ delayMs: 0,
1755
+ focusDelayMs: 0,
1756
+ portal: true,
1757
+ openOnClick: true,
1758
+ children: (0, react_jsx_runtime.jsx)("button", {
1759
+ type: "button",
1760
+ className: ModelsSection_module_css_default["helpButton"],
1761
+ "aria-label": t("authHelp"),
1762
+ children: "?"
1763
+ })
1764
+ });
1765
+ const keylessNativeAuth = nativeAuth?.env.length === 0 && nativeAuth.oauth;
1766
+ const keyPlaceholder = keyHint === void 0 || props.credentialRequired === true ? t("keyPlaceholder") : t(savedCredential ? "keyPlaceholderSaved" : "keyPlaceholderFallback").replace("{hint}", () => keyHint);
1665
1767
  /** What both family editors take: the rows, whose layer owns them, and the two writes. */
1666
1768
  const catalogProps = {
1667
1769
  models,
@@ -1675,33 +1777,32 @@ window.__ModuleLoader__.load({
1675
1777
  setDraft((current) => schema.deletePath(current, ["models"]));
1676
1778
  }
1677
1779
  };
1678
- if (accountProvider) return (0, react_jsx_runtime.jsx)(DeepSeekModelsEditor, {
1679
- ...catalogProps,
1680
- defaultContextWindow: typeof defaultContextWindow === "number" ? defaultContextWindow : void 0,
1681
- defaultMaxTokens: typeof defaultMaxTokens === "number" ? defaultMaxTokens : void 0
1682
- });
1683
- return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
1780
+ return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [keylessNativeAuth ? null : (0, react_jsx_runtime.jsxs)("div", {
1684
1781
  className: ModelsSection_module_css_default["field"],
1685
1782
  children: [
1686
- (0, react_jsx_runtime.jsx)("span", {
1783
+ (0, react_jsx_runtime.jsxs)("span", {
1687
1784
  className: ModelsSection_module_css_default["fieldLabel"],
1688
- children: t("keyInput")
1785
+ children: [t("keyInput"), authHelp]
1689
1786
  }),
1690
- (0, react_jsx_runtime.jsx)("input", {
1691
- className: ModelsSection_module_css_default["input"],
1692
- type: "password",
1693
- autoComplete: "new-password",
1787
+ (0, react_jsx_runtime.jsx)(ApiKeyInput, {
1788
+ t,
1694
1789
  value: keyDraft,
1695
1790
  placeholder: keyPlaceholder,
1696
1791
  "aria-label": t("keyInput"),
1697
1792
  "aria-invalid": shownKeyFailure !== void 0,
1793
+ "aria-describedby": credentialHint === void 0 ? void 0 : `${props.provider}-credential-hint`,
1698
1794
  required: props.credentialRequired === true,
1699
1795
  autoFocus: props.autoFocusCredential === true,
1700
- disabled: disabled || keyLocked,
1796
+ disabled,
1701
1797
  onChange: (event) => {
1702
1798
  setKeyDraft(event.target.value);
1703
1799
  }
1704
1800
  }),
1801
+ credentialHint === void 0 ? null : (0, react_jsx_runtime.jsx)("p", {
1802
+ id: `${props.provider}-credential-hint`,
1803
+ className: ModelsSection_module_css_default[missingRefs ? "notice" : detectedRef === void 0 ? "advancedHint" : "savedNotice"],
1804
+ children: credentialHint
1805
+ }),
1705
1806
  shownKeyFailure === void 0 ? null : (0, react_jsx_runtime.jsx)("p", {
1706
1807
  className: ModelsSection_module_css_default["error"],
1707
1808
  children: t(shownKeyFailure)
@@ -1743,19 +1844,19 @@ window.__ModuleLoader__.load({
1743
1844
  className: ModelsSection_module_css_default["input"],
1744
1845
  type: "text",
1745
1846
  value: stringAt(draft, "baseURL") ?? "",
1746
- placeholder: family === "deepseek" ? t("deepSeekBaseUrl") : stringAt(fallback, "baseURL") ?? t("baseUrlDefault"),
1747
- "aria-describedby": family === "deepseek" ? `${props.provider}-endpoint-hint` : void 0,
1847
+ placeholder: family === "deepseek" ? t("deepSeekBaseUrl") : baseURL === void 0 ? t(endpointEnv.length > 0 ? "baseUrlConfigured" : "baseUrlDefault") : t("baseUrlPlaceholder").replace("{url}", () => baseURL),
1848
+ "aria-describedby": endpointHint === void 0 ? void 0 : `${props.provider}-endpoint-hint`,
1748
1849
  "aria-label": t("baseUrl"),
1749
1850
  disabled,
1750
1851
  onChange: (event) => {
1751
1852
  setField("baseURL", event.target.value === "" ? void 0 : event.target.value);
1752
1853
  }
1753
1854
  }),
1754
- family === "deepseek" ? (0, react_jsx_runtime.jsx)("span", {
1855
+ endpointHint === void 0 ? null : (0, react_jsx_runtime.jsx)("span", {
1755
1856
  id: `${props.provider}-endpoint-hint`,
1756
1857
  className: ModelsSection_module_css_default["advancedHint"],
1757
- children: t("deepSeekEndpointHint")
1758
- }) : null
1858
+ children: endpointHint
1859
+ })
1759
1860
  ]
1760
1861
  }),
1761
1862
  ownsIdentity ? (0, react_jsx_runtime.jsxs)("div", {
@@ -1865,12 +1966,17 @@ window.__ModuleLoader__.load({
1865
1966
  * post-apply reload.
1866
1967
  */
1867
1968
  /** Render an editor for either the setup posture or an expanded provider row. */
1868
- function renderProviderEditor({ target, ...props }) {
1969
+ function renderProviderEditor({ target, row, ...props }) {
1869
1970
  return (0, react_jsx_runtime.jsx)(ProviderEditor, {
1870
1971
  provider: target.provider,
1871
1972
  displayName: target.displayName,
1872
1973
  settingsPath: target.settingsPath,
1873
1974
  ...target.declared === true ? { declared: true } : {},
1975
+ ...target.defaultBaseURL === void 0 ? {} : { defaultBaseURL: target.defaultBaseURL },
1976
+ ...target.baseURLEnv === void 0 ? {} : { baseURLEnv: target.baseURLEnv },
1977
+ ...target.nativeAuth === void 0 ? {} : { nativeAuth: target.nativeAuth },
1978
+ ...row.credential === void 0 ? {} : { credential: row.credential },
1979
+ ...row.credentialChecks === void 0 ? {} : { credentialChecks: row.credentialChecks },
1874
1980
  ...props
1875
1981
  });
1876
1982
  }
@@ -1930,7 +2036,10 @@ window.__ModuleLoader__.load({
1930
2036
  settingsNs: row.entry.settingsNs,
1931
2037
  settingsPath: row.entry.settingsPath,
1932
2038
  ...credentialRef === void 0 ? {} : { credentialRef },
1933
- ...row.entry.declared === true ? { declared: true } : {}
2039
+ ...row.entry.declared === true ? { declared: true } : {},
2040
+ ...row.entry.defaultBaseURL === void 0 ? {} : { defaultBaseURL: row.entry.defaultBaseURL },
2041
+ ...row.entry.baseURLEnv === void 0 ? {} : { baseURLEnv: row.entry.baseURLEnv },
2042
+ ...row.entry.nativeAuth === void 0 ? {} : { nativeAuth: row.entry.nativeAuth }
1934
2043
  };
1935
2044
  }
1936
2045
  /** Stable visible and accessible identity for one provider target. */
@@ -1973,6 +2082,10 @@ window.__ModuleLoader__.load({
1973
2082
  }
1974
2083
  } : row)
1975
2084
  };
2085
+ /** A new or failed surface waits for a current snapshot; background refreshes preserve its drafts. */
2086
+ const [hasReadySnapshot, setHasReadySnapshot] = (0, react.useState)(state.status === "ready");
2087
+ if (state.status === "ready" && !hasReadySnapshot) setHasReadySnapshot(true);
2088
+ if (state.status === "error" && hasReadySnapshot) setHasReadySnapshot(false);
1976
2089
  const [editing, setEditing] = (0, react.useState)(void 0);
1977
2090
  const [addOpen, setAddOpen] = (0, react.useState)(false);
1978
2091
  const [addMode, setAddMode] = (0, react.useState)("catalog");
@@ -2060,6 +2173,16 @@ window.__ModuleLoader__.load({
2060
2173
  })]
2061
2174
  });
2062
2175
  }
2176
+ if (!hasReadySnapshot && state.status !== "ready") return (0, react_jsx_runtime.jsxs)("div", {
2177
+ className: ModelsSection_module_css_default["section"],
2178
+ children: [(0, react_jsx_runtime.jsx)("h2", {
2179
+ className: ModelsSection_module_css_default["title"],
2180
+ children: t("title")
2181
+ }), (0, react_jsx_runtime.jsx)("p", {
2182
+ className: ModelsSection_module_css_default["intro"],
2183
+ children: t("intro")
2184
+ })]
2185
+ });
2063
2186
  const savedRow = savedTarget === void 0 ? void 0 : state.rows.find((row) => row.entry.provider === savedTarget.provider);
2064
2187
  const savedIdentity = savedRow === void 0 ? savedTarget : {
2065
2188
  provider: savedRow.entry.provider,
@@ -2138,6 +2261,7 @@ window.__ModuleLoader__.load({
2138
2261
  error,
2139
2262
  renderProviderEditor({
2140
2263
  target,
2264
+ row,
2141
2265
  namespace,
2142
2266
  schema,
2143
2267
  operations,
@@ -2173,6 +2297,13 @@ window.__ModuleLoader__.load({
2173
2297
  className: ModelsSection_module_css_default["rowTag"],
2174
2298
  children: t("customTag")
2175
2299
  }) : null,
2300
+ row.entry.provider === "deepseek-account" ? (0, react_jsx_runtime.jsxs)("span", {
2301
+ className: ModelsSection_module_css_default["accountStatus"],
2302
+ children: [(0, react_jsx_runtime.jsx)("span", {
2303
+ className: `${ModelsSection_module_css_default["credentialDot"]} ${ModelsSection_module_css_default["credentialDotConfigured"]}`,
2304
+ "aria-hidden": "true"
2305
+ }), t("accountSignedIn")]
2306
+ }) : null,
2176
2307
  credentialConfigured ? (0, react_jsx_runtime.jsx)("span", {
2177
2308
  className: `${ModelsSection_module_css_default["credentialDot"]} ${ModelsSection_module_css_default["credentialDotConfigured"]}`,
2178
2309
  role: "img",
@@ -2187,11 +2318,13 @@ window.__ModuleLoader__.load({
2187
2318
  ]
2188
2319
  }), (0, react_jsx_runtime.jsxs)("span", {
2189
2320
  className: ModelsSection_module_css_default["rowActions"],
2190
- children: [(0, react_jsx_runtime.jsx)("button", {
2321
+ children: [row.entry.provider === "deepseek-account" ? null : (0, react_jsx_runtime.jsx)("button", {
2191
2322
  type: "button",
2192
2323
  className: ModelsSection_module_css_default["secondaryButton"],
2193
2324
  "aria-label": providerCopy(t("editProvider"), target),
2325
+ disabled: !open && state.status !== "ready",
2194
2326
  onClick: () => {
2327
+ if (!open && controller.store.getSnapshot().status !== "ready") return;
2195
2328
  setSavedTarget(void 0);
2196
2329
  setAddOpen(false);
2197
2330
  setEditing(open ? void 0 : target);
@@ -2201,8 +2334,9 @@ window.__ModuleLoader__.load({
2201
2334
  type: "button",
2202
2335
  className: ModelsSection_module_css_default["dangerButton"],
2203
2336
  "aria-label": providerCopy(t("removeProvider"), target),
2204
- disabled: !state.writable,
2337
+ disabled: !state.writable || state.status !== "ready",
2205
2338
  onClick: () => {
2339
+ if (controller.store.getSnapshot().status !== "ready") return;
2206
2340
  setSavedTarget(void 0);
2207
2341
  setDeleteFailure(void 0);
2208
2342
  setDeleteTarget(target);
@@ -2219,6 +2353,7 @@ window.__ModuleLoader__.load({
2219
2353
  }, { entryKey: row.entry.settingsNs }),
2220
2354
  open ? renderProviderEditor({
2221
2355
  target,
2356
+ row,
2222
2357
  namespace,
2223
2358
  schema,
2224
2359
  operations,
@@ -2309,6 +2444,10 @@ window.__ModuleLoader__.load({
2309
2444
  schema,
2310
2445
  settingsPath: draft.target.settingsPath,
2311
2446
  operations,
2447
+ ...draft.target.defaultBaseURL === void 0 ? {} : { defaultBaseURL: draft.target.defaultBaseURL },
2448
+ ...draft.target.baseURLEnv === void 0 ? {} : { baseURLEnv: draft.target.baseURLEnv },
2449
+ ...draft.target.nativeAuth === void 0 ? {} : { nativeAuth: draft.target.nativeAuth },
2450
+ ...addRow?.credentialChecks === void 0 ? {} : { credentialChecks: addRow.credentialChecks },
2312
2451
  t,
2313
2452
  readOnly: !state.writable,
2314
2453
  onClose: (changed) => {
@@ -2351,8 +2490,9 @@ window.__ModuleLoader__.load({
2351
2490
  children: (0, react_jsx_runtime.jsxs)("button", {
2352
2491
  type: "button",
2353
2492
  className: ModelsSection_module_css_default["addButton"],
2354
- disabled: !state.writable || !catalogEnabled && !customEnabled,
2493
+ disabled: !state.writable || state.status !== "ready" || !catalogEnabled && !customEnabled,
2355
2494
  onClick: () => {
2495
+ if (controller.store.getSnapshot().status !== "ready") return;
2356
2496
  const first = addable[0];
2357
2497
  const initial = catalogEnabled ? "catalog" : "custom";
2358
2498
  setSavedTarget(void 0);
@@ -2651,16 +2791,14 @@ window.__ModuleLoader__.load({
2651
2791
  //#endregion
2652
2792
  //#region lib/types/client/welcome-store.js
2653
2793
  /**
2654
- * Welcome-notice state derived from the welcome settings scope. The scope is
2655
- * the transport: a loopback browser follows the durable Host section, while a
2656
- * remote browser's memory-mode scope never answers and the acknowledgement
2657
- * stays process-local here.
2794
+ * Welcome-notice state derived from the welcome settings scope: the notice
2795
+ * follows the Host section and records its acknowledgement through it.
2658
2796
  */
2659
2797
  /* v8 ignore next 3 -- closed-union default only defends future source widening */
2660
2798
  function assertNever(_value) {
2661
2799
  throw new Error("unexpected welcome settings status");
2662
2800
  }
2663
- /** Coordinates durable Host acknowledgement or a process-local remote fallback. */
2801
+ /** Coordinates the durable Host acknowledgement. */
2664
2802
  var WelcomeNoticeStore = class {
2665
2803
  scope;
2666
2804
  /** uSES-safe state source shared by the registered welcome step. */
@@ -2669,13 +2807,8 @@ window.__ModuleLoader__.load({
2669
2807
  acknowledged: false,
2670
2808
  error: null
2671
2809
  });
2672
- localAcknowledged = false;
2673
2810
  saving = false;
2674
2811
  following;
2675
- /**
2676
- * @param scope - the welcome settings namespace scope; its memory mode is
2677
- * what keeps a remote browser process-local.
2678
- */
2679
2812
  constructor(scope) {
2680
2813
  this.scope = scope;
2681
2814
  }
@@ -2691,17 +2824,12 @@ window.__ModuleLoader__.load({
2691
2824
  return Promise.resolve();
2692
2825
  }
2693
2826
  /**
2694
- * Persist this copy version, or advance only this process for a remote
2695
- * browser. Success is judged against the state the write left behind, so a
2696
- * refused or failed write reports false after its recovery read settles.
2697
- * @returns true when the selected persistence mode holds the acknowledgement.
2827
+ * Persist this copy version through the scope. Success is judged against the
2828
+ * state the write left behind, so a refused or failed write reports false
2829
+ * after its recovery read settles.
2830
+ * @returns true when the Host holds the acknowledgement.
2698
2831
  */
2699
2832
  async acknowledge() {
2700
- if (this.scope.getSnapshot().mode === "memory") {
2701
- this.localAcknowledged = true;
2702
- this.derive();
2703
- return true;
2704
- }
2705
2833
  this.saving = true;
2706
2834
  this.store.update((state) => {
2707
2835
  state.status = "saving";
@@ -2728,14 +2856,6 @@ window.__ModuleLoader__.load({
2728
2856
  derive() {
2729
2857
  if (this.saving) return;
2730
2858
  const scope = this.scope.getSnapshot();
2731
- if (scope.mode === "memory") {
2732
- this.store.update((state) => {
2733
- state.status = "ready";
2734
- state.acknowledged = this.localAcknowledged;
2735
- state.error = null;
2736
- });
2737
- return;
2738
- }
2739
2859
  switch (scope.status) {
2740
2860
  case "loading":
2741
2861
  this.store.update((state) => {
@@ -2780,10 +2900,6 @@ window.__ModuleLoader__.load({
2780
2900
  */
2781
2901
  function createModelsOperations(ctx) {
2782
2902
  return {
2783
- describeCredential: async (ref) => {
2784
- const response = await ctx.remote.credentials.describe([ref]);
2785
- return response.ok ? response.value[ref] : void 0;
2786
- },
2787
2903
  storeCredential: async (ref, value) => {
2788
2904
  const response = await ctx.remote.credentials.set(ref, value);
2789
2905
  return response.ok ? void 0 : response.error.message;
@@ -2871,19 +2987,30 @@ window.__ModuleLoader__.load({
2871
2987
  savedProvider: "Saved {provider}.",
2872
2988
  credentialConfigured: "API key configured",
2873
2989
  credentialMissing: "API key missing",
2990
+ accountSignedIn: "Signed in",
2874
2991
  readOnly: "The settings document is read-only in this deployment.",
2875
2992
  loadFailed: "Loading the provider directory failed",
2876
2993
  conflict: "Someone else changed these settings while this card was open. Close it and reopen to edit the current values.",
2877
2994
  retry: "Retry",
2878
2995
  keyInput: "API key",
2996
+ keyReveal: "Show secret",
2997
+ keyHide: "Hide secret",
2879
2998
  keyPlaceholder: "Enter your API key",
2880
- keyPlaceholderNative: "Enter an API key, or leave blank to use environment authentication",
2881
- keyStored: "Configured — enter a new value to replace",
2882
- keyEnvLocked: "Provided by the launch environment (read-only)",
2999
+ keyPlaceholderFallback: "Leave blank to use env as auth ({hint})",
3000
+ keyPlaceholderSaved: "Leave blank to use saved credentials",
3001
+ keyFallbackMissing: "Env var not detected. Enter an API key, or configure {hint} in your env.",
3002
+ keyFallbackDetected: "Env var detected. ({hint})",
3003
+ keyFallbackDetectedHidden: "Env var detected.",
3004
+ keyFallbackUnchecked: "Environment credential status has not been confirmed.",
3005
+ keyHelp: "Saved credentials are used first. Environment fallbacks shown here, in order: {env}. Sources are launch environment, $cwd/.env, then $DSH_HOME/.env.",
3006
+ authHelp: "Authentication details",
2883
3007
  customized: "Customized settings",
2884
3008
  baseUrl: "Base URL",
2885
- baseUrlDefault: "Provider default",
2886
- deepSeekBaseUrl: "https://api.deepseek.com/anthropic",
3009
+ baseUrlDefault: "No default endpoint",
3010
+ baseUrlEnvironment: "Provider endpoint configuration: {env}",
3011
+ baseUrlConfigured: "Provider-configured endpoint",
3012
+ baseUrlPlaceholder: "{url}",
3013
+ deepSeekBaseUrl: "https://api.deepseek.com/anthropic (Default to use DEEPSEEK_BASE_URL)",
2887
3014
  deepSeekEndpointHint: "Use an API endpoint compatible with Anthropic Messages.",
2888
3015
  models: "Models",
2889
3016
  modelsInherited: "Using the adapter defaults",
@@ -2987,19 +3114,30 @@ window.__ModuleLoader__.load({
2987
3114
  savedProvider: "已保存 {provider}。",
2988
3115
  credentialConfigured: "API 密钥已配置",
2989
3116
  credentialMissing: "API 密钥缺失",
3117
+ accountSignedIn: "已登录",
2990
3118
  readOnly: "当前部署的设置文档为只读。",
2991
3119
  loadFailed: "加载提供商目录失败",
2992
3120
  conflict: "这张卡片打开期间,这些设置已被其他地方改动。请关闭后重新打开,在当前值上编辑。",
2993
3121
  retry: "重试",
2994
3122
  keyInput: "API 密钥",
3123
+ keyReveal: "显示密钥",
3124
+ keyHide: "隐藏密钥",
2995
3125
  keyPlaceholder: "输入 API 密钥",
2996
- keyPlaceholderNative: "输入 API 密钥,或留空使用环境认证",
2997
- keyStored: "已配置——输入新值可替换",
2998
- keyEnvLocked: "由启动环境提供(只读)",
3126
+ keyPlaceholderFallback: "留空使用环境认证({hint})",
3127
+ keyPlaceholderSaved: "留空使用已保存凭据",
3128
+ keyFallbackMissing: "未检测到环境变量。请填写 API 密钥,或设置环境变量 {hint}。",
3129
+ keyFallbackDetected: "已检测到环境变量({hint})",
3130
+ keyFallbackDetectedHidden: "已检测到环境变量",
3131
+ keyFallbackUnchecked: "环境凭据状态尚未确认",
3132
+ keyHelp: "优先使用已保存的凭据。此处显示的环境后备按以下顺序排列:{env}。来源依次为启动环境、$cwd/.env、$DSH_HOME/.env。",
3133
+ authHelp: "认证说明",
2999
3134
  customized: "自定义设置",
3000
3135
  baseUrl: "API 地址",
3001
- baseUrlDefault: "提供商默认",
3002
- deepSeekBaseUrl: "https://api.deepseek.com/anthropic",
3136
+ baseUrlDefault: "没有默认地址",
3137
+ baseUrlEnvironment: "提供方地址配置:{env}",
3138
+ baseUrlConfigured: "由提供方配置地址",
3139
+ baseUrlPlaceholder: "{url}",
3140
+ deepSeekBaseUrl: "https://api.deepseek.com/anthropic(默认使用环境 DEEPSEEK_BASE_URL)",
3003
3141
  deepSeekEndpointHint: "请填写兼容 Anthropic Messages 协议的 API 地址。",
3004
3142
  models: "模型目录",
3005
3143
  modelsInherited: "正在使用适配器默认模型",
@@ -0,0 +1,17 @@
1
+ import type { InputHTMLAttributes, ReactNode } from 'react';
2
+ import type { en } from './locales.ts';
3
+ /** Input attributes and section copy for the current credential draft. */
4
+ interface ApiKeyInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'value' | 'type' | 'className' | 'autoComplete'> {
5
+ /** Unsaved input; stored credentials are never read into this field. */
6
+ readonly value: string;
7
+ /** Section copy, including the same reveal labels as SecretText. */
8
+ readonly t: (key: keyof typeof en) => string;
9
+ }
10
+ /**
11
+ * Reveal only a nonempty draft; a different value starts masked, as in SecretText.
12
+ * @param props - current draft, input attributes, and translated control labels.
13
+ * @returns a password input with an optional reveal button.
14
+ */
15
+ export declare function ApiKeyInput({ value, t, ...props }: ApiKeyInputProps): ReactNode;
16
+ export {};
17
+ //# sourceMappingURL=ApiKeyInput.d.ts.map
@@ -21,7 +21,7 @@
21
21
  * see instead of rebuilding the whole subtree from a partial descriptor.
22
22
  */
23
23
  import type { ReactNode } from 'react';
24
- import type { SettingsNamespaceView, SettingsPathOpView } from '@deepseek-ai/dsh-api-remotes/client';
24
+ import type { CredentialInfo, LlmNativeAuth, SettingsNamespaceView, SettingsPathOpView } from '@deepseek-ai/dsh-api-remotes/client';
25
25
  import type { ModelsOperations } from './operations.ts';
26
26
  import type { SettingsSchemaOperations } from './schema-operations.ts';
27
27
  import type { en } from './locales.ts';
@@ -49,6 +49,16 @@ export interface ProviderEditorProps {
49
49
  settingsPath: readonly string[];
50
50
  /** The Host operations this card writes and interrogates through. */
51
51
  operations: ModelsOperations;
52
+ /** Endpoint the route uses when its profile sets no `baseURL`; absent when the adapter reports none. */
53
+ defaultBaseURL?: string;
54
+ /** Environment variables used by the provider to construct its endpoint. */
55
+ baseURLEnv?: readonly string[];
56
+ /** Authentication the route performs when its profile names no credential reference; absent when the adapter reports none. */
57
+ nativeAuth?: LlmNativeAuth;
58
+ /** Redacted state of the profile's named credential reference, from the page snapshot. */
59
+ credential?: CredentialInfo;
60
+ /** Redacted checks for the profile's named reference or the provider's native variables. */
61
+ credentialChecks?: Readonly<Record<string, CredentialInfo>>;
52
62
  /** Section copy. */
53
63
  t: (key: keyof typeof en) => string;
54
64
  /** Disable writes (read-only settings provider). */
@@ -7,7 +7,7 @@ import type { en } from './locales.ts';
7
7
  /** Registration-side dependencies of {@link WelcomeNotice}. */
8
8
  export interface WelcomeNoticeInjected {
9
9
  hooks: {
10
- /** Durable or process-local acknowledgement state. */
10
+ /** Durable acknowledgement state. */
11
11
  welcome: SnapshotStore<WelcomeNoticeState>;
12
12
  };
13
13
  /** Welcome acknowledgement controller. */
@@ -30,18 +30,29 @@ export declare const en: {
30
30
  savedProvider: string;
31
31
  credentialConfigured: string;
32
32
  credentialMissing: string;
33
+ accountSignedIn: string;
33
34
  readOnly: string;
34
35
  loadFailed: string;
35
36
  conflict: string;
36
37
  retry: string;
37
38
  keyInput: string;
39
+ keyReveal: string;
40
+ keyHide: string;
38
41
  keyPlaceholder: string;
39
- keyPlaceholderNative: string;
40
- keyStored: string;
41
- keyEnvLocked: string;
42
+ keyPlaceholderFallback: string;
43
+ keyPlaceholderSaved: string;
44
+ keyFallbackMissing: string;
45
+ keyFallbackDetected: string;
46
+ keyFallbackDetectedHidden: string;
47
+ keyFallbackUnchecked: string;
48
+ keyHelp: string;
49
+ authHelp: string;
42
50
  customized: string;
43
51
  baseUrl: string;
44
52
  baseUrlDefault: string;
53
+ baseUrlEnvironment: string;
54
+ baseUrlConfigured: string;
55
+ baseUrlPlaceholder: string;
45
56
  deepSeekBaseUrl: string;
46
57
  deepSeekEndpointHint: string;
47
58
  models: string;
@@ -5,7 +5,7 @@
5
5
  * failure codes and Remote namespaces stay in the apply world.
6
6
  */
7
7
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
8
- import type { CredentialInfo, LlmDiscoveredModel, LlmModelDiscoveryRequest, SettingsNamespaceView, SettingsPathOpView } from '@deepseek-ai/dsh-api-remotes/client';
8
+ import type { LlmDiscoveredModel, LlmModelDiscoveryRequest, SettingsNamespaceView, SettingsPathOpView } from '@deepseek-ai/dsh-api-remotes/client';
9
9
  /** What one namespace write answered. */
10
10
  export type SettingsWriteOutcome =
11
11
  /** Committed; the view carries the stored user subtree and the new revision. */
@@ -40,12 +40,6 @@ export type ModelDiscoveryOutcome =
40
40
  };
41
41
  /** The Host operations the Models page and its cards invoke. */
42
42
  export interface ModelsOperations {
43
- /**
44
- * Read one credential reference's state.
45
- * @param ref - credential reference name.
46
- * @returns the state, or undefined when the reference is unknown or the read was refused.
47
- */
48
- describeCredential(ref: string): Promise<CredentialInfo | undefined>;
49
43
  /**
50
44
  * Store one credential literal under its reference.
51
45
  * @param ref - credential reference name.
@@ -7,7 +7,7 @@
7
7
  * re-renders from the next describe, pushed or refetched.
8
8
  */
9
9
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
10
- import type { CredentialInfo, LlmConfigurableProvider, LlmProviderInfo, SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client';
10
+ import type { CredentialInfo, LlmConfigurableProvider, LlmNativeAuth, LlmProviderInfo, SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client';
11
11
  import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
12
12
  import type { SettingsDescribeFace } from '@deepseek-ai/dsh-client-ui-settings/client';
13
13
  import type { SettingsSchemaOperations } from './schema-operations.ts';
@@ -19,6 +19,9 @@ export interface ProviderDirectoryEntry {
19
19
  readonly settingsPath: readonly string[];
20
20
  readonly active: boolean;
21
21
  readonly declared?: boolean;
22
+ readonly defaultBaseURL?: string;
23
+ readonly baseURLEnv?: readonly string[];
24
+ readonly nativeAuth?: LlmNativeAuth;
22
25
  readonly error?: string;
23
26
  }
24
27
  /**
@@ -49,6 +52,8 @@ export interface ProviderRow {
49
52
  * own derivation rule.
50
53
  */
51
54
  derivedCredential?: CredentialInfo;
55
+ /** Redacted checks for named or native references; absent when their read failed. */
56
+ credentialChecks?: Readonly<Record<string, CredentialInfo>>;
52
57
  }
53
58
  /** Page snapshot. */
54
59
  export interface ModelsSettingsState {
@@ -91,6 +96,8 @@ export declare class ModelsSettingsStore {
91
96
  readonly store: SnapshotStore<ModelsSettingsState>;
92
97
  /** Latest load wins; an older response never overwrites a newer one. */
93
98
  private generation;
99
+ /** Callers follow the current refresh's outcome, including callers whose read was superseded. */
100
+ private readonly pendingLoads;
94
101
  /**
95
102
  * @param ctx - the page plugin's context, whose `remote.llm` and
96
103
  * `remote.credentials` namespaces carry the directory and credential reads.
@@ -103,11 +110,17 @@ export declare class ModelsSettingsStore {
103
110
  * settings answer in parallel, then one batched credential describe over
104
111
  * every referenced ref. Provider failure or absence of an initial settings
105
112
  * answer keeps the last good rows and surfaces an error; a failed settings
106
- * refresh reuses the mirror's held view.
107
- * @returns nothing; the snapshot carries the outcome.
113
+ * refresh reuses the mirror's held view. Concurrent callers resolve only
114
+ * after the current refresh publishes its ready or error snapshot.
115
+ * @returns resolution after the current snapshot is published.
116
+ * @throws the current read's unexpected rejection; superseded rejections are ignored.
108
117
  */
109
118
  load(): Promise<void>;
110
- /** Publish one load's failure text, unless a newer load already took over. */
119
+ /** Publish one joined answer; stale answers leave the current refresh in charge. */
120
+ private read;
121
+ /** Settle callers only while this read still owns the current refresh. */
122
+ private settleLoads;
123
+ /** Publish the current joined read's failure text. */
111
124
  private failLoad;
112
125
  }
113
126
  /**
@@ -1,8 +1,6 @@
1
1
  /**
2
- * Welcome-notice state derived from the welcome settings scope. The scope is
3
- * the transport: a loopback browser follows the durable Host section, while a
4
- * remote browser's memory-mode scope never answers and the acknowledgement
5
- * stays process-local here.
2
+ * Welcome-notice state derived from the welcome settings scope: the notice
3
+ * follows the Host section and records its acknowledgement through it.
6
4
  */
7
5
  import { type SnapshotStore } from '@deepseek-ai/dsh-client-store';
8
6
  import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
@@ -22,18 +20,13 @@ export type WelcomeSection = Record<string, unknown>;
22
20
  * @returns the section object, or an empty one for non-object values.
23
21
  */
24
22
  export declare function decodeWelcomeSection(section: unknown): WelcomeSection;
25
- /** Coordinates durable Host acknowledgement or a process-local remote fallback. */
23
+ /** Coordinates the durable Host acknowledgement. */
26
24
  export declare class WelcomeNoticeStore {
27
25
  private readonly scope;
28
26
  /** uSES-safe state source shared by the registered welcome step. */
29
27
  readonly store: SnapshotStore<WelcomeNoticeState>;
30
- private localAcknowledged;
31
28
  private saving;
32
29
  private following;
33
- /**
34
- * @param scope - the welcome settings namespace scope; its memory mode is
35
- * what keeps a remote browser process-local.
36
- */
37
30
  constructor(scope: ConfigForm<WelcomeSection>);
38
31
  /**
39
32
  * Begin following the bound scope (idempotent) and publish its current answer.
@@ -41,10 +34,10 @@ export declare class WelcomeNoticeStore {
41
34
  */
42
35
  load(): Promise<void>;
43
36
  /**
44
- * Persist this copy version, or advance only this process for a remote
45
- * browser. Success is judged against the state the write left behind, so a
46
- * refused or failed write reports false after its recovery read settles.
47
- * @returns true when the selected persistence mode holds the acknowledgement.
37
+ * Persist this copy version through the scope. Success is judged against the
38
+ * state the write left behind, so a refused or failed write reports false
39
+ * after its recovery read settles.
40
+ * @returns true when the Host holds the acknowledgement.
48
41
  */
49
42
  acknowledge(): Promise<boolean>;
50
43
  /** Stop following the scope. */
package/locale/en.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "meta": {
3
+ "title": "ui-settings-models"
4
+ }
5
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "模型设置界面",
4
+ "description": "管理模型服务及其接入配置,并提供相关引导。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-ui-settings-models",
3
3
  "description": "Models settings and shared product-onboarding dialogs over existing settings and credential joins",
4
- "version": "0.2.1-alpha.1",
4
+ "version": "0.2.1-alpha.3",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -23,7 +23,8 @@
23
23
  "default": "./lib/client.js"
24
24
  },
25
25
  "./src/*": "./src/*",
26
- "./package.json": "./package.json"
26
+ "./package.json": "./package.json",
27
+ "./locale/*.json": "./locale/*.json"
27
28
  },
28
29
  "dsh": {
29
30
  "client": {
@@ -37,29 +38,29 @@
37
38
  },
38
39
  "license": "MIT",
39
40
  "peerDependencies": {
40
- "@deepseek-ai/cordis": "~4.0.5-alpha.1"
41
+ "@deepseek-ai/cordis": "~4.0.5-alpha.2"
41
42
  },
42
43
  "devDependencies": {
43
- "@types/react": "~18.3.1",
44
+ "@deepseek-ai/dsh-host-webserver": "0.2.1-alpha.3",
45
+ "@deepseek-ai/dsh-api-remotes": "0.2.1-alpha.3",
46
+ "@deepseek-ai/dsh-client-locale": "0.2.1-alpha.3",
47
+ "@deepseek-ai/dsh-client-store": "0.2.1-alpha.3",
48
+ "@deepseek-ai/dsh-client-test-runtime": "0.2.1-alpha.3",
49
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-alpha.3",
50
+ "@deepseek-ai/dsh-client-ui-settings": "0.2.1-alpha.3",
51
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.1-alpha.3",
52
+ "@deepseek-ai/cordis": "~4.0.5-alpha.2",
44
53
  "react": "^18.2.0",
45
- "@deepseek-ai/dsh-api-remotes": "0.2.1-alpha.1",
46
- "@deepseek-ai/dsh-client-locale": "0.2.1-alpha.1",
47
- "@deepseek-ai/dsh-client-store": "0.2.1-alpha.1",
48
- "@deepseek-ai/dsh-host-webserver": "0.2.1-alpha.1",
49
- "@deepseek-ai/dsh-client-test-runtime": "0.2.1-alpha.1",
50
- "@deepseek-ai/dsh-client-ui-slots": "0.2.1-alpha.1",
51
- "@deepseek-ai/cordis": "~4.0.5-alpha.1",
52
- "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-alpha.1",
53
- "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-alpha.1",
54
- "@deepseek-ai/dsh-util-values": "0.2.1-alpha.1",
55
- "@deepseek-ai/dsh-remote-mock": "0.2.1-alpha.1",
56
- "@deepseek-ai/dsh-client-ui-settings": "0.2.1-alpha.1",
57
- "@deepseek-ai/dsh-client-product-analytics": "0.2.1-alpha.1"
54
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-alpha.3",
55
+ "@deepseek-ai/dsh-util-values": "0.2.1-alpha.3",
56
+ "@deepseek-ai/dsh-remote-mock": "0.2.1-alpha.3",
57
+ "@deepseek-ai/dsh-client-product-analytics": "0.2.1-alpha.3"
58
58
  },
59
59
  "dependencies": {
60
- "@deepseek-ai/schemastery": "~3.18.5-alpha.1"
60
+ "@deepseek-ai/schemastery": "~3.18.5-alpha.2"
61
61
  },
62
62
  "files": [
63
+ "locale/*.json",
63
64
  "lib/index.js",
64
65
  "lib/client.js",
65
66
  "lib/types/**/*.d.ts"