dsh-provider-proxy 0.0.0-stage → 0.2.1
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/LICENSE +21 -0
- package/README.md +115 -2
- package/cordis.patch.yml +14 -0
- package/index.js +6 -0
- package/lib/client.js +535 -0
- package/lib/client.js.map +7 -0
- package/lib/index.js +1212 -0
- package/lib/index.js.map +7 -0
- package/package.json +88 -6
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-provider-proxy contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,116 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-provider-proxy
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
给 **DeepSeek Harness**(`0.2.0-rc.1` 及以上)用的插件:在 **设置** 里为**每个 provider 单独配置**「这个 provider 要不要走代理」,填写代理类型 + IP/端口。只有被你打开开关的 provider 走代理,其余 provider 和 DSH 的其它流量保持原来的直连路径。
|
|
4
|
+
|
|
5
|
+
支持官方桌面版(DSH Desktop)与 `dsh web`。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
先确保已构建过产物(`lib/index.js`、`lib/client.js`):
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install
|
|
13
|
+
npm run build
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
桌面版(**必须先完整打开一次桌面版以初始化 profile,然后完全退出**,否则安装命令会拒绝执行):
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
dsh plugin --profile desktop add D:\gitwork\dsh-provider-proxy
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Web:
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
dsh plugin --profile web add /absolute/path/to/dsh-provider-proxy
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
安装后重启对应应用。设置项写在 `$DSH_HOME/profiles/<profile>/cordis.patch.yml`。
|
|
29
|
+
|
|
30
|
+
## 使用
|
|
31
|
+
|
|
32
|
+
打开 **设置**,左侧导航里会出现一项 **Provider 代理**(英文界面为 *Provider Proxy*)。
|
|
33
|
+
页面里每个 provider 一张卡片,各自独立配置:
|
|
34
|
+
|
|
35
|
+
| 字段 | 说明 |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| 开关 | 该 provider 是否走代理 |
|
|
38
|
+
| 类型 | `HTTP` / `HTTPS`(`SOCKS5` 目前未实现,选它会给出提示) |
|
|
39
|
+
| IP / 主机 | 只填主机名或 IP,不要带 `http://` 或路径 |
|
|
40
|
+
| 端口 | 1–65535 的整数 |
|
|
41
|
+
| 用户名 | 可选 |
|
|
42
|
+
|
|
43
|
+
列表只包含**已激活**的 provider(`llm.listProviders()` 能列出的那些)。仅在配置里声明、
|
|
44
|
+
但没有实际注册的可配置 provider 不显示——它的请求根本不会被发出去。
|
|
45
|
+
已经在 `providers` 里配过代理的条目即使暂时不激活也仍然保留,避免已保存的设置悄悄消失。
|
|
46
|
+
|
|
47
|
+
保存立即对新请求生效,不需要重启;正在进行中的流会继续用它启动时的那条连接。
|
|
48
|
+
|
|
49
|
+
> **为什么是独立设置页,而不是内嵌在 Models 的 provider 卡片里?**
|
|
50
|
+
> 本插件最早按卡片内嵌(`settings.models.provider-card`)实现,但那个槽位是 keyed 的、
|
|
51
|
+
> 按 provider 命名空间派发,第三方注册在 0.2.0-rc.1 上始终匹配不上——不报错、不发请求、
|
|
52
|
+
> 界面上什么都不出现。独立分区(`settings.section`)是同一 profile 下
|
|
53
|
+
> `dsh-llm-retry-settings` 已被验证可用的机制。
|
|
54
|
+
|
|
55
|
+
## 它是怎么工作的
|
|
56
|
+
|
|
57
|
+
0.2.0 没有「给单个 provider 注入 transport」的官方口子,这是核对过源码的结论:
|
|
58
|
+
|
|
59
|
+
- `llm-deepseek` 直接调用全局 `fetch`,请求参数里没有 `dispatcher`;
|
|
60
|
+
- `llm-pi-ai` 走 pi-ai SDK 自己的 HTTP,DSH 无法配置;
|
|
61
|
+
- `LlmCallControls` 只有 `reasoningEffort` / `temperature` / `maxTokens` / `stop`;
|
|
62
|
+
- `llm/stream` 拦截的是**已解码的** `StreamChunk`,发生在 HTTP 之后;
|
|
63
|
+
- `ctx.web` 只服务 `web_search` / `web_fetch`,与 provider 流量无关;
|
|
64
|
+
- 官方的 `@deepseek-ai/dsh-http-proxy` 是**库**不是插件:`installGlobalProxy` 是模块私有函数,没有 service、没有 Config、只有从环境变量解析出的一份进程级策略。
|
|
65
|
+
|
|
66
|
+
所以请求在唯一还能拿到线索的地方被路由:`llm/stream` waterfall 的 `options.provider`。用一个 `AsyncLocalStorage` 把选中的 dispatcher 带到流上,再由一层很薄的 `globalThis.fetch` 包装把它交给 fetch。
|
|
67
|
+
|
|
68
|
+
两个关键点:
|
|
69
|
+
|
|
70
|
+
1. **每个 provider 各自持有一个 dispatcher**(而不是共用一个),所以即使两个 provider 指向同一个上游主机,也不可能互相串用对方的代理。
|
|
71
|
+
2. **流的惰性消费必须被处理**:adapter 的 HTTP 调用通常发生在后续的 `next()` 里,那时 `als.run()` 早已返回。所以每次迭代器的 `next/return/throw` 都会被重新包进作用域里 —— 这正是旧版实现在这里踩过的坑,仓库里有对应的回归测试。
|
|
72
|
+
|
|
73
|
+
代价是这一个全局 `fetch` 包装:作用域之外的行为与原生完全一致,作用域之内若调用方已经自带 `dispatcher` 也不会被覆盖。
|
|
74
|
+
|
|
75
|
+
## 已知限制
|
|
76
|
+
|
|
77
|
+
- **SOCKS5 未实现。** `undici` 的 `ProxyAgent` 只会用 HTTP CONNECT 连 HTTP 代理,对 SOCKS5 需要另写连接器。界面里会明确提示,而不是悄悄按 HTTP 路由。
|
|
78
|
+
- **上游若绕过 `globalThis.fetch` 自行建连,则不受影响。** `llm-deepseek` 确认是调用时解析全局 `fetch`;`llm-pi-ai` 走外部 SDK,抓取 `fetch` 的时机未能在源码层面确证。
|
|
79
|
+
- **不支持 PAC**,也不读取系统代理设置。
|
|
80
|
+
- **不改写 `baseURL`** —— 反向代理/中转请直接在 provider 上配置。
|
|
81
|
+
- **代理不可达时**请求照常失败,DSH 既有的重试与错误码语义不变。
|
|
82
|
+
- 代理密码不进 `cordis.patch.yml`:配置里只存凭据引用名,密码本身走 credentials 服务。
|
|
83
|
+
- **代理用户名可用,密码尚未接入。** `passwordRef` 字段已预留并默认留空,UI 上只提供用户名输入框。
|
|
84
|
+
|
|
85
|
+
## 如何验证代理确实生效
|
|
86
|
+
|
|
87
|
+
插件本身不显示出口 IP。可靠的判断方式是在代理端看日志,或临时把端口改成
|
|
88
|
+
一个没人监听的端口——如果请求失败并报 `ECONNREFUSED 127.0.0.1:<该端口>`,
|
|
89
|
+
就说明请求确实是被代理承载的(直连时报错会指向目标站点)。
|
|
90
|
+
|
|
91
|
+
仓库里的 `tests/proxy-routing-proof.test.ts` 就是用这个思路做的:起一个会记录
|
|
92
|
+
连接的假代理,断言「启用的路由收到 1 次连接、未配置的 provider 收到 0 次」。
|
|
93
|
+
|
|
94
|
+
## 开发
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
npm run build # 构建 lib/index.js 与 lib/client.js
|
|
98
|
+
npm test # 69 个用例,跑的是构建产物
|
|
99
|
+
npm run typecheck # 类型检查(仅覆盖 src/)
|
|
100
|
+
npm run verify # 上面三件事,按顺序
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`lib/` 是构建产物,不入库;包上的 `prepare` 脚本会在 `npm publish` 以及从 git
|
|
104
|
+
地址安装时自动重建。直接 `dsh plugin add <克隆目录>` 前请先执行
|
|
105
|
+
`npm install && npm run build`。
|
|
106
|
+
|
|
107
|
+
浏览器半必须输出 client module system 的 lazy-CJS factory 格式(脚本本身只注册工厂,所有副作用都在工厂闭包里执行),`scripts/build.mjs` 负责这件事,格式与 DSH 自带插件一致。
|
|
108
|
+
|
|
109
|
+
## 发布
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
npm run verify # 先确认全绿
|
|
113
|
+
npm version patch # 0.2.0 -> 0.2.1
|
|
114
|
+
npm publish
|
|
115
|
+
git push --follow-tags
|
|
116
|
+
```
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# dsh-provider-proxy bundle layer.
|
|
2
|
+
#
|
|
3
|
+
# Installed automatically when the package is present (package.json declares
|
|
4
|
+
# dsh.bundle.patch). Mounts the Host-side entry that routes the LLM calls of
|
|
5
|
+
# providers the user enabled through their own forward proxy; every other
|
|
6
|
+
# provider — and all other DSH traffic — keeps using the normal network path.
|
|
7
|
+
- insert:
|
|
8
|
+
- id: provider-proxy
|
|
9
|
+
name: 'dsh-provider-proxy'
|
|
10
|
+
# Named explicitly so the settings form has a stable address. An empty
|
|
11
|
+
# `providers` keeps the plugin dormant: nothing is proxied until the user
|
|
12
|
+
# turns a provider on in Settings → Provider 代理.
|
|
13
|
+
config:
|
|
14
|
+
providers: {}
|
package/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Loader compatibility shim.
|
|
2
|
+
//
|
|
3
|
+
// cordis-plugin-loader resolves an entry as "<package>/index.js" by convention
|
|
4
|
+
// and does NOT read package.json `main`/`exports`, so a package installed into
|
|
5
|
+
// a profile must expose this file. The implementation lives in ./lib/index.js.
|
|
6
|
+
export * from './lib/index.js'
|