dsh-provider-proxy 0.4.1 → 0.5.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/README.md CHANGED
@@ -35,19 +35,80 @@ dsh plugin --profile web add dsh-provider-proxy
35
35
  | 字段 | 说明 |
36
36
  | --- | --- |
37
37
  | 开关 | 该 provider 是否走代理 |
38
+ | 使用默认代理 | 勾选后本卡片的传输字段整体取自页面顶部「默认代理」区块,无需逐个重填 |
38
39
  | 类型 | `HTTP` / `HTTPS` / `SOCKS5` |
39
- | IP / 主机 | 只填主机名或 IP,不要带 `http://` 或路径 |
40
+ | IP / 主机 | 只填 IPv4、IPv6 或 ASCII 域名;不要带协议前缀、端口、路径或凭据。IDN 需先转 punycode |
40
41
  | 端口 | 1–65535 的整数 |
41
42
  | 用户名 | 可选(与密码成对填写) |
42
43
  | 密码 | 可选(与用户名成对填写)。作为代理认证口令发给代理(`HTTP` / `HTTPS` / `SOCKS5` 均支持) |
43
44
 
45
+ 同一个代理要给多张卡片用时,只在页面顶部的 **默认代理** 区块填一次,再在每张卡片勾上 **使用默认代理**。
46
+ 勾上之后该卡片的传输字段(类型 / 主机 / 端口 / 用户名 / 密码)**整体**取自默认区块,卡片不再显示这些输入框,
47
+ 改为显示它实际会用的地址——所以不会出现「填了一半、另一半不知道从哪来」的模糊状态。目标域名规则与自检目标
48
+ 始终是卡片自己的。默认区块本身不可用时(未填地址、地址不是合法 IPv4/IPv6/域名、端口非法、凭据只填了一半),
49
+ 勾了继承的卡片会被拒绝并说明原因(列在「连接诊断」里),卡片上也直接显示这条原因;默认块的「配置完整」
50
+ 状态与批量按钮用的是同一个判定,所以不会出现「亮着绿灯却一张卡也用不上」。
51
+
52
+ **端点输入不随开关变化**:关闭的卡片照样可以填写、也可以测试。这样就能**先验证再启用**,避开了
53
+ 「必须先打开代理才能测,而配置是坏的」这个窗口。
54
+
55
+ 卡片头上的状态只描述**路由**:
56
+
57
+ | 状态 | 含义 |
58
+ | --- | --- |
59
+ | 配置被拒 | 已启用,但端点 / 凭据不完整或 Host 无法建立路由。该 provider 的请求**不会经过本插件代理**;原因直接显示在卡片内,也列在「连接诊断」里 |
60
+ | 走代理 | 已启用且配置可建立路由。单次请求仍可能因目标域名规则直连;它不代表代理**可达**,连通性看「测试」与「最近调用」 |
61
+ | 直连 | 未启用,流量走宿主原有路径 |
62
+
63
+ Host catalog 是否列出某个 provider(`live`)不影响已保存路由的卡片状态;它只决定**未配置**的 provider 是否出现在列表里。
64
+
65
+ 页面上的几处便利:
66
+
67
+ - **过滤框**:provider 多时按 id 或名称筛掉不必看的卡片。它纯粹是视图,不写任何设置。
68
+ - **粘贴完整代理 URL**:把 `socks5://user:pass@host:1080` 粘进任一「IP / 主机」输入框,会自动拆成
69
+ 协议 / 主机 / 端口 / 用户名 / 密码,并且是**一次**写入(不是五个字段各写一次)。显式写出的 `:80` / `:443`
70
+ 会保留;完全没写端口时保持「未配置」,不会自动补默认端口。
71
+ - **设为默认代理**(卡片上,仅在该卡片有自己的端点时出现):把它当前的端点一次性提升为顶部的默认端点。
72
+ - **所有 provider 使用默认代理**(默认区块里):一条批量写把每张卡片都指向默认端点;默认端点不可用时按钮禁用。
73
+ - **最近一次调用**:卡片头上会显示「最近成功 3 分钟前」,失败时显示「最近失败 ECONNREFUSED」。
74
+ 命中计数只说明这条路由被用过,这一行说明它**现在是否还工作**。已启用但还没代理过请求的路由显示
75
+ 「尚无代理记录」;**测试按钮不计入**——它证明的是「代理这一跳通」,不是「这条路由承载过真实流量」,两者混在一起会让这一行的含义不可信。
76
+ 「最近成功」的口径是**拿到了 <500 的响应**,而不是「整个流跑完」——流式中途被切断仍会记为成功,
77
+ 因为成功判据在响应头到达时就已成立(流式转发不能靠消费 body 来判断)。
78
+ 「最近调用」的时间取请求**发起**时刻;「最近成功 / 失败」的结果与时间来自最近一次**结束**的请求——
79
+ 两者分开记录,并发调用下不会把一次请求的发起时间配到另一次的结果上。
80
+
81
+ 页面顶部与顶层还有三项整页设置(写在 `cordis.patch.yml`,也可以直接编辑):
82
+
83
+ | 顶层设置 | 默认 | 说明 |
84
+ | --- | --- | --- |
85
+ | `defaultProxy` | 未配置 | 供勾选「使用默认代理」的卡片复用的端点 |
86
+ | `localDirect` | `true` | 本机 / 内网目标是否直连(见「目标域名规则」一节) |
87
+ | `testUrl` | `https://api.ipify.org?format=json` | 自检的最后兜底目标;可写多个(逗号或空格分隔,上限 5 个),按序回退 |
88
+
44
89
  列表只包含**已激活**的 provider(`llm.listProviders()` 能列出的那些)。仅在配置里声明、
45
90
  但没有实际注册的可配置 provider 不显示——它的请求根本不会被发出去。
46
- 已经在 `providers` 里配过代理的条目即使暂时不激活也仍然保留,避免已保存的设置悄悄消失。
91
+ 已经在 `providers` 里配过代理的条目即使暂时不激活也仍然保留,避免已保存的设置悄悄消失;
92
+ catalog 的 `live` 只影响未配置 provider 是否出现在列表里,不参与已保存卡片的状态判定。
47
93
 
48
94
  保存立即对新请求生效,不需要重启;正在进行中的流会继续用它启动时的那条连接。
49
95
 
50
- 每张卡片在**该 provider 已启用代理**时还有一个 **测试** 按钮:点它会让 Host 通过**该 provider 自己的代理**发一次请求,回报延迟与(探测目标返回时)出口 IP。探测目标默认是 `https://api.ipify.org?format=json`,可用顶层设置 `testUrl` 换成自建的回显服务。代理不可达时它会明确失败,而不是假通过。延迟数值按快慢着色——≤500 ms 绿、≤1500 ms 黄、更慢红——但数值始终照常显示,颜色只是辅助。
96
+ 每张卡片还有一个 **测试** 按钮:点它会让 Host 通过**该 provider 自己的代理**发一次请求,回报延迟与(探测目标返回时)出口 IP。只要该卡片有完整端点它就会出现——**不必先启用**;未启用的检查会标注为「未启用,仅测试」。代理不可达时它会明确失败,而不是假通过。结果与当前配置绑定:改端点 / 凭据 / 自检目标 / 域名规则后旧结果会立即消失;点「测试」前会先把防抖中的设置写入,Host 测的是刚填的值。
97
+
98
+ **目标与判定口径**:目标按三级取用——卡片的 `自检目标` → 该 provider 的真实上游(见下一节)→ 顶层 `testUrl`(可写多个候选,按序回退,命中任一即停;`407` 会提前终止,因为那是代理的问题)。结果按状态码分类:
99
+
100
+ | 状态 | 含义 |
101
+ | --- | --- |
102
+ | `407` | 代理要求认证 |
103
+ | `2xx` / `4xx`(非 407) | **已达上游,链路通**。真实上游对一次裸 `GET` 正常就是回 401/404,那正是「通了」的证据 |
104
+ | `5xx` | 疑似代理或上游故障。HTTP 代理够不到上游时会**自己**回 502/503/504 |
105
+ | 抛出(`ECONNREFUSED`、超时等) | 按失败原因分类,不会假通过 |
106
+
107
+ 延迟数值按快慢着色——≤500 ms 绿、≤1500 ms 黄、更慢红——但数值始终照常显示,颜色只是辅助。
108
+
109
+ **自检遵循与真实流量相同的代理 / 直连判定。** 目标命中本机 / 内网地址、`bypassHosts`,或落在 `onlyHosts` 之外时,自检与路由一样**直连**——不把一个到不了该目标的请求塞给代理,也就不会把一条健康路由报成「代理坏了」。直连成功时卡片会标注「· 直连(未经过代理)」,直连失败则报「目标不可达」而非指向从未使用的代理。
110
+
111
+ 卡片的「高级」里还有一个 `自检目标`(`testTarget`):留空时先用该 provider **适配器发布的上游地址**(Host 经 `ctx.settings` 按该 provider 的 settings 路径读 `baseURL`,取不到再退回顶层 `testUrl`);填写后该卡片一律请求这个地址。它只被自检读取,路由本身不受影响;填错了只会让一次检查失败,不会拒绝该路由。
51
112
 
52
113
  卡片里还有一个折叠的 **高级:目标域名规则**,按**目标主机**决定是否走代理:
53
114
 
@@ -56,7 +117,11 @@ dsh plugin --profile web add dsh-provider-proxy
56
117
  | `仅走代理`(`onlyHosts`) | 逗号或空格分隔。非空时,只有匹配这些主机的请求走代理,其余直连。 |
57
118
  | `直连`(`bypassHosts`) | 逗号或空格分隔。匹配这些主机的请求直连,优先于 `onlyHosts`。 |
58
119
 
59
- 两者都按后缀匹配(`example.com` 覆盖 `api.example.com`),`*` 表示全部,大小写不敏感。**默认是 fail-closed**:启用了代理的路由默认走代理,只有「`bypassHosts` 命中」与「`onlyHosts` 非空且未命中」两种显式例外才直连;解析不出目标主机时**仍走代理**,避免畸形 URL 把带凭据的请求悄悄直连出去。
120
+ 两者都按后缀匹配(`example.com` 覆盖 `api.example.com`),`*` 表示全部,大小写不敏感。**默认是 fail-closed**:启用了代理的路由默认走代理,只有三种显式例外才直连——「目标命中本机 / 内网地址」「`bypassHosts` 命中」「`onlyHosts` 非空且未命中」;解析不出目标主机时**仍走代理**,避免畸形 URL 把带凭据的请求悄悄直连出去。
121
+
122
+ **指向本机或内网的请求默认直连。** 目标主机命中 `localhost`(含子域)、`127.0.0.0/8`、`::1`、`10.0.0.0/8`、`172.16.0.0/12`、`192.168.0.0/16`、`169.254.0.0/16` 时不经代理。这类目标通常是跑在同一台机器上的中转(LiteLLM、Ollama、本地 mock),正向代理根本到不了它,硬走代理只会失败,而且失败看起来像「代理坏了」。顶层 `localDirect: false` 可关掉这条(例如代理本身就是目标)。
123
+
124
+ 带协议前缀(`https://…`)或路径(`example.com/v1`)的条目永远匹配不上——规则只认主机名。这类条目会在输入框下以错误色标出,因为后果是静默的:`onlyHosts` 里的死条目会让 fail-closed 把流量直连出去,而用户以为已经走代理。
60
125
 
61
126
  当组成里存在 web 服务时,列表还会多出一张 **Web 搜索** 卡片(保留 id `web-search`,字段与其它卡片完全相同,也支持自检、计数与域名规则)。它承载 `web_search` 工具的流量;不启用它,搜索保持直连。
62
127
 
@@ -89,7 +154,8 @@ dsh plugin --profile web add dsh-provider-proxy
89
154
  ## 已知限制
90
155
 
91
156
  - **SOCKS5 由 `undici` 内置的 `Socks5ProxyAgent` 实现**(RFC 1928 / RFC 1929),API 稳定性标记为 *Experimental*;`undici` 若在未来移除,需要同步调整。
92
- - **代理认证字段成对填写:用户名与密码要么都填,要么都不填。** 只填任一项都会被拒绝并给出提示。SOCKS5 侧的依据是 `Socks5ProxyAgent` 仅在两者齐备时才发起认证——只填用户名会静默广告无认证,只填密码则密码根本不会被发送(对开放代理能用,对要求认证的代理必然失败,且失败原因看起来像密码错误);HTTP/HTTPS 侧基本认证本可只发用户名(`alice:`,空密码),这里仍按成对拒绝:半填的凭据几乎总是漏填,三种传输共用一条规则比「哪种传输能承载哪种组合」更好记。代价如实写明——凭据真的使用空密码的代理无法在这里配置。不合规则的配置会被拒绝,原因列在设置页的「连接诊断」面板里(该路由的「测试」也会返回同一条),而不是被静默忽略——否则在强制认证的代理端,报错会指向代理而真正原因是设置缺项。
157
+ - **代理地址只接受 IPv4、IPv6 或 ASCII DNS 域名。** IDN 需先转 punycode;下划线、纯数字单标签(如 `127`),以及把端口写进主机字段(`127.0.0.1:7890`)都会被拒绝。粘贴完整 URL 时显式 `:80` / `:443` 会保留,未写端口仍保持「未配置」。旧配置里不合规的主机名升级后会显示「配置被拒」,需要改成合法地址。
158
+ - **代理认证字段成对填写:用户名与密码要么都填,要么都不填。** 只填任一项都会被拒绝并给出提示;用户名在配对前会 `trim()`,纯空白的用户名视为未填。SOCKS5 侧的依据是 `Socks5ProxyAgent` 仅在两者齐备时才发起认证——只填用户名会静默广告无认证,只填密码则密码根本不会被发送(对开放代理能用,对要求认证的代理必然失败,且失败原因看起来像密码错误);HTTP/HTTPS 侧基本认证本可只发用户名(`alice:`,空密码),这里仍按成对拒绝:半填的凭据几乎总是漏填,三种传输共用一条规则比「哪种传输能承载哪种组合」更好记。代价如实写明——凭据真的使用空密码的代理无法在这里配置。不合规则的配置会被拒绝,原因列在设置页的「连接诊断」面板里(该路由的「测试」也会返回同一条),而不是被静默忽略——否则在强制认证的代理端,报错会指向代理而真正原因是设置缺项。
93
159
  - **SOCKS5 不支持 `socks://` 别名与 SOCKS5 over TLS**(`proxyTls`)。
94
160
  - **`bypassHosts` 命中时由本机解析并连接目标**(DNS 不经过代理)。SOCKS5 下这一点后果最明显:命中的域名不再由代理端解析,本机 DNS 会暴露该次查询。
95
161
  - **`web_search` 可以走代理,`web_fetch` 不行。** 搜索 provider(`dsh-web-search-deepseek`)用全局 `fetch`,插件通过包裹 `ctx.web.search` 把它送进代理作用域;这条成立的前提是:工具每次调用都重新取 `ctx.web.search`,且 provider 的 `search()` 是 await-to-completion(不是惰性消费的流)。`web_fetch` 则用 `undici` 自带的 `dispatcher`(来自官方的进程级 env 策略),没有 per-provider 注入点,本插件覆盖不到——需要覆盖它请改用 `HTTP_PROXY` 之类的环境变量。
@@ -98,8 +164,9 @@ dsh plugin --profile web add dsh-provider-proxy
98
164
  - **不支持 PAC**,也不读取系统代理设置。
99
165
  - **不改写 `baseURL`** —— 反向代理/中转请直接在 provider 上配置。
100
166
  - **代理不可达时**请求照常失败,DSH 既有的重试与错误码语义不变。
101
- - **连接自检依赖 `testUrl` 可达。** 默认的回显主机在部分网络下可能不可达,会让自检误报失败;请通过 `testUrl` 换成自建回显服务。自检只接受 `POST`,同一 provider 的多次点击会共享同一次在飞请求,不会被当成外呼放大器。
167
+ - **连接自检的目标可能不可达。** 默认先尝试该 provider 适配器发布的上游地址,取不到才退回顶层 `testUrl`(默认是回显服务,部分网络下不可达,请换自建服务)。自检只接受 `POST`,同一 provider 的在飞请求会合并;**配置未变时**,完成后的 3 秒内重复点击直接返回上次结果(标记 `cached`),一旦改动配置(端口 / 地址 / 凭据 / 自检目标 / 域名规则 / `localDirect` / `testUrl`)即重新外呼。未启用的检查用完即释放临时 dispatcher——不会被当成外呼放大器。
102
168
  - **代理密码以明文随设置写入 `cordis.patch.yml`。** HTTP/HTTPS 的密码会拼进交给 `undici` 的代理 URL(`ProxyAgent` 由此生成 `Proxy-Authorization`),SOCKS5 的密码只作为选项传给 `Socks5ProxyAgent`;两者都不会出现在日志或诊断里的代理 URL——那里只显示不含凭据的 origin。
169
+ - **设置页的两个端点没有自己的鉴权。** `/dsh-provider-proxy/providers`(provider 列表、计数、被拒原因)与 `/dsh-provider-proxy/test` 的访问边界完全由 DSH 的 webServer 决定。webServer 只绑定本机回环时没有顾虑;若它绑定到非 loopback 地址,同网络的其它主机可以读到这些信息并触发自检外呼(自检有在飞合并、8 KiB 请求体上限与 3 秒冷却,但仍是可触发的外呼)。
103
170
 
104
171
  ## 如何验证代理确实生效
105
172
 
@@ -114,7 +181,7 @@ dsh plugin --profile web add dsh-provider-proxy
114
181
 
115
182
  ```sh
116
183
  npm run build # 构建 lib/index.js 与 lib/client.js
117
- npm test # 82 个用例,跑的是构建产物
184
+ npm test # 218 个用例,跑的是构建产物
118
185
  npm run typecheck # 类型检查(仅覆盖 src/)
119
186
  npm run verify # 上面三件事,按顺序
120
187
  ```