dsh-plugin-guide 0.3.3 → 0.3.4
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.es.md +5 -1
- package/README.hi.md +5 -1
- package/README.md +5 -1
- package/README.pt.md +5 -1
- package/README.zh.md +5 -1
- package/dist/dsh-plugin-dev.js +3 -3
- package/guide/plugin-dev-guide.md +2 -2
- package/guide/quick-reference.es.md +2 -2
- package/guide/quick-reference.hi.md +1 -1
- package/guide/quick-reference.md +2 -2
- package/guide/quick-reference.pt.md +2 -2
- package/guide/quick-reference.zh-CN.md +2 -2
- package/package.json +3 -3
- package/references/official-docs/AGENTS.md +2 -2
- package/references/official-docs/SNAPSHOT.md +3 -3
- package/references/official-docs/THIRD_PARTY_NOTICES.md +4 -6
- package/references/official-docs/docs/agent-lifecycle.i18n.yaml +2 -2
- package/references/official-docs/docs/agent-lifecycle.md +5 -3
- package/references/official-docs/docs/agent-lifecycle.zh.md +5 -3
- package/references/official-docs/docs/architecture.i18n.yaml +2 -2
- package/references/official-docs/docs/architecture.md +7 -3
- package/references/official-docs/docs/architecture.zh.md +7 -3
- package/references/official-docs/docs/capability-seams.i18n.yaml +2 -2
- package/references/official-docs/docs/capability-seams.md +5 -0
- package/references/official-docs/docs/capability-seams.zh.md +5 -0
- package/references/official-docs/docs/config-catalog.i18n.yaml +2 -2
- package/references/official-docs/docs/config-catalog.md +15 -21
- package/references/official-docs/docs/config-catalog.zh.md +15 -21
- package/references/official-docs/docs/cookbook/extension-cookbook.i18n.yaml +2 -2
- package/references/official-docs/docs/cookbook/extension-cookbook.md +12 -10
- package/references/official-docs/docs/cookbook/extension-cookbook.zh.md +12 -10
- package/references/official-docs/docs/deepseek-llm-api-wire-extensions.i18n.yaml +2 -2
- package/references/official-docs/docs/deepseek-llm-api-wire-extensions.md +16 -12
- package/references/official-docs/docs/deepseek-llm-api-wire-extensions.zh.md +16 -12
- package/references/official-docs/docs/event-producer-consumer.i18n.yaml +2 -2
- package/references/official-docs/docs/event-producer-consumer.md +28 -27
- package/references/official-docs/docs/event-producer-consumer.zh.md +32 -31
- package/references/official-docs/docs/module-graph.i18n.yaml +2 -2
- package/references/official-docs/docs/module-graph.md +36 -14
- package/references/official-docs/docs/module-graph.zh.md +36 -14
- package/references/official-docs/docs/persistence-catalog.i18n.yaml +2 -2
- package/references/official-docs/docs/persistence-catalog.md +49 -39
- package/references/official-docs/docs/persistence-catalog.zh.md +39 -29
- package/references/official-docs/docs/subsystems/attachment.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/attachment.md +157 -4
- package/references/official-docs/docs/subsystems/attachment.zh.md +157 -4
- package/references/official-docs/docs/subsystems/commands.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/commands.md +24 -15
- package/references/official-docs/docs/subsystems/commands.zh.md +24 -15
- package/references/official-docs/docs/subsystems/conversation.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/conversation.md +4 -4
- package/references/official-docs/docs/subsystems/conversation.zh.md +4 -4
- package/references/official-docs/docs/subsystems/core.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/core.md +62 -1
- package/references/official-docs/docs/subsystems/core.zh.md +62 -1
- package/references/official-docs/docs/subsystems/llm-streaming.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/llm-streaming.md +23 -2
- package/references/official-docs/docs/subsystems/llm-streaming.zh.md +23 -2
- package/references/official-docs/docs/subsystems/persistence.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/persistence.md +10 -10
- package/references/official-docs/docs/subsystems/persistence.zh.md +10 -10
- package/references/official-docs/docs/subsystems/session-projection.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/session-projection.md +19 -0
- package/references/official-docs/docs/subsystems/session-projection.zh.md +19 -0
- package/references/official-docs/docs/subsystems/session-reference.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/session-reference.md +25 -1
- package/references/official-docs/docs/subsystems/session-reference.zh.md +25 -1
- package/references/official-docs/docs/subsystems/session-telemetry.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/session-telemetry.md +6 -5
- package/references/official-docs/docs/subsystems/session-telemetry.zh.md +6 -5
- package/references/official-docs/docs/subsystems/session.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/session.md +52 -40
- package/references/official-docs/docs/subsystems/session.zh.md +52 -40
- package/references/official-docs/docs/subsystems/web-client.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/web-client.md +1 -1
- package/references/official-docs/docs/subsystems/web-client.zh.md +1 -1
- package/references/official-docs/docs/subsystems/workspace.i18n.yaml +2 -2
- package/references/official-docs/docs/subsystems/workspace.md +8 -8
- package/references/official-docs/docs/subsystems/workspace.zh.md +8 -8
- package/references/official-docs/docs/testing.i18n.yaml +2 -2
- package/references/official-docs/docs/testing.md +2 -2
- package/references/official-docs/docs/testing.zh.md +2 -2
- package/references/official-docs/docs/user/guide/network-proxy.i18n.yaml +6 -0
- package/references/official-docs/docs/user/guide/network-proxy.md +85 -0
- package/references/official-docs/docs/user/guide/network-proxy.zh.md +85 -0
- package/references/official-docs/docs/user/guide/providers.i18n.yaml +2 -2
- package/references/official-docs/docs/user/guide/providers.md +70 -17
- package/references/official-docs/docs/user/guide/providers.zh.md +70 -17
- package/references/official-docs/docs/web-styling.i18n.yaml +2 -2
- package/references/official-docs/docs/web-styling.md +1 -0
- package/references/official-docs/docs/web-styling.zh.md +1 -0
- package/references/official-docs/website-docs.ts +8 -0
- package/templates/js/README.es.md +1 -1
- package/templates/js/README.hi.md +1 -1
- package/templates/js/README.md +1 -1
- package/templates/js/README.pt.md +1 -1
- package/templates/js/README.zh.md +1 -1
- package/templates/ts/README.es.md +1 -1
- package/templates/ts/README.hi.md +1 -1
- package/templates/ts/README.md +1 -1
- package/templates/ts/README.pt.md +1 -1
- package/templates/ts/README.zh.md +1 -1
|
@@ -40,7 +40,7 @@ interface Workspace {
|
|
|
40
40
|
*/
|
|
41
41
|
readonly path: string
|
|
42
42
|
|
|
43
|
-
/** Display title. Defaults to
|
|
43
|
+
/** Display title. Defaults to the final path segment, or a filesystem root's own spelling; duplicates are allowed. */
|
|
44
44
|
readonly title: string
|
|
45
45
|
|
|
46
46
|
/** ISO-8601 creation instant, stamped at create and never rewritten. */
|
|
@@ -117,7 +117,7 @@ interface Workspace {
|
|
|
117
117
|
|
|
118
118
|
## 注册表:`ctx.workspaceRegistry`
|
|
119
119
|
|
|
120
|
-
`WorkspaceRegistry`([签名](#ctxworkspaceregistry--workspaceregistry))拥有注册与解析。`create(path, title?)`
|
|
120
|
+
`WorkspaceRegistry`([签名](#ctxworkspaceregistry--workspaceregistry))拥有注册与解析。`create(path, title?)` 要求完全限定路径并将其规范化,拒绝不存在的路径(原样传出原始 `ENOENT`)或非目录;当规范路径已被拥有时原样返回既有实体;否则创建一条标题为 `title ?? defaultWorkspaceTitle(path)` 的记录并前插到持久的注册表顺序中(不同规范路径可以共享同一显示标题,没有最终路径段时使用根路径拼写)。`get(id)` 与有序的 `list()` 是同步缓存读取;`resolveByPath(path)` 应用同一套完全限定 realpath 规范但不创建。`delete(id)` 只移除注册记录、顺序条目和会话账本——目录、用户文件、实时会话和已持久化日志一概不动,因此这些会话变为 Ungrouped([决策](../../.agents/notes/implemented/feature/2026-07-27-workspace-registration-deletion.zh.md));未知 id 返回 `false`。create 与 delete 会在其两次写入(记录 + 顺序)可能分叉之前先持久写入一个待定变更标记;启动时恰好解决被标记的那次变更——通过删除被标记的表行:这会补完被中断的 delete,并回滚被中断的 create(注册可以重建,因此回滚是安全方向)——而没有标记的顺序/表不一致则作为损坏大声失败。
|
|
121
121
|
|
|
122
122
|
会话的 cwd 在创建时由创建者赋予,而不是由本注册表赋予——API 网关从所选工作区的 `path` 解析新会话的 cwd(回退到显式或默认 cwd),先创建会话使 cwd 落入其不可变的 [`SessionHeader`](persistence.zh.md#sessionheader--metadata-beside-the-log),再调用 `attachSession`,后者会把已存储的 header cwd 与工作区路径重新校验一遍。首次成功启动时,注册表仅凭已持久化的 header(`id`、`cwd`、`createdAt`——绝不读事件正文)引导历史:把规范 cwd 有效的会话按目录分组为工作区,最新的排在最前;「已初始化」标记最后写入,因此被中断的引导可以安全续跑。引导只发生这一次:没有 cwd 的历史遗留会话保持 Ungrouped,此后创建的会话只能通过 `attachSession` 加入工作区。
|
|
123
123
|
|
|
@@ -250,13 +250,13 @@ Durable workspace registry. Startup waits for `sessionPersistence`, builds one c
|
|
|
250
250
|
|
|
251
251
|
```ts cordis-catalog
|
|
252
252
|
/**
|
|
253
|
-
* Create or reuse a workspace for an existing directory. The
|
|
254
|
-
* canonicalized through `fs.realpath`; a nonexistent
|
|
255
|
-
*
|
|
256
|
-
*
|
|
253
|
+
* Create or reuse a workspace for an existing directory. The fully qualified
|
|
254
|
+
* path is canonicalized through `fs.realpath`; a relative, nonexistent, or
|
|
255
|
+
* non-directory path rejects. Repeated calls for the same canonical path
|
|
256
|
+
* return the existing entity without changing its title.
|
|
257
257
|
* A newly created workspace is prepended to the durable registry order.
|
|
258
258
|
* Different canonical paths may share a display title.
|
|
259
|
-
* @param path - Existing directory to own, in
|
|
259
|
+
* @param path - Existing directory to own, in a fully qualified path spelling.
|
|
260
260
|
* @param title - Display title used only when a new record is created.
|
|
261
261
|
* @returns the existing or newly durable workspace.
|
|
262
262
|
*/
|
|
@@ -309,7 +309,7 @@ archiveSession(sessionId: SessionId): Promise<void>
|
|
|
309
309
|
* Resolve by canonical directory path without creating or mutating a
|
|
310
310
|
* workspace. A missing path rejects during `realpath`; an existing unowned
|
|
311
311
|
* directory returns `undefined`.
|
|
312
|
-
* @param path - Existing directory path in
|
|
312
|
+
* @param path - Existing directory path in a fully qualified spelling.
|
|
313
313
|
* @returns the workspace owning the canonical path, when one exists.
|
|
314
314
|
*/
|
|
315
315
|
async resolveByPath(path: string): Promise<Workspace | undefined>
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write docs/testing.md
|
|
5
|
-
testing.md:
|
|
6
|
-
testing.zh.md:
|
|
5
|
+
testing.md: fbf075ecde1dc38b816bcfbbee551f2e1a8178e0
|
|
6
|
+
testing.zh.md: 6b5e553b3089d30f104947b8570e6388d807758f
|
|
@@ -10,10 +10,10 @@ How this repo tests, tier by tier, and the rules that keep a green suite meaning
|
|
|
10
10
|
- **Coverage gate** (`pnpm run test:coverage`): the gating run, per-file 100% on `packages/*/*/src`. An uncovered line is often dead code the gate flags for deletion, not a missing test to bolt on. Line coverage is necessary, never sufficient — it proves lines ran, not that the feature works as shipped. Per-file 100% on `packages/shell/pwsh-local/src` needs a real `pwsh`: without one its executor suites self-skip and `vitest.config.ts` exempts the file so pwsh-less hosts stay green, while CI runners ship pwsh and enforce the full bar.
|
|
11
11
|
- **Real-API e2e** (`pnpm run test:e2e`): with-key tests against live provider APIs — the DeepSeek model plus provider-specific smokes that gate on their own keys (`EXA_API_KEY`, `PERPLEXITY_API_KEY`, …); each suite self-skips without its key so keyless CI stays green ([real-API e2e Agent Note](../.agents/notes/implemented/testing/2026-06-19-real-api-e2e-ci.md)).
|
|
12
12
|
- **Owner-local expected output** (`pnpm run test:expected`): keyless assembled CLI/process expectations without a recorded-session round trip. Drivers use `*.expected.e2e.ts` beside `tests/expected/`; CI runs built exports. Package/script expectations use `test`, while browser expectations use `test:web`.
|
|
13
|
-
- **Snapshot** (`pnpm run test:snapshot`): a top-level scenario's recorded
|
|
13
|
+
- **Snapshot** (`pnpm run test:snapshot`): a top-level scenario's highest recorded parent generation supplies user input and model replay, then serves as the expected persisted result. Parent filenames are `session[.vN].jsonl`; child roles are `session.<ordinal>[.vN].jsonl`; v0 omits `.v0`, positive versions require lowercase `.vN`, and each filename must agree with its header. Process scenarios start through `dsh`: headless owns one-shot behavior, the SDK owns persistent control, ACP owns automation-protocol behavior, and Web retains browser/ARIA evidence beside the same Session. `snapshot.yml` declares the profile, composition/header class, recording policy, exceptional replay or input metadata, and workspace facts. Typed tokens preserve parent/child identity relationships; only header pins own prompt/schema sidecars. A mutating scenario independently compares the complete `workspace.expected/` tree, which record and refresh never rewrite. Use `test:snapshot:record` when a model transcript changes and `test:snapshot:refresh` when replay input remains valid; review every resulting diff.
|
|
14
14
|
- **Web browser snapshot** (`pnpm run test:web`; required Linux PR gate): Chromium compares session-driven output under `snapshots/web/` and UI-only output under `apps/web/tests/expected/`. CI forces read-only `DSH_SNAPSHOT=replay`, never writing expected outputs; record/refresh stay local and every diff is reviewed ([web e2e lane](../.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.md), [CI gate decision](../.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.md)). `test:web` [builds first](../.agents/notes/implemented/bug-fix/2026-07-28-themed-scrollbars-and-reserved-gutter.md) for plugin CSS.
|
|
15
15
|
|
|
16
|
-
Session fixtures
|
|
16
|
+
Session fixtures retain headers and payloads but omit body sequence/time envelopes; replay synthesizes them. Replay, record, and refresh select each parent/child role's highest generation. Current v2 uses `.v2`, one row per event, and embedded compact Assistant streams; retained v0 (suffixless) and v1 (`.v1`) may keep canonical packed rows for migration coverage. [The migrator](../scripts/migrate-packed-session-fixtures.ts) rewrites older historical layouts.
|
|
17
17
|
|
|
18
18
|
## How specs execute
|
|
19
19
|
|
|
@@ -10,10 +10,10 @@
|
|
|
10
10
|
- **覆盖率门禁**(`pnpm run test:coverage`):门禁级运行,对 `packages/*/*/src` 按文件 100% 覆盖。未覆盖的行往往是门禁正确标记出的死代码(应删除),而非需要补写的测试。行覆盖率是必要条件,但永远不是充分条件:它证明行被执行过,不证明功能按交付预期工作。`packages/shell/pwsh-local/src` 的按文件 100% 覆盖需要真实的 `pwsh`:缺少它时其执行器套件会自动跳过,`vitest.config.ts` 会豁免该文件以使无 pwsh 的主机保持绿色,而 CI runner 自带 pwsh,仍按完整标准执行门禁。
|
|
11
11
|
- **真实 API e2e**(`pnpm run test:e2e`):带密钥测试调用真实提供方 API,包括 DeepSeek 模型以及各提供方特有的冒烟测试;这些测试各自由自己的密钥控制(`EXA_API_KEY`、`PERPLEXITY_API_KEY` 等),缺少密钥时套件会自动跳过,使 keyless CI 保持绿色([真实 API e2e Agent Note](../.agents/notes/implemented/testing/2026-06-19-real-api-e2e-ci.zh.md))。
|
|
12
12
|
- **所属位置的预期输出**(`pnpm run test:expected`):无录制会话往返的无密钥组装 CLI/进程预期。驱动使用 `*.expected.e2e.ts`,并与 `tests/expected/` 同属一处;CI 针对构建产物运行。包/脚本预期使用 `test`,浏览器预期使用 `test:web`。
|
|
13
|
-
- **快照**(`pnpm run test:snapshot
|
|
13
|
+
- **快照**(`pnpm run test:snapshot`):顶层场景数值最高的已录制 parent generation 同时提供用户输入和模型回放,并作为持久化结果的预期值。parent 文件名是 `session[.vN].jsonl`;child 角色使用 `session.<ordinal>[.vN].jsonl`;v0 省略 `.v0`,正版本必须使用小写 `.vN`,且每个文件名必须与其 header 一致。进程级场景都通过 `dsh` 启动:headless 负责一次性行为,SDK 负责持久控制,ACP 负责自动化协议行为,Web 在同一 Session 旁保留浏览器与 ARIA 证据。`snapshot.yml` 声明 profile、组合与请求头类别、录制策略、例外回放或输入元数据以及 workspace 事实。带类型的 token 保留父子身份关系;只有请求头 pin 拥有 prompt/schema sidecar。变更 workspace 的场景会独立比较完整的 `workspace.expected/` 目录,record 与 refresh 绝不改写该目录。当模型 transcript(文本记录)变化时使用 `test:snapshot:record`,回放输入仍有效时使用 `test:snapshot:refresh`;请审查所有结果差异。
|
|
14
14
|
- **Web 浏览器快照**(`pnpm run test:web`;必需的 Linux PR(Pull Request)门禁):Chromium 比较 `snapshots/web/` 下由会话驱动的输出,以及 `apps/web/tests/expected/` 下仅含 UI 的输出。CI 强制只读的 `DSH_SNAPSHOT=replay`,绝不写入预期输出;record/refresh 留在本地,每处 diff 都须评审([web e2e 车道](../.agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.zh.md)、[CI 门禁决策](../.agents/notes/implemented/testing/2026-07-30-web-browser-snapshot-ci-gate.zh.md))。`test:web` 会[先构建](../.agents/notes/implemented/bug-fix/2026-07-28-themed-scrollbars-and-reserved-gutter.zh.md)以交付插件 CSS。
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Session fixture 保留 header 与 payload,但省略正文 seq/time envelope;replay 会合成这些 envelope。Replay、record 与 refresh 会选择每个 parent/child 角色的最高 generation。当前 v2 使用 `.v2`、每个事件一行,并嵌入紧凑 Assistant stream;保留的 v0(无后缀)与 v1(`.v1`)可以为迁移覆盖保留规范 packed row。[迁移器](../scripts/migrate-packed-session-fixtures.ts)会改写更旧的历史布局。
|
|
17
17
|
|
|
18
18
|
## spec 如何被执行
|
|
19
19
|
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write docs/user/guide/network-proxy.md
|
|
5
|
+
network-proxy.md: d53d48688490c741f0ed7f950c5ff39db02dc1e9
|
|
6
|
+
network-proxy.zh.md: 1eee1e67abb700e15f3b2cea1b22bf036c694302
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Run DSH behind a network proxy
|
|
2
|
+
|
|
3
|
+
English | [中文](network-proxy.zh.md)
|
|
4
|
+
|
|
5
|
+
DSH routes its outbound requests — model calls, web search, page fetches, and MCP servers over HTTP — through the proxy named by the standard proxy environment variables. It reads them at launch; nothing else needs configuring. A few paths stay direct by design or by runtime limit, listed under "What stays direct" below.
|
|
6
|
+
|
|
7
|
+
## Export the variables
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
export HTTPS_PROXY=http://127.0.0.1:7890
|
|
11
|
+
export HTTP_PROXY=http://127.0.0.1:7890
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Put both lines in your shell profile so every `dsh` invocation inherits them, or in `$DSH_HOME/.env` (`~/.dsh/.env` by default) next to your API key; an exported variable always wins over that file. A project's own `.env` cannot set them: it arrives with `git clone`, and DSH refuses to start rather than let a repository decide where your traffic goes.
|
|
15
|
+
|
|
16
|
+
A proxy that needs credentials takes them in the URL: `http://user:password@proxy.example:8080`. DSH never prints the URL back: a diagnostic names the variable it rejected, so neither the username nor the password appears anywhere.
|
|
17
|
+
|
|
18
|
+
## Why your browser is proxied but your terminal is not
|
|
19
|
+
|
|
20
|
+
This is the most common surprise, and it is not specific to DSH. There is no single "system proxy" that all software obeys — there are three unrelated mechanisms:
|
|
21
|
+
|
|
22
|
+
| Mechanism | Who follows it |
|
|
23
|
+
|---|---|
|
|
24
|
+
| The operating system's proxy settings | Safari, most native macOS apps, Chrome and Edge |
|
|
25
|
+
| The `HTTP_PROXY` / `HTTPS_PROXY` environment variables | `curl`, `git`, `npm`, `pip`, and DSH |
|
|
26
|
+
| TUN mode (a virtual network interface) | Everything, transparently |
|
|
27
|
+
|
|
28
|
+
The "system proxy" switch in a proxy application such as Clash writes only the first one. Browsers pick it up; command-line tools never see it. That is why exporting the variables is a separate step, and why turning on TUN mode makes both work without any variables at all.
|
|
29
|
+
|
|
30
|
+
DSH does not read the operating system's proxy settings. Export the variables, or use TUN mode.
|
|
31
|
+
|
|
32
|
+
## Choose what stays direct
|
|
33
|
+
|
|
34
|
+
`NO_PROXY` lists hosts to reach directly:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
export NO_PROXY=internal.example.com,.corp.example.com,registry.local
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
An entry names a host and matches it together with every subdomain under it: `NO_PROXY=example.com` also sends `api.example.com` direct. A leading `.` or `*.` is accepted and means the same thing. An entry may carry a `:port`, and `*` bypasses everything.
|
|
41
|
+
|
|
42
|
+
**CIDR ranges do not work.** An operating system bypass list often contains entries like `10.0.0.0/8` or `192.168.0.0/16`; copying those into `NO_PROXY` has no effect. Use host names or domain suffixes instead.
|
|
43
|
+
|
|
44
|
+
You do not need to list `localhost` or `127.0.0.1`. DSH always bypasses loopback, because its own Web UI and local servers would otherwise route through the proxy and loop.
|
|
45
|
+
|
|
46
|
+
## Limits worth knowing
|
|
47
|
+
|
|
48
|
+
**SOCKS proxies are not supported.** A `socks5://` value is reported at startup and skipped, and DSH connects directly for the scheme that named it — setting `HTTPS_PROXY=socks5://…` alongside a usable `HTTP_PROXY` leaves `https:` direct rather than borrowing the HTTP proxy. Point the variables at your proxy application's HTTP port instead — most expose both, and the HTTP one is usually a neighbouring port number.
|
|
49
|
+
|
|
50
|
+
**`ALL_PROXY` alone is enough.** DSH falls back to it for both schemes, even though Node and curl differ on this. Setting `HTTPS_PROXY` explicitly is still clearer.
|
|
51
|
+
|
|
52
|
+
**A TLS-intercepting corporate proxy needs its certificate.** If requests fail with a certificate error once the proxy is reachable, point Node at your organisation's CA bundle before launching:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
export NODE_EXTRA_CA_CERTS=/path/to/corporate-ca.pem
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Node reads that variable only at process start, so export it before running `dsh`.
|
|
59
|
+
|
|
60
|
+
**Tools DSH runs for you follow the same proxy.** Commands in the bash tool, `git`, `gh`, and MCP servers started as child processes all inherit these variables. A child that is itself a Node program honors them only on Node 22.21 or later; an older Node connects directly. If one of your proxy variables holds a value DSH rejected — a SOCKS URL, say — Node-based tools also connect directly rather than fail to start, while `curl` and `git` still read that value.
|
|
61
|
+
|
|
62
|
+
**A password in the proxy URL reaches those tools too.** `HTTPS_PROXY=http://alice:s3cret@proxy.example:8080` is a normal environment variable, so every command DSH runs — including the ones the model writes — can read it, and a command that prints its environment puts the password in output that is kept. This is how the variable already behaves for everything else in your shell. If that matters, give the proxy a credential-free entry point, or authenticate it some other way than in the URL.
|
|
63
|
+
|
|
64
|
+
## What stays direct
|
|
65
|
+
|
|
66
|
+
Not every request DSH makes goes through the proxy:
|
|
67
|
+
|
|
68
|
+
- **Anything on this machine.** Loopback is always direct: `localhost`, the whole `127.0.0.0/8` range, `::1`, and `0.0.0.0`. A proxy cannot usefully reach a service that only listens locally.
|
|
69
|
+
- **Code the model writes.** The workflow and code-runtime workers never receive the proxy settings, so a script the model authors cannot read a proxy URL that may carry a password. Such a script reaches the network only if it configures that itself.
|
|
70
|
+
- **Usage telemetry.** The OTLP exporter uses Node's own HTTP client rather than the one a proxy configures, so telemetry connects directly and simply fails where direct egress is blocked. Nothing you do in DSH depends on it. Set `DSH_TELEMETRY_MODE=DISABLED` to turn it off entirely.
|
|
71
|
+
- **`web_fetch` to a literal private address.** A URL naming an address like `http://10.0.0.5/` is refused rather than handed to the proxy, the same refusal it gets with no proxy configured.
|
|
72
|
+
|
|
73
|
+
## Check that it worked
|
|
74
|
+
|
|
75
|
+
Ask the agent to fetch a page and watch your proxy application's connection log:
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
dsh --profile headless "fetch https://example.com and tell me the page title"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
If the request does not appear there, confirm the variables survive into DSH's own environment:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
env | grep -i proxy
|
|
85
|
+
```
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# 在网络代理后面运行 DSH
|
|
2
|
+
|
|
3
|
+
[English](network-proxy.md) | 中文
|
|
4
|
+
|
|
5
|
+
DSH 会把自身的出站请求——模型调用、web 搜索、页面抓取、走 HTTP 的 MCP 服务器——都经由标准代理环境变量所指定的代理发出。它在启动时读取这些变量,不需要其他配置。有几条路径出于设计或运行时限制保持直连,下文"哪些保持直连"一节列出了它们。
|
|
6
|
+
|
|
7
|
+
## 导出环境变量
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
export HTTPS_PROXY=http://127.0.0.1:7890
|
|
11
|
+
export HTTP_PROXY=http://127.0.0.1:7890
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
把这两行写进 shell 配置,这样每次调用 `dsh` 都会继承它们;也可以写进 `$DSH_HOME/.env`(默认 `~/.dsh/.env`),和 API key 放在一起;导出的环境变量始终优先于该文件。项目自己的 `.env` 不能设置它们:它随 `git clone` 一起到来,DSH 宁可拒绝启动,也不让一个仓库决定你的流量去向。
|
|
15
|
+
|
|
16
|
+
需要凭据的代理把凭据写在 URL 里:`http://user:password@proxy.example:8080`。DSH 绝不会回显这个 URL:诊断只点名被拒绝的变量,因此用户名和密码都不会出现在任何地方。
|
|
17
|
+
|
|
18
|
+
## 为什么浏览器走代理、终端却不走
|
|
19
|
+
|
|
20
|
+
这是最常见的意外,而且并非 DSH 特有。**根本不存在一个所有软件都遵循的"系统代理"**——实际上有三套互不相干的机制:
|
|
21
|
+
|
|
22
|
+
| 机制 | 谁会遵循 |
|
|
23
|
+
|---|---|
|
|
24
|
+
| 操作系统的代理设置 | Safari、绝大多数 macOS 原生应用、Chrome 与 Edge |
|
|
25
|
+
| `HTTP_PROXY` / `HTTPS_PROXY` 环境变量 | `curl`、`git`、`npm`、`pip` 以及 DSH |
|
|
26
|
+
| TUN 模式(虚拟网卡) | 所有程序,且对应用透明 |
|
|
27
|
+
|
|
28
|
+
Clash 这类代理软件里的"系统代理"开关只写第一套。浏览器会读到它,命令行工具则永远看不到。这就是为什么导出环境变量是一个独立步骤,也是为什么打开 TUN 模式后两者都能工作、且完全不需要变量。
|
|
29
|
+
|
|
30
|
+
DSH 不读取操作系统的代理设置。请导出环境变量,或使用 TUN 模式。
|
|
31
|
+
|
|
32
|
+
## 指定哪些目标保持直连
|
|
33
|
+
|
|
34
|
+
`NO_PROXY` 列出需要直连的主机:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
export NO_PROXY=internal.example.com,.corp.example.com,registry.local
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
一个条目写的是主机名,它连同其下所有子域名一起匹配:`NO_PROXY=example.com` 也会让 `api.example.com` 直连。前缀 `.` 或 `*.` 可以写,含义相同。条目可带 `:port`,`*` 则放行全部。
|
|
41
|
+
|
|
42
|
+
**CIDR 网段不生效。** 操作系统的绕过列表常含 `10.0.0.0/8` 或 `192.168.0.0/16` 这类条目;把它们复制进 `NO_PROXY` 不会有任何效果。请改用主机名或域名后缀。
|
|
43
|
+
|
|
44
|
+
不需要列出 `localhost` 或 `127.0.0.1`。DSH 始终绕过 loopback,否则它自己的 Web UI 与本地服务器都会经由代理并形成回环。
|
|
45
|
+
|
|
46
|
+
## 值得知道的限制
|
|
47
|
+
|
|
48
|
+
**不支持 SOCKS 代理。** `socks5://` 形式的值会在启动时被报告并跳过,指定它的那个 scheme 转为直连——把 `HTTPS_PROXY=socks5://…` 与一个可用的 `HTTP_PROXY` 一起设置时,`https:` 会保持直连,而不会去借用 HTTP 代理。请把变量指向代理软件的 HTTP 端口——多数软件两者都提供,且 HTTP 端口通常就在相邻的端口号上。
|
|
49
|
+
|
|
50
|
+
**只设 `ALL_PROXY` 也够用。** DSH 会用它为两种协议兜底,尽管 Node 与 curl 在这一点上并不一致。显式设置 `HTTPS_PROXY` 仍然更清楚。
|
|
51
|
+
|
|
52
|
+
**做 TLS 拦截的企业代理需要它的证书。** 如果代理已经可达但请求仍报证书错误,请在启动前把 Node 指向你所在组织的 CA 包:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
export NODE_EXTRA_CA_CERTS=/path/to/corporate-ca.pem
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Node 只在进程启动时读取该变量,所以要在运行 `dsh` 之前导出。
|
|
59
|
+
|
|
60
|
+
**DSH 替你运行的工具遵循同一个代理。** bash 工具里的命令、`git`、`gh`,以及作为子进程启动的 MCP 服务器都会继承这些变量。子进程若本身是 Node 程序,则需 Node 22.21 或更高版本才会遵循;更旧的 Node 会直连。如果你的某个代理变量是 DSH 拒绝的值——比如 SOCKS URL——基于 Node 的工具同样直连而不是起不来,`curl` 与 `git` 则仍会读取那个值。
|
|
61
|
+
|
|
62
|
+
**代理 URL 里的密码同样会到达这些工具。** `HTTPS_PROXY=http://alice:s3cret@proxy.example:8080` 就是一个普通环境变量,因此 DSH 运行的每一条命令——包括模型编写的那些——都能读到它,而打印环境的命令会把密码写进被保留的输出。这与该变量在你 shell 里对其他一切程序的行为一致。若这一点重要,请为代理提供一个无需凭据的入口,或改用 URL 之外的方式认证。
|
|
63
|
+
|
|
64
|
+
## 哪些保持直连
|
|
65
|
+
|
|
66
|
+
并非 DSH 发出的每个请求都会走代理:
|
|
67
|
+
|
|
68
|
+
- **本机上的一切。** loopback 始终直连:`localhost`、整个 `127.0.0.0/8` 段、`::1` 与 `0.0.0.0`。代理无法有意义地访问一个只在本地监听的服务。
|
|
69
|
+
- **模型编写的代码。** workflow 与 code-runtime worker 从不接收代理配置,因此模型编写的脚本读不到可能携带密码的代理 URL。这类脚本只有自行配置才能联网。
|
|
70
|
+
- **使用情况遥测。** OTLP 导出器用的是 Node 自带的 HTTP 客户端,而不是代理所配置的那个,因此遥测直连;在禁止直连出网的环境里它只会失败。DSH 的任何功能都不依赖它。设 `DSH_TELEMETRY_MODE=DISABLED` 可完全关闭。
|
|
71
|
+
- **`web_fetch` 访问字面量私网地址。** 形如 `http://10.0.0.5/` 的 URL 会被拒绝而非交给代理,与未配置代理时得到的拒绝相同。
|
|
72
|
+
|
|
73
|
+
## 验证是否生效
|
|
74
|
+
|
|
75
|
+
让 agent 抓取一个页面,同时观察代理软件的连接日志:
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
dsh --profile headless "fetch https://example.com and tell me the page title"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
如果请求没有出现在那里,确认变量确实进入了 DSH 自己的环境:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
env | grep -i proxy
|
|
85
|
+
```
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write docs/user/guide/providers.md
|
|
5
|
-
providers.md:
|
|
6
|
-
providers.zh.md:
|
|
5
|
+
providers.md: 0db3280760c487c8469399bc9668b0685213e375
|
|
6
|
+
providers.zh.md: 3af9096f5a77ee40afe2ee788aa22a0758dd0e86
|
|
@@ -12,21 +12,39 @@ Open **Settings → Models**. The DeepSeek card exposes one API-key field; enter
|
|
|
12
12
|
|
|
13
13
|
Keys are write-only. The page receives a redacted descriptor after saving, never the literal secret. The key is stored in `$DSH_HOME/.credentials.yaml`, while settings retain only its credential reference.
|
|
14
14
|
|
|
15
|
-
## Add a
|
|
15
|
+
## Add a built-in provider
|
|
16
16
|
|
|
17
|
-
Choose **Add provider
|
|
17
|
+
Choose **Add provider** and pick a provider dsh ships with; the list shows provider ids such as `anthropic`, `openai`, `moonshotai` for Kimi, or `zai` for GLM. Enter its API key and save. The installed catalog supplies the endpoint, protocol, and model list.
|
|
18
18
|
|
|
19
|
-
Providers
|
|
19
|
+
Providers that sign in with OAuth, such as Codex, are not supported here yet.
|
|
20
20
|
|
|
21
21
|
## Add a custom provider
|
|
22
22
|
|
|
23
|
-
Choose **Add a custom provider** for a company gateway, self-hosted server, or provider absent from the installed catalog. Supply a lowercase Provider ID, base URL, API protocol, credential, and at least one model.
|
|
23
|
+
Choose **Add a custom provider** for a company gateway, self-hosted server, or provider absent from the installed catalog. Supply a lowercase Provider ID, base URL, API protocol, credential, and at least one model. The **API protocol** must be the one your gateway speaks, and the form offers three: `openai-completions` for OpenAI Chat Completions, `openai-responses` for the OpenAI Responses API, and `anthropic-messages` for the Anthropic Messages API. A provider speaks one protocol, so a gateway that serves two needs two providers.
|
|
24
24
|
|
|
25
25
|

|
|
26
26
|
|
|
27
27
|
The Provider ID is permanent because requests, saved sessions, model defaults, and credential references use it. To rename a provider, add a new provider and delete the old one. The display name, base URL, protocol, credential, and models remain editable.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
### Discover models
|
|
30
|
+
|
|
31
|
+
Under **Model catalog**, choose **Fetch available models** to ask the endpoint which models it serves. The request uses the base URL, protocol, and key currently in the form, or a saved provider's stored key, and the reply opens a searchable picker: search, tick the models you want, and choose **Add selected**. Nothing is stored until you save or create the provider.
|
|
32
|
+
|
|
33
|
+
Discovery reads the listing formats common gateways publish, but not every endpoint answers in one of them, so treat it as a convenience rather than a guarantee: when it fails or lists nothing, add the model ids by hand and they work just the same. A built-in provider is always answered from the installed catalog, even when its base URL points at a gateway, so fetch through a custom provider to see what the gateway really serves.
|
|
34
|
+
|
|
35
|
+
## Select a model
|
|
36
|
+
|
|
37
|
+
Configured providers appear in the model picker. Selecting a model also makes it the default for new sessions. A session that has already sent a request retains the model recorded in its own log.
|
|
38
|
+
|
|
39
|
+
If a saved default names a provider that was deleted, the composer displays **Select model** and blocks input until another model is selected.
|
|
40
|
+
|
|
41
|
+
## Advanced configuration
|
|
42
|
+
|
|
43
|
+
The generated [plugin configuration catalog](../../config-catalog.md) lists every supported field and default for every plugin; [`dsh-llm-pi-ai`](../../config-catalog.md#deepseek-aidsh-llm-pi-ai) is the provider section this page configures. The [`dsh-llm-pi-ai`](../../../packages/llm/llm-pi-ai/README.md) and [`dsh-llm-deepseek`](../../../packages/llm/llm-deepseek/README.md) references own direct `settings.yaml` configuration, catalog resolution, reasoning controls, credentials, and adapter errors.
|
|
44
|
+
|
|
45
|
+
::: tip The form is deliberately small
|
|
46
|
+
The Models page exposes only what a route needs to exist: the API key, display name, base URL, API protocol, and for each model its id, display name, context window, and max output tokens. Every other field — reasoning effort levels, image input, request-compatibility switches, headers, timeouts, retry policy — is set in `$DSH_HOME/settings.yaml`, the same document the page writes. Edit it directly, or, when the browser runs on the same machine as the server, open it with **Open configuration file** in the Settings header; the adapters re-read it on the next request, so nothing needs a restart. The subsections below cover the fields most gateways need.
|
|
47
|
+
:::
|
|
30
48
|
|
|
31
49
|
### Image input
|
|
32
50
|
|
|
@@ -64,7 +82,7 @@ llm-pi-ai:
|
|
|
64
82
|
- id: second-model
|
|
65
83
|
```
|
|
66
84
|
|
|
67
|
-
`defaultInput` is a fallback, not an override, and defaults to `[text]`: on a
|
|
85
|
+
`defaultInput` is a fallback, not an override, and defaults to `[text]`: on a built-in provider it answers only for models its catalog does not describe, so it never removes images from a catalog model that has them. Narrow one of those with that model's own `input`. A built-in provider has no `models` list to put it in, so write it under `modelOverrides`, keyed by model id:
|
|
68
86
|
|
|
69
87
|
```yaml
|
|
70
88
|
llm-pi-ai:
|
|
@@ -79,6 +97,48 @@ Every list must name at least one modality except a model's own, where an empty
|
|
|
79
97
|
|
|
80
98
|
Both fields state a claim about your endpoint rather than checking it. A model that declares images its endpoint does not serve is not caught here; the provider rejects the request instead.
|
|
81
99
|
|
|
100
|
+
### Reasoning effort
|
|
101
|
+
|
|
102
|
+
The model picker offers an **Effort** menu for a model that declares reasoning levels. A built-in provider's models inherit their levels from the installed catalog. A model you enter by hand declares none, so the Effort entry does not appear in the menu and the endpoint's own default decides whether the model thinks. Declare the levels with `reasoningEfforts` in `$DSH_HOME/settings.yaml`:
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
llm-pi-ai:
|
|
106
|
+
providers:
|
|
107
|
+
my-gateway:
|
|
108
|
+
apiKeyEnv: GATEWAY_API_KEY
|
|
109
|
+
api: openai-completions
|
|
110
|
+
baseURL: https://gateway.example/v1
|
|
111
|
+
reasoning: high
|
|
112
|
+
models:
|
|
113
|
+
- id: my-reasoner
|
|
114
|
+
reasoningEfforts:
|
|
115
|
+
off:
|
|
116
|
+
high: high
|
|
117
|
+
max: max
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Each key is a level the menu offers, and its value is the spelling sent on the wire as `reasoning_effort`, so `max: xhigh` renames a level for a gateway with its own vocabulary. Only `off` may stay empty, because for most endpoints not thinking is the parameter's absence. The route's `reasoning` is the level used while a session has picked none; choosing an effort in the picker saves it, with the model, as the default for new sessions.
|
|
121
|
+
|
|
122
|
+
An `off` left empty sends nothing, which only stops a model that thinks on request; an `off` given a value sends that value as `reasoning_effort` instead. A model that thinks unless told not to — DeepSeek V4 behind an OpenAI-compatible gateway, for example — needs `compat.thinkingFormat: deepseek`, which makes `off` send `thinking: {type: disabled}` and every other level send `thinking: {type: enabled}` beside the effort:
|
|
123
|
+
|
|
124
|
+
```yaml
|
|
125
|
+
models:
|
|
126
|
+
- id: deepseek-v4-pro
|
|
127
|
+
compat:
|
|
128
|
+
thinkingFormat: deepseek
|
|
129
|
+
reasoningEfforts:
|
|
130
|
+
off:
|
|
131
|
+
high: high
|
|
132
|
+
max: max
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
A built-in provider's model whose gateway does not reason loses its levels with `reasoningEfforts: false` under `modelOverrides`; selecting an effort for it is then refused as `UNSUPPORTED_REASONING_EFFORT`. DeepSeek's own route needs none of this: its models already offer `off`, `low`, `high`, and `max`, and `llm-deepseek.reasoningEffort` sets the default the picker starts from:
|
|
136
|
+
|
|
137
|
+
```yaml
|
|
138
|
+
llm-deepseek:
|
|
139
|
+
reasoningEffort: max
|
|
140
|
+
```
|
|
141
|
+
|
|
82
142
|
### Request compatibility
|
|
83
143
|
|
|
84
144
|
A gateway can hold a working key at a reachable address and still refuse every request. pi-ai decides the shape of a request — which role carries the system prompt, which field caps the output, how a thinking level travels — from the endpoint's URL, and an address it does not recognize is addressed as though it were OpenAI itself. Most OpenAI-compatible gateways refuse at least one thing OpenAI accepts.
|
|
@@ -115,23 +175,16 @@ Each switch belongs to the protocols that declare it, so a switch valid on one `
|
|
|
115
175
|
|
|
116
176
|
Every switch, its accepted values, and the protocols that take it are listed under `PiAiCompatProfile` in the [generated `dsh-llm-pi-ai` configuration reference](../../config-catalog.md#deepseek-aidsh-llm-pi-ai) — which is derived from the source, so it cannot fall behind what the adapter accepts.
|
|
117
177
|
|
|
118
|
-
## Select a model
|
|
119
|
-
|
|
120
|
-
Configured providers appear in the model picker. Selecting a model also makes it the default for new sessions. A session that has already sent a request retains the model recorded in its own log.
|
|
121
|
-
|
|
122
|
-
If a saved default names a provider that was deleted, the composer displays **Select model** and blocks input until another model is selected.
|
|
123
|
-
|
|
124
178
|
## Troubleshooting
|
|
125
179
|
|
|
126
180
|
- **`MISSING_CREDENTIAL`** — Store the provider key through the Models page or supply the referenced environment variable.
|
|
127
181
|
- **`UNKNOWN_MODEL`** — Select a configured model or add the missing model to the custom provider.
|
|
128
182
|
- **Fetching available models returns 401** — Check the key. Model discovery calls the OpenAI-compatible `GET /models` endpoint; enter models manually for endpoints that do not provide it.
|
|
183
|
+
- **Fetching available models reports neither a `data` array nor a `models` object** — The endpoint's listing is in a format discovery does not read. Enter the models by hand.
|
|
129
184
|
- **The gateway refuses every request although the key and URL are right** — Its request shape differs from OpenAI's. Start with `compat.supportsDeveloperRole: false` and `compat.maxTokensField: max_tokens` on the route.
|
|
130
185
|
- **Only reasoning models fail** — pi-ai sends their system prompt as the `developer` role, which the gateway rejects. Set `compat.supportsDeveloperRole: false`.
|
|
186
|
+
- **The Effort menu does not appear for a model you entered by hand** — It declares no levels. Add `reasoningEfforts` to the model in `settings.yaml`.
|
|
187
|
+
- **`off` does not stop a DeepSeek model from thinking** — An empty `off` sends no reasoning field at all, and an endpoint that thinks by default keeps thinking. Set `compat.thinkingFormat: deepseek` on the model or the route.
|
|
131
188
|
- **A compat switch is refused as having no value** — A key written with nothing after the colon. Give it a value, or remove the key to keep the installed catalog's.
|
|
132
|
-
- **An image is refused before sending** — The model declares no image modality. Give a custom provider's model `input: [text, image]`; DeepSeek's own
|
|
189
|
+
- **An image is refused before sending** — The model declares no image modality. Give a custom provider's model `input: [text, image]`; on DeepSeek's own route, select `deepseek-v4-flash-vision-exp`, the model that declares images.
|
|
133
190
|
- **The provider rejects a request carrying an image** — The model declares images its endpoint does not actually serve. Remove `image` from whichever list granted it — the model's `input`, or the route's `defaultInput` — then start a new session: the attached image stays in the session log, so the same request repeats until the session moves off it.
|
|
134
|
-
|
|
135
|
-
## Advanced configuration
|
|
136
|
-
|
|
137
|
-
The generated [plugin configuration catalog](../../config-catalog.md) lists every supported field and default for every plugin; [`dsh-llm-pi-ai`](../../config-catalog.md#deepseek-aidsh-llm-pi-ai) is the provider section this page configures. The [`dsh-llm-pi-ai`](../../../packages/llm/llm-pi-ai/README.md) and [`dsh-llm-deepseek`](../../../packages/llm/llm-deepseek/README.md) references own direct `settings.yaml` configuration, catalog resolution, reasoning controls, credentials, and adapter errors.
|
|
@@ -12,21 +12,39 @@
|
|
|
12
12
|
|
|
13
13
|
密钥是只写的。保存后,页面只会收到脱敏描述符,永远不会收到明文密钥。密钥存储在 `$DSH_HOME/.credentials.yaml` 中,settings 只保留它的凭据引用。
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## 添加内置提供方
|
|
16
16
|
|
|
17
|
-
选择**添加提供方**,选取
|
|
17
|
+
选择**添加提供方**,选取 dsh 自带的提供方;列表显示的是提供方 id,例如 `anthropic`、`openai`、Kimi 对应的 `moonshotai`、GLM 对应的 `zai`。输入其 API 密钥并保存。已安装目录会提供端点、协议和模型列表。
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
通过 OAuth 登录的提供方(例如 Codex)暂不支持。
|
|
20
20
|
|
|
21
21
|
## 添加自定义提供方
|
|
22
22
|
|
|
23
|
-
对于公司网关、自建服务器或已安装目录中不存在的提供方,选择**添加自定义提供方**。提供小写 Provider ID、基础 URL、API
|
|
23
|
+
对于公司网关、自建服务器或已安装目录中不存在的提供方,选择**添加自定义提供方**。提供小写 Provider ID、基础 URL、API 协议、凭据和至少一个模型。**API 协议**必须选网关实际使用的那一种,表单提供三种:`openai-completions` 对应 OpenAI Chat Completions,`openai-responses` 对应 OpenAI Responses API,`anthropic-messages` 对应 Anthropic Messages API。一个提供方只使用一种协议,网关同时提供两种时需要建两个提供方。
|
|
24
24
|
|
|
25
25
|

|
|
26
26
|
|
|
27
27
|
Provider ID 是永久的,因为请求、已保存会话、模型默认值和凭据引用都会使用它。如需重命名提供方,请添加新提供方并删除旧提供方。显示名称、基础 URL、协议、凭据和模型仍可编辑。
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
### 探测模型
|
|
30
|
+
|
|
31
|
+
在**模型目录**中选择**获取可用模型**,即可询问端点它提供哪些模型。请求使用表单当前的 API 地址、协议和密钥,已保存的提供方则用已存储的密钥;响应会打开一个可搜索的选择框,搜索、勾选想要的模型,再点**添加所选**。保存或创建提供方之前不会存储任何内容。
|
|
32
|
+
|
|
33
|
+
探测读取的是常见网关公开的列表格式,但并非每个端点都用这些格式作答,所以它只是便利手段而非保证:探测失败或列表为空时,手动添加模型 ID 即可,效果完全一样。内置提供方一律由已安装目录作答,即使其 API 地址指向网关也是如此,要查看网关实际提供的模型,请通过自定义提供方探测。
|
|
34
|
+
|
|
35
|
+
## 选择模型
|
|
36
|
+
|
|
37
|
+
已配置的提供方会出现在模型选择器中。选择模型也会将其设为新会话的默认值。已发送过请求的会话会保留自身日志中记录的模型。
|
|
38
|
+
|
|
39
|
+
如果已保存默认值指向已删除的提供方,输入框会显示**选择模型**,并在选择其他模型前阻止输入。
|
|
40
|
+
|
|
41
|
+
## 进阶配置
|
|
42
|
+
|
|
43
|
+
自动生成的[插件配置目录](../../config-catalog.zh.md)列出每个插件的所有受支持字段与默认值;[`dsh-llm-pi-ai`](../../config-catalog.zh.md#deepseek-aidsh-llm-pi-ai) 就是本页所配置的那个提供方段落。[`dsh-llm-pi-ai`](../../../packages/llm/llm-pi-ai/README.zh.md) 和 [`dsh-llm-deepseek`](../../../packages/llm/llm-deepseek/README.zh.md) 参考文档负责直接 `settings.yaml` 配置、目录解析、推理控制、凭据与适配器错误。
|
|
44
|
+
|
|
45
|
+
::: tip 表单刻意保持精简
|
|
46
|
+
模型页只开放让一条路由得以存在的字段:API 密钥、显示名称、API 地址、API 协议,以及每个模型的 ID、显示名称、上下文窗口和最大输出 token 数。其余所有字段——推理等级、图片输入、请求兼容性开关、请求头、超时、重试策略——都在 `$DSH_HOME/settings.yaml` 中设置,也就是模型页写入的同一份文档。可以直接编辑它;浏览器与服务器在同一台机器时,也可以点击设置页顶部的**打开配置文件**打开它。适配器会在下一次请求时重新读取,无需重启任何东西。下面各小节介绍多数网关会用到的字段。
|
|
47
|
+
:::
|
|
30
48
|
|
|
31
49
|
### 图片输入
|
|
32
50
|
|
|
@@ -64,7 +82,7 @@ llm-pi-ai:
|
|
|
64
82
|
- id: second-model
|
|
65
83
|
```
|
|
66
84
|
|
|
67
|
-
`defaultInput` 是回退值而不是覆盖值,默认为 `[text]
|
|
85
|
+
`defaultInput` 是回退值而不是覆盖值,默认为 `[text]`:在内置提供方上,它只为其目录未描述的模型作答,因此绝不会把目录中本就具备图片能力的模型的该能力去掉。要收窄这类模型,请用它自己的 `input`。内置提供方没有可供填写的 `models` 列表,因此写在 `modelOverrides` 下,以模型 id 为键:
|
|
68
86
|
|
|
69
87
|
```yaml
|
|
70
88
|
llm-pi-ai:
|
|
@@ -79,6 +97,48 @@ llm-pi-ai:
|
|
|
79
97
|
|
|
80
98
|
这两个字段都是对你端点的断言,而不是对它的检查。声明了端点并不提供的图片能力的模型不会在这里被拦下,改由提供方拒绝该请求。
|
|
81
99
|
|
|
100
|
+
### 推理等级
|
|
101
|
+
|
|
102
|
+
对于声明了推理等级的模型,模型选择器会提供**推理等级**菜单。内置提供方的模型从已安装目录继承其等级。手动录入的模型不声明任何等级,因此模型菜单里不会出现推理等级项,由端点自身的默认值决定模型是否思考。请在 `$DSH_HOME/settings.yaml` 中用 `reasoningEfforts` 声明等级:
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
llm-pi-ai:
|
|
106
|
+
providers:
|
|
107
|
+
my-gateway:
|
|
108
|
+
apiKeyEnv: GATEWAY_API_KEY
|
|
109
|
+
api: openai-completions
|
|
110
|
+
baseURL: https://gateway.example/v1
|
|
111
|
+
reasoning: high
|
|
112
|
+
models:
|
|
113
|
+
- id: my-reasoner
|
|
114
|
+
reasoningEfforts:
|
|
115
|
+
off:
|
|
116
|
+
high: high
|
|
117
|
+
max: max
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
每个键都是菜单提供的一个等级,其值是在协议上以 `reasoning_effort` 发送的写法,因此 `max: xhigh` 可以为自有一套词汇的网关重命名某个等级。只有 `off` 可以留空,因为对多数端点来说,不思考就是不传该参数。路由的 `reasoning` 是会话尚未选择等级时采用的等级;在选择器中选定某个等级后,它会与模型一起保存为新会话的默认值。
|
|
121
|
+
|
|
122
|
+
留空的 `off` 什么都不发送,这只能让「按请求才思考」的模型停下来;给 `off` 一个值,则会把该值作为 `reasoning_effort` 发送。对于「不明确关闭就会思考」的模型——例如 OpenAI 兼容网关后面的 DeepSeek V4——需要 `compat.thinkingFormat: deepseek`:它让 `off` 发送 `thinking: {type: disabled}`,其他每个等级则在 effort 之外再发送 `thinking: {type: enabled}`:
|
|
123
|
+
|
|
124
|
+
```yaml
|
|
125
|
+
models:
|
|
126
|
+
- id: deepseek-v4-pro
|
|
127
|
+
compat:
|
|
128
|
+
thinkingFormat: deepseek
|
|
129
|
+
reasoningEfforts:
|
|
130
|
+
off:
|
|
131
|
+
high: high
|
|
132
|
+
max: max
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
网关并不提供推理能力的内置提供方模型,可在 `modelOverrides` 下用 `reasoningEfforts: false` 去掉其等级;之后再为它选择等级会被拒绝并报 `UNSUPPORTED_REASONING_EFFORT`。DeepSeek 自身的路由不需要以上任何配置:其模型已经提供 `off`、`low`、`high` 和 `max`,`llm-deepseek.reasoningEffort` 设置选择器的起始默认值:
|
|
136
|
+
|
|
137
|
+
```yaml
|
|
138
|
+
llm-deepseek:
|
|
139
|
+
reasoningEffort: max
|
|
140
|
+
```
|
|
141
|
+
|
|
82
142
|
### 请求兼容性
|
|
83
143
|
|
|
84
144
|
网关可能持有可用的密钥、地址也通得到,却仍然拒绝每一个请求。pi-ai 依据端点的 URL 决定请求的形状——系统提示词由哪个角色承载、输出上限写在哪个字段、思考级别如何传输——而对于它无法识别的地址,会当作 OpenAI 本身来对待。多数 OpenAI 兼容网关至少会拒绝 OpenAI 所接受的某一样东西。
|
|
@@ -115,23 +175,16 @@ llm-pi-ai:
|
|
|
115
175
|
|
|
116
176
|
全部开关、各自接受的取值,以及接受它们的协议,都列在[生成的 `dsh-llm-pi-ai` 配置参考](../../config-catalog.zh.md#deepseek-aidsh-llm-pi-ai)的 `PiAiCompatProfile` 之下——该参考派生自源码,因此不会落后于适配器实际接受的内容。
|
|
117
177
|
|
|
118
|
-
## 选择模型
|
|
119
|
-
|
|
120
|
-
已配置的提供方会出现在模型选择器中。选择模型也会将其设为新会话的默认值。已发送过请求的会话会保留自身日志中记录的模型。
|
|
121
|
-
|
|
122
|
-
如果已保存默认值指向已删除的提供方,输入框会显示**选择模型**,并在选择其他模型前阻止输入。
|
|
123
|
-
|
|
124
178
|
## 排错
|
|
125
179
|
|
|
126
180
|
- **`MISSING_CREDENTIAL`**:通过模型页存储提供方密钥,或提供被引用的环境变量。
|
|
127
181
|
- **`UNKNOWN_MODEL`**:选择已配置的模型,或向自定义提供方添加缺失的模型。
|
|
128
182
|
- **获取可用模型返回 401**:检查密钥。模型发现会调用 OpenAI 兼容的 `GET /models` 端点;对于不提供该端点的服务,请手动输入模型。
|
|
183
|
+
- **获取可用模型提示既没有 `data` 数组也没有 `models` 对象**:端点返回的列表格式不在探测的读取范围内。请手动输入模型。
|
|
129
184
|
- **密钥与地址都正确,网关却拒绝每一个请求**:它的请求形状与 OpenAI 不同。先在路由上设 `compat.supportsDeveloperRole: false` 与 `compat.maxTokensField: max_tokens`。
|
|
130
185
|
- **只有推理模型失败**:pi-ai 把它们的系统提示词以 `developer` 角色发出,而网关拒绝该角色。设 `compat.supportsDeveloperRole: false`。
|
|
186
|
+
- **手动录入的模型没有推理等级菜单**:该模型没有声明任何等级。在 `settings.yaml` 中给该模型加上 `reasoningEfforts`。
|
|
187
|
+
- **`off` 无法让 DeepSeek 模型停止思考**:留空的 `off` 不发送任何推理字段,默认思考的端点就继续思考。请在模型或路由上设置 `compat.thinkingFormat: deepseek`。
|
|
131
188
|
- **某个 compat 开关因没有值而被拒绝**:冒号后什么都没写。给它一个值,或删掉该键以沿用已安装 catalog 的值。
|
|
132
|
-
- **图片在发送前被拒绝**:该模型未声明图片模态。请给自定义提供方的模型加上 `input: [text, image]
|
|
189
|
+
- **图片在发送前被拒绝**:该模型未声明图片模态。请给自定义提供方的模型加上 `input: [text, image]`;在 DeepSeek 自身的路由上,请选择声明了图片能力的模型 `deepseek-v4-flash-vision-exp`。
|
|
133
190
|
- **提供方拒绝了带图片的请求**:该模型声明了其端点实际并不提供的图片能力。请从授予它图片能力的那个列表中移除 `image`——可能是模型的 `input`,也可能是路由的 `defaultInput`——然后开启新会话:附加的图片会留在会话日志里,因此在会话离开它之前,同一个请求会不断重复。
|
|
134
|
-
|
|
135
|
-
## 进阶配置
|
|
136
|
-
|
|
137
|
-
自动生成的[插件配置目录](../../config-catalog.zh.md)列出每个插件的所有受支持字段与默认值;[`dsh-llm-pi-ai`](../../config-catalog.zh.md#deepseek-aidsh-llm-pi-ai) 就是本页所配置的那个提供方段落。[`dsh-llm-pi-ai`](../../../packages/llm/llm-pi-ai/README.zh.md) 和 [`dsh-llm-deepseek`](../../../packages/llm/llm-deepseek/README.zh.md) 参考文档负责直接 `settings.yaml` 配置、目录解析、推理控制、凭据与适配器错误。
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write docs/web-styling.md
|
|
5
|
-
web-styling.md:
|
|
6
|
-
web-styling.zh.md:
|
|
5
|
+
web-styling.md: 250936b82f06b47d00c0b6646cbea5bdb41f42ea
|
|
6
|
+
web-styling.zh.md: 9845b4f5d8341624d294a008f505c0fb1ec9757a
|
|
@@ -22,6 +22,7 @@ Global style sheets belong in `ui-theme/src/styles/`. Component styles live besi
|
|
|
22
22
|
- Rounded corners inherit the global superellipse smoothing from ui-theme's `corner-shape.css` on supporting engines. Pair `corner-shape: round` with every full-round `border-radius` (`50%`, `100%`, or a pill radius) so circles and capsules keep circular arcs; the ui-theme corner-shape spec enforces the pairing.
|
|
23
23
|
- Elevated surfaces (menus, popovers, modals, panels, floating buttons, the composer) set `border: 0` and take `box-shadow: var(--dsw-elevation-panel)`, `var(--dsw-elevation-prominent)`, or the composer's `var(--dsw-elevation-soft)` (larger blur at lower alpha): the 0.5px hairline stroke is the first shadow layer, and `--dsw-elevation-stroke-color` rebinds or suppresses it per surface or state. Never pair a `--dsw-alias-border-*` border with an lv/elevation shadow — the ui-theme elevation spec rejects the pairing; state-colored borders (warn panels) stay real borders.
|
|
24
24
|
- Flat borders and separators that use a neutral `--dsw-alias-border-*` token draw at `0.5px` — buttons, inputs, cards, row dividers, and separators drawn as filled boxes (menu separators, the conversation header seam, markdown `hr`, vertical rails) share the hairline weight, which Chromium paints as one device pixel. Dashed affordances and state-colored borders keep 1px; spinner ring tracks keep their width through the spec's explicit allowlist. The ui-theme elevation spec rejects wider neutral solid borders.
|
|
25
|
+
- Clickable artifact links (markdown anchors, prose file mentions, web source and fetch links, produced-file chips, workflow member links) color through `--dsw-alias-link` at `font-weight: 500`, with no underline at rest and a dotted 3px-offset underline on hover/focus. Text-leading anchors also lead with the ui-primitives `LinkIcon` category glyph riding `currentColor`; workflow member links and image-only anchors carry no glyph, and tool-row file links keep their grey dotted affordance ([clickable-link Agent Note](../.agents/notes/implemented/feature/2026-09-04-web-clickable-link-styles.md)).
|
|
25
26
|
|
|
26
27
|
## Changing the system
|
|
27
28
|
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
- 支持的引擎上,圆角继承 ui-theme `corner-shape.css` 的全局超级椭圆平滑。每个正圆 `border-radius`(`50%`、`100%` 或胶囊半径)必须配对 `corner-shape: round`,使圆形与胶囊保持圆弧;ui-theme 的 corner-shape spec 强制这一配对。
|
|
23
23
|
- 高层级表面(菜单、浮层、对话框、面板、悬浮按钮、输入框)设 `border: 0` 并使用 `box-shadow: var(--dsw-elevation-panel)`、`var(--dsw-elevation-prominent)` 或输入框专用的 `var(--dsw-elevation-soft)`(更大模糊、更低透明度):0.5px 发丝描边是第一层投影,`--dsw-elevation-stroke-color` 可按表面或状态重绑或抑制描边。不得将 `--dsw-alias-border-*` border 与 lv/elevation 投影配对——ui-theme 的 elevation spec 会拒绝;状态色 border(warn 面板)保持真 border。
|
|
24
24
|
- 使用中性 `--dsw-alias-border-*` token 的平面边框与分割线一律 `0.5px`——按钮、输入框、卡片、行分割线,以及以填充盒绘制的分隔线(菜单分隔、对话标题栏接缝、markdown `hr`、竖向轨道线)共用发丝线粗细,Chromium 将其绘制为一个设备像素。dashed 记号与状态色 border 保持 1px;spinner 圆环经 spec 的显式豁免保留原宽度。更宽的中性 solid border 会被 ui-theme elevation spec 拒绝。
|
|
25
|
+
- 可点击产物链接(Markdown 锚点、正文文件引用、网页来源与抓取链接、产物 chips、workflow 成员链接)经 `--dsw-alias-link` 着色、`font-weight: 500`,默认无下划线,hover/focus 时为 3px offset 的点状下划线。带文字的锚点另以 ui-primitives 的 `LinkIcon` 分类图形(随 `currentColor`)作前置;workflow 成员链接与只包图片的锚点不带图形,工具行文件链接保持其灰色点线示能([可点击链接 Agent Note](../.agents/notes/implemented/feature/2026-09-04-web-clickable-link-styles.zh.md))。
|
|
25
26
|
|
|
26
27
|
## 变更系统
|
|
27
28
|
|
|
@@ -130,6 +130,14 @@ const homeAndGuide = pairedPages([
|
|
|
130
130
|
section: { root: '入门', en: 'Guide' },
|
|
131
131
|
order: 2,
|
|
132
132
|
},
|
|
133
|
+
{
|
|
134
|
+
source: 'docs/user/guide/network-proxy.md',
|
|
135
|
+
route: 'guide/network-proxy.md',
|
|
136
|
+
label: { root: '网络代理', en: 'Network proxy' },
|
|
137
|
+
sidebar: { root: 'zh-guide', en: 'en-guide' },
|
|
138
|
+
section: { root: '入门', en: 'Guide' },
|
|
139
|
+
order: 3,
|
|
140
|
+
},
|
|
133
141
|
{
|
|
134
142
|
source: 'docs/user/guide/python-sdk.md',
|
|
135
143
|
route: 'guide/python-sdk.md',
|