dsh-router-core 0.4.1 → 0.4.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.md +18 -15
- package/cordis.patch.yml +10 -0
- package/docs/ext.md +65 -3
- package/lib/client-registry.js +633 -50
- package/lib/client.js +633 -50
- package/lib/contract-D4BWZ7Xe.js +13 -0
- package/lib/ext-prompt/index.js +527 -0
- package/lib/ext-prompt/locale/en.json +6 -0
- package/lib/ext-prompt/locale/zh.json +6 -0
- package/lib/ext-prompt/package.json +5 -0
- package/lib/ext-test/index.js +1 -12
- package/lib/index.js +298 -119
- package/lib/types/ext/contract.d.ts +113 -0
- package/lib/types/ext-prompt/content.d.ts +57 -0
- package/lib/types/ext-prompt/index.d.ts +53 -0
- package/lib/types/ext-prompt/logo.d.ts +17 -0
- package/lib/types/ext-prompt/mount.d.ts +51 -0
- package/lib/types/ext-prompt/plugin.d.ts +32 -0
- package/lib/types/ext-prompt/render.d.ts +105 -0
- package/lib/types/host-base-url.d.ts +31 -0
- package/lib/types/host-version.d.ts +7 -8
- package/lib/types/index.d.ts +1 -1
- package/lib/types/llm/adapter.d.ts +6 -1
- package/lib/types/shared.d.ts +33 -0
- package/package.json +17 -7
- package/src/client/EndpointTab.tsx +15 -18
- package/src/client/ExtDetail.tsx +552 -9
- package/src/client/index.tsx +7 -28
- package/src/client/router.css +132 -62
- package/src/ext/contract.ts +111 -0
- package/src/ext-prompt/assembly.test.ts +79 -0
- package/src/ext-prompt/content.ts +174 -0
- package/src/ext-prompt/ext-prompt.test.ts +432 -0
- package/src/ext-prompt/index.ts +104 -0
- package/src/ext-prompt/locale/en.json +6 -0
- package/src/ext-prompt/locale/zh.json +6 -0
- package/src/ext-prompt/logo.svg +14 -0
- package/src/ext-prompt/logo.ts +18 -0
- package/src/ext-prompt/mount.test.ts +95 -0
- package/src/ext-prompt/mount.ts +79 -0
- package/src/ext-prompt/plugin.ts +223 -0
- package/src/ext-prompt/render.test.ts +256 -0
- package/src/ext-prompt/render.ts +216 -0
- package/src/ext-prompt/row.json +5 -0
- package/src/ext-route.test.ts +78 -0
- package/src/host-base-url.test.ts +129 -0
- package/src/host-base-url.ts +43 -0
- package/src/host-version.test.ts +21 -28
- package/src/host-version.ts +39 -39
- package/src/index.ts +230 -96
- package/src/llm/adapter.test.ts +19 -42
- package/src/llm/adapter.ts +32 -50
- package/src/shared.ts +34 -0
package/README.md
CHANGED
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
<p align="center">
|
|
6
6
|
<a href="https://www.npmjs.com/package/dsh-router-core"><img src="https://img.shields.io/npm/v/dsh-router-core?style=flat-square&logo=npm&label=npm" alt="npm version"></a>
|
|
7
7
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-10b981?style=flat-square" alt="MIT license"></a>
|
|
8
|
-
<a href="https://www.npmjs.com/package/@deepseek-ai/dsh?activeTab=versions"><img alt="DSH 0.
|
|
9
|
-
<a href="https://www.npmjs.com/package/@deepseek-ai/dsh?activeTab=versions"><img alt="DSH 0.1.7-alpha.2 支持" src="https://img.shields.io/badge/DSH-0.1.7--alpha.2-e400dd?style=flat-square" /></a>
|
|
8
|
+
<a href="https://www.npmjs.com/package/@deepseek-ai/dsh?activeTab=versions"><img alt="DSH 0.2.0" src="https://img.shields.io/badge/DSH-0.2.0-4d6bfe?style=flat-square" /></a>
|
|
10
9
|
<a href="#agent-team-支持"><img alt="Agent Team" src="https://img.shields.io/badge/Agent%20Team-%E2%9C%93-10b981?style=flat-square" /></a>
|
|
11
10
|
</p>
|
|
12
11
|
|
|
@@ -14,13 +13,13 @@
|
|
|
14
13
|
<a href="#快速安装">快速安装</a> ·
|
|
15
14
|
<a href="#面板设置--路由">面板</a> ·
|
|
16
15
|
<a href="#agent-team-支持">Agent Team</a> ·
|
|
17
|
-
<a href="#api-端点openai-兼容
|
|
16
|
+
<a href="#api-端点openai-兼容v1">API 端点</a> ·
|
|
18
17
|
<a href="docs/suppliers.md">供应商开发</a> ·
|
|
19
18
|
<a href="docs/ext.md">扩展开发</a>
|
|
20
19
|
</p>
|
|
21
20
|
|
|
22
21
|
**插件版的 9router** —— 不是另开一个网关服务,而是直接作为 DSH 插件嵌进 DSH web,
|
|
23
|
-
|
|
22
|
+
在宿主 Web 服务的 `/v1` 上原生暴露 OpenAI 兼容端点(`http://127.0.0.1:<宿主页端口>/v1`;`dsh web` 默认 3080,面板「端点与密钥」页显示的是实际地址),把请求路由到内部供应商。
|
|
24
23
|
管理界面在**设置 → 路由**(官方设置页座位,不是自己开的页面)。装好即用,
|
|
25
24
|
不用多开一个 9router、不用维护第二个端口、不用在网关和 DSH 之间搬配置。
|
|
26
25
|
|
|
@@ -33,7 +32,8 @@ dsh-router 按**会话身份**亲和选号:同会话固定落同一个连接(前
|
|
|
33
32
|
|
|
34
33
|
## 快速安装
|
|
35
34
|
|
|
36
|
-
需要 **DSH `0.
|
|
35
|
+
需要 **DSH `0.2.0` 及以上**、Node.js `>= 20`,以及 `web` profile。0.1.x 宿主不再支持(设置注册、
|
|
36
|
+
tool 消息形态都已按 0.2.0 单面实现)。
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
39
|
dsh plugin --profile web add dsh-router-core
|
|
@@ -98,9 +98,9 @@ team 测试就是多个会话交错发请求。dsh-router 原生适配这个场
|
|
|
98
98
|
> **加连接**或**限制 team 并发成员数 ≤ 连接数**。详见
|
|
99
99
|
> [`docs/pool-sticky-block.md`](docs/pool-sticky-block.md) §11。
|
|
100
100
|
>
|
|
101
|
-
>
|
|
102
|
-
>
|
|
103
|
-
>
|
|
101
|
+
> 版本适配:插件按 **DSH 0.2.0 单面**实现(0.1.5/0.1.7 的兼容分叉已删除)。跑在
|
|
102
|
+
> 低于 0.2.0 的宿主上仍会加载,但会在宿主日志里打一条警告 —— 那些症状与「插件坏了」
|
|
103
|
+
> 无法区分,所以宁可出声。
|
|
104
104
|
|
|
105
105
|
## 面板(设置 → 路由)
|
|
106
106
|
|
|
@@ -160,11 +160,15 @@ team 测试就是多个会话交错发请求。dsh-router 原生适配这个场
|
|
|
160
160
|
为 DSH 模型目录里的 `router` provider 选项(设置 → 模型直接选组合名即可用),请求
|
|
161
161
|
按组合策略命中其中一个供应商模型;
|
|
162
162
|
- **端点与密钥** — 端点核心(无隧道/Tailscale):
|
|
163
|
-
- API 端点 URL(`
|
|
163
|
+
- API 端点 URL(面板按当前页面 origin 显示,可复制;端口跟着宿主走,`dsh web` 默认 3080);
|
|
164
164
|
- 鉴权设置 `requireApiKey` 开关;
|
|
165
165
|
- API Keys 管理:创建 / 启用切换 / 显示 / 复制 / 删除(持久化到 `data/keys.json`)。
|
|
166
166
|
|
|
167
|
-
## API 端点(OpenAI
|
|
167
|
+
## API 端点(OpenAI 兼容,`/v1`)
|
|
168
|
+
|
|
169
|
+
> 端口 = **宿主 Web 服务实际监听的端口**:`dsh web` 默认 3080,桌面端等通道由宿主
|
|
170
|
+
> 分配(不一定还是 3080)。下面按 3080 写;换通道时把端口换成面板「端点与密钥」
|
|
171
|
+
> 页里显示的那个即可。
|
|
168
172
|
|
|
169
173
|
```bash
|
|
170
174
|
# 模型列表
|
|
@@ -177,7 +181,7 @@ curl -X POST http://localhost:3080/v1/chat/completions \
|
|
|
177
181
|
```
|
|
178
182
|
|
|
179
183
|
任何支持 OpenAI 兼容 API 的工具(Claude Code、Cline、DSH 设置-模型 等)都可以把
|
|
180
|
-
`baseURL`
|
|
184
|
+
`baseURL` 指向面板「端点与密钥」页里显示的那个端点(`dsh web` 下即 `http://localhost:3080/v1`)。
|
|
181
185
|
|
|
182
186
|
**鉴权**:默认 `requireApiKey=false`,`/v1/*` 不要求鉴权(本地使用,与 9router 一致)。
|
|
183
187
|
在「端点与密钥」页开启「要求 API Key」后,请求必须带
|
|
@@ -342,7 +346,7 @@ curl -X POST http://localhost:3080/v1/chat/completions \
|
|
|
342
346
|
│ │ ├─ SupplierDetail 供应商详情:链接池 + 加链接 + 可用模型 + 模型测试
|
|
343
347
|
│ │ ├─ CombosTab / EndpointTab
|
|
344
348
|
│ │ └─ ExtTab → ExtDetail 扩展列表(只列已启用)与详情(优先渲染扩展自带的面板)
|
|
345
|
-
│ └─ LastHitDock 「路由」徽章(composer.dock
|
|
349
|
+
│ └─ LastHitDock 「路由」徽章(composer.dock 座位;按会话显示最近命中)
|
|
346
350
|
├─ 设置 → 插件(官方 plugins.detail.section 座位)
|
|
347
351
|
│ ├─ (原生「包含的组件」) core patch 里声明的行:core 本体 + 内置供应商 + 内置扩展
|
|
348
352
|
│ │ 宿主管的开关,关一行 = loader 不 import 它
|
|
@@ -355,7 +359,7 @@ curl -X POST http://localhost:3080/v1/chat/completions \
|
|
|
355
359
|
│ └─ RouterAdapter(src/llm/adapter.ts) OpenAI SSE → DSH StreamChunk
|
|
356
360
|
│ (usage 经 toTokenUsage 转 DSH 契约,见 docs/suppliers.md)
|
|
357
361
|
│ (带 x-dsh-router-session 头:宿主 sessionId → 会话亲和)
|
|
358
|
-
├─ host-version(src/host-version.ts)
|
|
362
|
+
├─ host-version(src/host-version.ts) 报宿主版本 + 守 0.2.0 版本地板(低于则告警)
|
|
359
363
|
├─ KeysStore(src/keys.ts) 密钥库 + requireApiKey
|
|
360
364
|
└─ Router(路由器) → suppliers[]
|
|
361
365
|
├─ OpenCodeSupplier(lib/suppliers/opencode/index.js) 无账号直连(Zen 免费档,需 CLI 握手)
|
|
@@ -400,8 +404,7 @@ curl -X POST http://localhost:3080/v1/chat/completions \
|
|
|
400
404
|
|
|
401
405
|
## 前提
|
|
402
406
|
|
|
403
|
-
- **DSH 版本**:`0.
|
|
404
|
-
差异(设置注册方式、tool 消息形态、底部徽章);「路由」徽章需 **≥ 0.1.7**;
|
|
407
|
+
- **DSH 版本**:`0.2.0` 及以上(单面支持;低于此版本会在宿主日志里告警);
|
|
405
408
|
- 凭证由 dsh-router 核心统一管(SQLite 库 `<dataDir>/auths/credentials.sqlite`);
|
|
406
409
|
- 供应商接入与开发见 [`docs/suppliers.md`](docs/suppliers.md);
|
|
407
410
|
- 重启 DSH 后 `/v1/*` 即生效;面板管理账号、模型与密钥。
|
package/cordis.patch.yml
CHANGED
|
@@ -35,3 +35,13 @@
|
|
|
35
35
|
- id: dsh-router-ext-test
|
|
36
36
|
name: 'dsh-router-core/ext-test'
|
|
37
37
|
disabled: true
|
|
38
|
+
# 分层提示词扩展。**默认关闭**(与 ext-test 同款):行关闭时 loader 根本不
|
|
39
|
+
# import 它,扩展就不在 `router.ext` 表里 —— 「打开才出现这张卡片」是天然的。
|
|
40
|
+
#
|
|
41
|
+
# ⚠️ 这里来回改过一次(2026-09-29):先改成默认开启(理由是"准则是基础设施"),
|
|
42
|
+
# 随后又改回默认关闭 —— **决定权在用户**。判据见 ext-prompt.test.ts 的
|
|
43
|
+
# 「那一行默认关闭」用例(它真读本文件,改了会红)。
|
|
44
|
+
# 准则内容随核心分发(`src/ext-prompt/content.ts`)—— 改准则要发新版本。
|
|
45
|
+
- id: dsh-router-ext-prompt
|
|
46
|
+
name: 'dsh-router-core/ext-prompt'
|
|
47
|
+
disabled: true
|
package/docs/ext.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
扩展插件是**独立的 DSH 插件(npm 包)**,只提供**差异化能力** —— 目前是改写一条
|
|
4
4
|
bash 命令。装好后出现在「设置 → 路由 → 扩展」,**开关不在那里** —— 启停的唯一入口是
|
|
5
5
|
官方插件页「设置 → 插件 → dsh-router-core」详情页的「路由组件」一节;面板这一页只列
|
|
6
|
-
|
|
6
|
+
已启用的扩展。**总开关**在这一层只读;但扩展自报的**子开关**(见下「子开关」一节)
|
|
7
|
+
在这一层可点。
|
|
7
8
|
|
|
8
9
|
**dsh-router 核心只做管理面,不做拦截。** 扩展插件自己挂监听、自己裁决、自己短路
|
|
9
10
|
(2026-09 重构,见下)。
|
|
@@ -28,6 +29,49 @@ bash 命令。装好后出现在「设置 → 路由 → 扩展」,**开关不
|
|
|
28
29
|
参考实现:[dsh-router-ext-rtk](https://github.com/CARVIN94/dsh-router-ext-rtk)
|
|
29
30
|
(把命令改写成 `rtk <cmd>` 压缩输出)。
|
|
30
31
|
|
|
32
|
+
## 子开关:一个扩展里有几件独立的事
|
|
33
|
+
|
|
34
|
+
有些扩展的「开/关」不是一件事而是**几件独立的事** —— 例如 `分层提示词` 有 14 个
|
|
35
|
+
准则分类,每条都该能单独关。硬塞进一个总开关 = 要么全给、要么全不给。
|
|
36
|
+
|
|
37
|
+
扩展可在 `RouterExt` 上声明 `controls`(子开关列表)与 `setControl`(改一个):
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
export function createPromptExt(deps): RouterExt {
|
|
41
|
+
return {
|
|
42
|
+
id: 'prompt',
|
|
43
|
+
// ... name / description / source / getState
|
|
44
|
+
controls: PROMPT_CATEGORIES.map((c) => ({
|
|
45
|
+
id: c.id, title: c.title,
|
|
46
|
+
body: c.body, // ← 原文:面板点标题就能读
|
|
47
|
+
on: resolve(c.id),
|
|
48
|
+
})),
|
|
49
|
+
setControl: (controlId, on) => { /* 写进 data 抽屉;返回 false = 失败 */ },
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
面板:扩展详情页(设置 → 路由 → 扩展 → 点开卡片)逐条渲染,走同一个
|
|
55
|
+
`PATCH /router/api/ext`,body 是 `{ id, controlId, on }`。
|
|
56
|
+
|
|
57
|
+
⚠️ **`controls[].body` 要给原文**,不只是标题。不给的话用户是在**盲切** ——
|
|
58
|
+
只看到「结构」「交付」这样的名字,不知道这一条到底写了什么,也无从判断该不该
|
|
59
|
+
关掉它。面板用原生 `<details>` 折叠展示(默认收起,点标题展开),**开关留在收起
|
|
60
|
+
态也可见** —— 不能要求先展开才关得掉。
|
|
61
|
+
|
|
62
|
+
⚠️ 三条约定:
|
|
63
|
+
|
|
64
|
+
1. **落盘形状归扩展,核心不代劳。** 核心只把 `{controlId, on}` 递进来并如实回传
|
|
65
|
+
成功/失败 —— 它**不认识** `data.categories` 长什么样。核心一旦代写,就会变成
|
|
66
|
+
「认识所有扩展私有数据格式」的地方。
|
|
67
|
+
2. **总开关关 ⇒ 子开关一并失效,但不清零。** 面板显示为**禁用**而不是隐藏
|
|
68
|
+
(隐藏会让用户以为它们不存在),重新打开总开关就恢复原样。
|
|
69
|
+
3. **`control.id` 是持久化键**,改名会让用户已保存的选择丢失 —— 同 `ExtId`。
|
|
70
|
+
|
|
71
|
+
⚠️ **不适用于外部插件包(暂时)**:与下面的 `registerExtPanel` 一样,
|
|
72
|
+
`controls` 的读写经 `dsh-router-core/client` 才能到达外部 bundle,而该入口不导出
|
|
73
|
+
这些能力。要让外部扩展用上,需要给它加一条真正的导出路径。这是已知的升级路径。
|
|
74
|
+
|
|
31
75
|
## 分工:管理归核心,执行归插件
|
|
32
76
|
|
|
33
77
|
| | 谁负责 |
|
|
@@ -67,6 +111,19 @@ interface RouterExt {
|
|
|
67
111
|
readonly description?: string // 面板内容区说明
|
|
68
112
|
readonly icon?: string // 卡片/详情图标 URL
|
|
69
113
|
readonly source?: 'builtin' // 标了 = 随核心分发(见下);不标 = 独立插件
|
|
114
|
+
|
|
115
|
+
// ↓ 以下 7 个全是**可选**的子开关能力。不实现 =「一个总开关的扩展」,
|
|
116
|
+
// 面板只显示总开关、不渲染子开关列表。权威定义见 src/ext/contract.ts。
|
|
117
|
+
readonly controls?: ExtControl[] // 子开关列表。⚠️ 必须每次读都重算(getter);
|
|
118
|
+
// 做成构造时的数组快照 ⇒ 改完文本刷新面板
|
|
119
|
+
// 拿到的还是旧值(2026-09-29 实测踩过)
|
|
120
|
+
setControl?(id: string, on: boolean): boolean // 开关一个。false = 失败,核心回 400 且不写盘
|
|
121
|
+
setControlText?(id: string, patch: {title?, body?}): boolean // 改文本(部分更新,未出现的字段保持原样)
|
|
122
|
+
setControlOrder?(ids: string[]): boolean // 拖动排序(改的是列表本身,不是某一条)
|
|
123
|
+
addCustomControl?(title: string, body: string): string | null // 新增自建条目,返回新 id;失败 null
|
|
124
|
+
removeCustomControl?(id: string): boolean // 删。只该允许删自建的
|
|
125
|
+
resetControlText?(id: string): boolean // 还原文本。自建条目无「自带内容」,应返 false
|
|
126
|
+
|
|
70
127
|
getState(): ExtState // 运行时事实
|
|
71
128
|
dispose?(): void
|
|
72
129
|
}
|
|
@@ -80,6 +137,11 @@ interface ExtState {
|
|
|
80
137
|
|
|
81
138
|
**没有 `rewrite`。** 怎么改命令是插件的实现细节,核心不感知、不调用。
|
|
82
139
|
|
|
140
|
+
> 子开关的**落盘形状归扩展所有**:本仓是 `data.categories[id]` 存开关、
|
|
141
|
+
> `data.text[id]` 存文本,**两个键分开**(开关是"开不开",文本是"写成什么样",
|
|
142
|
+
> 混在一个键里改标题会顺手碰到开关状态)。核心只把 `{controlId, on}` 递进来、
|
|
143
|
+
> 如实回传成功/失败,**不代写** —— 否则核心会变成「认识所有扩展私有数据格式」的地方。
|
|
144
|
+
|
|
83
145
|
**`source: 'builtin'`** 标的是「随核心分发」。这类扩展同时是插件页原生「包含的组件」
|
|
84
146
|
里的**一行**(自带宿主管的开关,关一行 = loader 不 import 它),因此插件页那个自绘的
|
|
85
147
|
「路由组件」节**只列独立安装的扩展**,不把它们重复列一遍。判定只认这一个字段,没有别的
|
|
@@ -181,7 +243,7 @@ tsdown.config.ts
|
|
|
181
243
|
{
|
|
182
244
|
"main": "lib/index.js",
|
|
183
245
|
"dsh": { "bundle": { "patch": "./cordis.patch.yml" } },
|
|
184
|
-
"peerDependencies": { "@deepseek-ai/cordis": "^4.0.
|
|
246
|
+
"peerDependencies": { "@deepseek-ai/cordis": "^4.0.4" }
|
|
185
247
|
}
|
|
186
248
|
```
|
|
187
249
|
|
|
@@ -223,7 +285,7 @@ tools.get('bash') // ❌ 只查全局视图,查不到 → 静默走
|
|
|
223
285
|
|
|
224
286
|
```bash
|
|
225
287
|
# 1) 看 API:扩展器 enabled / ready 是否都为 true
|
|
226
|
-
curl -s http://127.0.0.1:3080/router/api/ext
|
|
288
|
+
curl -s http://127.0.0.1:3080/router/api/ext # 端口 = 宿主页端口(面板可见),非 3080 的通道要换
|
|
227
289
|
|
|
228
290
|
# 2) 跑一条有等价改写的命令,看输出是否变成压缩格式
|
|
229
291
|
ls # 被改写时是 rtk 树形格式,不是原生列表
|