dsh-model-proxy-switch 0.0.0-stage → 1.0.0

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.
Files changed (220) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +76 -2
  3. package/client.js +473 -0
  4. package/cordis.patch.yml +7 -0
  5. package/icon.svg +13 -0
  6. package/index.js +341 -0
  7. package/locale/en.json +25 -0
  8. package/locale/zh.json +25 -0
  9. package/package.json +55 -4
  10. package/vendor/http-proxy.js +542 -0
  11. package/vendor/node_modules/undici/LICENSE +21 -0
  12. package/vendor/node_modules/undici/README.md +741 -0
  13. package/vendor/node_modules/undici/docs/docs/api/Agent.md +84 -0
  14. package/vendor/node_modules/undici/docs/docs/api/BalancedPool.md +99 -0
  15. package/vendor/node_modules/undici/docs/docs/api/CacheStorage.md +30 -0
  16. package/vendor/node_modules/undici/docs/docs/api/CacheStore.md +164 -0
  17. package/vendor/node_modules/undici/docs/docs/api/Client.md +290 -0
  18. package/vendor/node_modules/undici/docs/docs/api/ClientStats.md +27 -0
  19. package/vendor/node_modules/undici/docs/docs/api/Connector.md +115 -0
  20. package/vendor/node_modules/undici/docs/docs/api/ContentType.md +57 -0
  21. package/vendor/node_modules/undici/docs/docs/api/Cookies.md +128 -0
  22. package/vendor/node_modules/undici/docs/docs/api/Debug.md +62 -0
  23. package/vendor/node_modules/undici/docs/docs/api/DiagnosticsChannel.md +317 -0
  24. package/vendor/node_modules/undici/docs/docs/api/Dispatcher.md +1393 -0
  25. package/vendor/node_modules/undici/docs/docs/api/EnvHttpProxyAgent.md +159 -0
  26. package/vendor/node_modules/undici/docs/docs/api/Errors.md +49 -0
  27. package/vendor/node_modules/undici/docs/docs/api/EventSource.md +45 -0
  28. package/vendor/node_modules/undici/docs/docs/api/Fetch.md +60 -0
  29. package/vendor/node_modules/undici/docs/docs/api/GlobalInstallation.md +139 -0
  30. package/vendor/node_modules/undici/docs/docs/api/H2CClient.md +263 -0
  31. package/vendor/node_modules/undici/docs/docs/api/MockAgent.md +603 -0
  32. package/vendor/node_modules/undici/docs/docs/api/MockCallHistory.md +197 -0
  33. package/vendor/node_modules/undici/docs/docs/api/MockCallHistoryLog.md +43 -0
  34. package/vendor/node_modules/undici/docs/docs/api/MockClient.md +81 -0
  35. package/vendor/node_modules/undici/docs/docs/api/MockErrors.md +12 -0
  36. package/vendor/node_modules/undici/docs/docs/api/MockPool.md +555 -0
  37. package/vendor/node_modules/undici/docs/docs/api/Pool.md +84 -0
  38. package/vendor/node_modules/undici/docs/docs/api/PoolStats.md +35 -0
  39. package/vendor/node_modules/undici/docs/docs/api/ProxyAgent.md +229 -0
  40. package/vendor/node_modules/undici/docs/docs/api/RedirectHandler.md +93 -0
  41. package/vendor/node_modules/undici/docs/docs/api/RetryAgent.md +50 -0
  42. package/vendor/node_modules/undici/docs/docs/api/RetryHandler.md +118 -0
  43. package/vendor/node_modules/undici/docs/docs/api/RoundRobinPool.md +145 -0
  44. package/vendor/node_modules/undici/docs/docs/api/SnapshotAgent.md +616 -0
  45. package/vendor/node_modules/undici/docs/docs/api/Socks5ProxyAgent.md +275 -0
  46. package/vendor/node_modules/undici/docs/docs/api/Util.md +25 -0
  47. package/vendor/node_modules/undici/docs/docs/api/WebSocket.md +141 -0
  48. package/vendor/node_modules/undici/docs/docs/api/api-lifecycle.md +91 -0
  49. package/vendor/node_modules/undici/docs/docs/best-practices/client-certificate.md +64 -0
  50. package/vendor/node_modules/undici/docs/docs/best-practices/crawling.md +58 -0
  51. package/vendor/node_modules/undici/docs/docs/best-practices/mocking-request.md +190 -0
  52. package/vendor/node_modules/undici/docs/docs/best-practices/proxy.md +127 -0
  53. package/vendor/node_modules/undici/docs/docs/best-practices/undici-vs-builtin-fetch.md +224 -0
  54. package/vendor/node_modules/undici/docs/docs/best-practices/writing-tests.md +63 -0
  55. package/vendor/node_modules/undici/index-fetch.js +65 -0
  56. package/vendor/node_modules/undici/index.d.ts +3 -0
  57. package/vendor/node_modules/undici/index.js +234 -0
  58. package/vendor/node_modules/undici/lib/api/abort-signal.js +59 -0
  59. package/vendor/node_modules/undici/lib/api/api-connect.js +110 -0
  60. package/vendor/node_modules/undici/lib/api/api-pipeline.js +252 -0
  61. package/vendor/node_modules/undici/lib/api/api-request.js +214 -0
  62. package/vendor/node_modules/undici/lib/api/api-stream.js +209 -0
  63. package/vendor/node_modules/undici/lib/api/api-upgrade.js +111 -0
  64. package/vendor/node_modules/undici/lib/api/index.js +7 -0
  65. package/vendor/node_modules/undici/lib/api/readable.js +580 -0
  66. package/vendor/node_modules/undici/lib/cache/memory-cache-store.js +279 -0
  67. package/vendor/node_modules/undici/lib/cache/sqlite-cache-store.js +467 -0
  68. package/vendor/node_modules/undici/lib/core/connect.js +159 -0
  69. package/vendor/node_modules/undici/lib/core/constants.js +143 -0
  70. package/vendor/node_modules/undici/lib/core/diagnostics.js +227 -0
  71. package/vendor/node_modules/undici/lib/core/errors.js +477 -0
  72. package/vendor/node_modules/undici/lib/core/request.js +485 -0
  73. package/vendor/node_modules/undici/lib/core/socks5-client.js +422 -0
  74. package/vendor/node_modules/undici/lib/core/socks5-utils.js +212 -0
  75. package/vendor/node_modules/undici/lib/core/symbols.js +76 -0
  76. package/vendor/node_modules/undici/lib/core/tree.js +160 -0
  77. package/vendor/node_modules/undici/lib/core/util.js +992 -0
  78. package/vendor/node_modules/undici/lib/dispatcher/agent.js +162 -0
  79. package/vendor/node_modules/undici/lib/dispatcher/balanced-pool.js +221 -0
  80. package/vendor/node_modules/undici/lib/dispatcher/client-h1.js +1744 -0
  81. package/vendor/node_modules/undici/lib/dispatcher/client-h2.js +1056 -0
  82. package/vendor/node_modules/undici/lib/dispatcher/client.js +669 -0
  83. package/vendor/node_modules/undici/lib/dispatcher/dispatcher-base.js +196 -0
  84. package/vendor/node_modules/undici/lib/dispatcher/dispatcher.js +48 -0
  85. package/vendor/node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +146 -0
  86. package/vendor/node_modules/undici/lib/dispatcher/fixed-queue.js +135 -0
  87. package/vendor/node_modules/undici/lib/dispatcher/h2c-client.js +51 -0
  88. package/vendor/node_modules/undici/lib/dispatcher/pool-base.js +214 -0
  89. package/vendor/node_modules/undici/lib/dispatcher/pool.js +118 -0
  90. package/vendor/node_modules/undici/lib/dispatcher/proxy-agent.js +319 -0
  91. package/vendor/node_modules/undici/lib/dispatcher/retry-agent.js +35 -0
  92. package/vendor/node_modules/undici/lib/dispatcher/round-robin-pool.js +137 -0
  93. package/vendor/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +260 -0
  94. package/vendor/node_modules/undici/lib/encoding/index.js +33 -0
  95. package/vendor/node_modules/undici/lib/global.js +59 -0
  96. package/vendor/node_modules/undici/lib/handler/cache-handler.js +824 -0
  97. package/vendor/node_modules/undici/lib/handler/cache-revalidation-handler.js +124 -0
  98. package/vendor/node_modules/undici/lib/handler/decorator-handler.js +67 -0
  99. package/vendor/node_modules/undici/lib/handler/deduplication-handler.js +504 -0
  100. package/vendor/node_modules/undici/lib/handler/redirect-handler.js +245 -0
  101. package/vendor/node_modules/undici/lib/handler/retry-handler.js +504 -0
  102. package/vendor/node_modules/undici/lib/handler/unwrap-handler.js +106 -0
  103. package/vendor/node_modules/undici/lib/handler/wrap-handler.js +105 -0
  104. package/vendor/node_modules/undici/lib/interceptor/cache.js +621 -0
  105. package/vendor/node_modules/undici/lib/interceptor/decompress.js +561 -0
  106. package/vendor/node_modules/undici/lib/interceptor/deduplicate.js +117 -0
  107. package/vendor/node_modules/undici/lib/interceptor/dns.js +571 -0
  108. package/vendor/node_modules/undici/lib/interceptor/dump.js +99 -0
  109. package/vendor/node_modules/undici/lib/interceptor/redirect.js +21 -0
  110. package/vendor/node_modules/undici/lib/interceptor/response-error.js +95 -0
  111. package/vendor/node_modules/undici/lib/interceptor/retry.js +19 -0
  112. package/vendor/node_modules/undici/lib/llhttp/.gitkeep +0 -0
  113. package/vendor/node_modules/undici/lib/llhttp/constants.d.ts +195 -0
  114. package/vendor/node_modules/undici/lib/llhttp/constants.js +531 -0
  115. package/vendor/node_modules/undici/lib/llhttp/llhttp-wasm.js +15 -0
  116. package/vendor/node_modules/undici/lib/llhttp/llhttp_simd-wasm.js +15 -0
  117. package/vendor/node_modules/undici/lib/llhttp/utils.d.ts +2 -0
  118. package/vendor/node_modules/undici/lib/llhttp/utils.js +12 -0
  119. package/vendor/node_modules/undici/lib/mock/mock-agent.js +232 -0
  120. package/vendor/node_modules/undici/lib/mock/mock-call-history.js +248 -0
  121. package/vendor/node_modules/undici/lib/mock/mock-client.js +68 -0
  122. package/vendor/node_modules/undici/lib/mock/mock-errors.js +29 -0
  123. package/vendor/node_modules/undici/lib/mock/mock-interceptor.js +209 -0
  124. package/vendor/node_modules/undici/lib/mock/mock-pool.js +68 -0
  125. package/vendor/node_modules/undici/lib/mock/mock-symbols.js +32 -0
  126. package/vendor/node_modules/undici/lib/mock/mock-utils.js +486 -0
  127. package/vendor/node_modules/undici/lib/mock/pending-interceptors-formatter.js +43 -0
  128. package/vendor/node_modules/undici/lib/mock/snapshot-agent.js +353 -0
  129. package/vendor/node_modules/undici/lib/mock/snapshot-recorder.js +588 -0
  130. package/vendor/node_modules/undici/lib/mock/snapshot-utils.js +158 -0
  131. package/vendor/node_modules/undici/lib/util/cache.js +676 -0
  132. package/vendor/node_modules/undici/lib/util/date.js +670 -0
  133. package/vendor/node_modules/undici/lib/util/promise.js +28 -0
  134. package/vendor/node_modules/undici/lib/util/runtime-features.js +124 -0
  135. package/vendor/node_modules/undici/lib/util/stats.js +32 -0
  136. package/vendor/node_modules/undici/lib/util/timers.js +425 -0
  137. package/vendor/node_modules/undici/lib/web/cache/cache.js +864 -0
  138. package/vendor/node_modules/undici/lib/web/cache/cachestorage.js +152 -0
  139. package/vendor/node_modules/undici/lib/web/cache/util.js +45 -0
  140. package/vendor/node_modules/undici/lib/web/cookies/constants.js +12 -0
  141. package/vendor/node_modules/undici/lib/web/cookies/index.js +201 -0
  142. package/vendor/node_modules/undici/lib/web/cookies/parse.js +315 -0
  143. package/vendor/node_modules/undici/lib/web/cookies/util.js +371 -0
  144. package/vendor/node_modules/undici/lib/web/eventsource/eventsource-stream.js +575 -0
  145. package/vendor/node_modules/undici/lib/web/eventsource/eventsource.js +504 -0
  146. package/vendor/node_modules/undici/lib/web/eventsource/util.js +29 -0
  147. package/vendor/node_modules/undici/lib/web/fetch/LICENSE +21 -0
  148. package/vendor/node_modules/undici/lib/web/fetch/body.js +509 -0
  149. package/vendor/node_modules/undici/lib/web/fetch/constants.js +131 -0
  150. package/vendor/node_modules/undici/lib/web/fetch/data-url.js +596 -0
  151. package/vendor/node_modules/undici/lib/web/fetch/formdata-parser.js +586 -0
  152. package/vendor/node_modules/undici/lib/web/fetch/formdata.js +259 -0
  153. package/vendor/node_modules/undici/lib/web/fetch/global.js +40 -0
  154. package/vendor/node_modules/undici/lib/web/fetch/headers.js +719 -0
  155. package/vendor/node_modules/undici/lib/web/fetch/index.js +2413 -0
  156. package/vendor/node_modules/undici/lib/web/fetch/request.js +1115 -0
  157. package/vendor/node_modules/undici/lib/web/fetch/response.js +641 -0
  158. package/vendor/node_modules/undici/lib/web/fetch/util.js +1522 -0
  159. package/vendor/node_modules/undici/lib/web/infra/index.js +243 -0
  160. package/vendor/node_modules/undici/lib/web/subresource-integrity/Readme.md +9 -0
  161. package/vendor/node_modules/undici/lib/web/subresource-integrity/subresource-integrity.js +307 -0
  162. package/vendor/node_modules/undici/lib/web/webidl/index.js +1006 -0
  163. package/vendor/node_modules/undici/lib/web/websocket/connection.js +339 -0
  164. package/vendor/node_modules/undici/lib/web/websocket/constants.js +126 -0
  165. package/vendor/node_modules/undici/lib/web/websocket/events.js +331 -0
  166. package/vendor/node_modules/undici/lib/web/websocket/frame.js +133 -0
  167. package/vendor/node_modules/undici/lib/web/websocket/permessage-deflate.js +105 -0
  168. package/vendor/node_modules/undici/lib/web/websocket/receiver.js +520 -0
  169. package/vendor/node_modules/undici/lib/web/websocket/sender.js +109 -0
  170. package/vendor/node_modules/undici/lib/web/websocket/stream/websocketerror.js +104 -0
  171. package/vendor/node_modules/undici/lib/web/websocket/stream/websocketstream.js +496 -0
  172. package/vendor/node_modules/undici/lib/web/websocket/util.js +347 -0
  173. package/vendor/node_modules/undici/lib/web/websocket/websocket.js +758 -0
  174. package/vendor/node_modules/undici/package.json +152 -0
  175. package/vendor/node_modules/undici/scripts/strip-comments.js +10 -0
  176. package/vendor/node_modules/undici/types/README.md +6 -0
  177. package/vendor/node_modules/undici/types/agent.d.ts +32 -0
  178. package/vendor/node_modules/undici/types/api.d.ts +43 -0
  179. package/vendor/node_modules/undici/types/balanced-pool.d.ts +30 -0
  180. package/vendor/node_modules/undici/types/cache-interceptor.d.ts +179 -0
  181. package/vendor/node_modules/undici/types/cache.d.ts +36 -0
  182. package/vendor/node_modules/undici/types/client-stats.d.ts +15 -0
  183. package/vendor/node_modules/undici/types/client.d.ts +153 -0
  184. package/vendor/node_modules/undici/types/connector.d.ts +36 -0
  185. package/vendor/node_modules/undici/types/content-type.d.ts +21 -0
  186. package/vendor/node_modules/undici/types/cookies.d.ts +30 -0
  187. package/vendor/node_modules/undici/types/diagnostics-channel.d.ts +74 -0
  188. package/vendor/node_modules/undici/types/dispatcher.d.ts +275 -0
  189. package/vendor/node_modules/undici/types/env-http-proxy-agent.d.ts +22 -0
  190. package/vendor/node_modules/undici/types/errors.d.ts +177 -0
  191. package/vendor/node_modules/undici/types/eventsource.d.ts +66 -0
  192. package/vendor/node_modules/undici/types/fetch.d.ts +231 -0
  193. package/vendor/node_modules/undici/types/formdata.d.ts +114 -0
  194. package/vendor/node_modules/undici/types/global-dispatcher.d.ts +9 -0
  195. package/vendor/node_modules/undici/types/global-origin.d.ts +7 -0
  196. package/vendor/node_modules/undici/types/h2c-client.d.ts +73 -0
  197. package/vendor/node_modules/undici/types/handlers.d.ts +14 -0
  198. package/vendor/node_modules/undici/types/header.d.ts +160 -0
  199. package/vendor/node_modules/undici/types/index.d.ts +91 -0
  200. package/vendor/node_modules/undici/types/interceptors.d.ts +82 -0
  201. package/vendor/node_modules/undici/types/mock-agent.d.ts +68 -0
  202. package/vendor/node_modules/undici/types/mock-call-history.d.ts +111 -0
  203. package/vendor/node_modules/undici/types/mock-client.d.ts +27 -0
  204. package/vendor/node_modules/undici/types/mock-errors.d.ts +12 -0
  205. package/vendor/node_modules/undici/types/mock-interceptor.d.ts +94 -0
  206. package/vendor/node_modules/undici/types/mock-pool.d.ts +27 -0
  207. package/vendor/node_modules/undici/types/patch.d.ts +29 -0
  208. package/vendor/node_modules/undici/types/pool-stats.d.ts +19 -0
  209. package/vendor/node_modules/undici/types/pool.d.ts +41 -0
  210. package/vendor/node_modules/undici/types/proxy-agent.d.ts +29 -0
  211. package/vendor/node_modules/undici/types/readable.d.ts +68 -0
  212. package/vendor/node_modules/undici/types/retry-agent.d.ts +8 -0
  213. package/vendor/node_modules/undici/types/retry-handler.d.ts +125 -0
  214. package/vendor/node_modules/undici/types/round-robin-pool.d.ts +41 -0
  215. package/vendor/node_modules/undici/types/snapshot-agent.d.ts +109 -0
  216. package/vendor/node_modules/undici/types/socks5-proxy-agent.d.ts +25 -0
  217. package/vendor/node_modules/undici/types/util.d.ts +18 -0
  218. package/vendor/node_modules/undici/types/utility.d.ts +7 -0
  219. package/vendor/node_modules/undici/types/webidl.d.ts +347 -0
  220. package/vendor/node_modules/undici/types/websocket.d.ts +188 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-model-proxy-switch 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,77 @@
1
- # Temporary Holding Version
1
+ # dsh-model-proxy-switch
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ English | [中文](#中文)
4
+
5
+ A sidebar switch for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh`) that routes outbound requests — model API calls, model-list discovery, web search, HTTP MCP — through a local HTTP proxy, and restores direct connections when switched off.
6
+
7
+ ![icon](./icon.svg)
8
+
9
+ ## Why
10
+
11
+ Some model relays are unreachable from a direct connection (the TLS handshake is reset), while the operating system's proxy reaches them fine. The harness resolves its proxy policy once at launch from the launch environment and never re-reads it, so flipping the OS proxy later does nothing. This plugin installs the same policy at runtime, on demand, as a toggle.
12
+
13
+ ## Features
14
+
15
+ - **One switch in the sidebar** — a new "Proxy" entry next to Plugins, opening a small panel with the toggle, the active proxy address, and where it came from.
16
+ - **Follows the system proxy by default** (`proxy: auto`) — on Windows it reads the `Internet Settings` registry values, so swapping proxy software or ports needs no edit. A fixed URL can be set in the plugin config instead.
17
+ - **Optional per-host allowlist** — leave it empty to route every external address, or list domains (comma-separated, subdomains included) so only they go through the proxy. Loopback always stays direct.
18
+ - **No restart, no `.env`** — toggling installs/disposes the process-wide dispatcher live; the choice persists across restarts in `~/.dsh/model-proxy.json`.
19
+ - **Self-contained** — the proxy installer and its `undici` dependency are vendored, so the package has no runtime dependencies.
20
+
21
+ ## Install
22
+
23
+ ```sh
24
+ dsh plugin --profile <profile> add dsh-model-proxy-switch
25
+ ```
26
+
27
+ or from the Plugins page: **Add plugin → package name `dsh-model-proxy-switch`**, a Git URL, a tarball, or a local directory.
28
+
29
+ ## Usage
30
+
31
+ 1. Open **Proxy** in the sidebar.
32
+ 2. Flip **Use proxy** on. The address line shows the detected proxy and its source (`from system settings` / `from registry` / `from plugin config`).
33
+ 3. Optionally fill **Only proxy these hosts** (e.g. `api.example.com`) and save — everything else then stays direct.
34
+
35
+ Configuration (bundle `cordis.patch.yml` or the row config): `proxy` (`auto` or an `http(s)://` URL), `bypass` (extra direct hosts), `hosts` (initial allowlist).
36
+
37
+ Privacy: the plugin routes traffic you ask it to route and writes one small state file in the harness home. It collects nothing and calls nowhere on its own.
38
+
39
+ ---
40
+
41
+ ## 中文
42
+
43
+ 一个 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`dsh`)的侧栏开关插件:开启后,模型 API、获取模型列表、网页搜索、MCP 等出网请求改走本地 HTTP 代理;关闭即恢复直连。
44
+
45
+ ### 解决什么问题
46
+
47
+ 有些模型中转站直连会被重置(TLS 握手被掐),但系统代理能通。而 DSH 只在启动时读一次代理环境变量,之后改系统代理没有用。这个插件把同样的代理策略做成了**运行时可开关**的按钮。
48
+
49
+ ### 功能
50
+
51
+ - **侧栏一个开关**:「插件」旁边多出「代理」入口,面板里有开关、当前代理地址和来源标注。
52
+ - **默认跟随系统代理**(`proxy: auto`):Windows 下读注册表的 Internet Settings,换代理软件或端口不用改插件;也可以在配置里写死地址。
53
+ - **域名白名单(可选)**:留空表示所有外部地址都走代理;填入域名(逗号分隔,含子域名)后只有这些域名走代理。本机回环永远直连。
54
+ - **不用重启、不用改 .env**:切换即时装卸全局 dispatcher,选择持久化在 `~/.dsh/model-proxy.json`。
55
+ - **零依赖**:代理安装器和 `undici` 已内置于包内。
56
+
57
+ ### 安装
58
+
59
+ ```sh
60
+ dsh plugin --profile <profile> add dsh-model-proxy-switch
61
+ ```
62
+
63
+ 或在插件页 **添加插件**,填包名 `dsh-model-proxy-switch`、Git 地址、压缩包或本地目录。
64
+
65
+ ### 使用
66
+
67
+ 1. 打开侧栏「代理」。
68
+ 2. 打开「使用代理」,地址行会显示检测到的代理及来源(跟随系统代理 / 注册表记录 / 插件配置)。
69
+ 3. 可选:在「只对这些域名走代理」里填域名(如 `api.wufannao.cc.cd`)并保存,其余流量保持直连。
70
+
71
+ 配置项(bundle 的 `cordis.patch.yml` 或行配置):`proxy`(`auto` 或 `http(s)://` 地址)、`bypass`(额外直连域名)、`hosts`(初始白名单)。
72
+
73
+ 隐私:插件只按你的要求转发流量,并在 harness home 写一个很小的状态文件;不收集、不上报任何数据。
74
+
75
+ ## License
76
+
77
+ MIT
package/client.js ADDED
@@ -0,0 +1,473 @@
1
+ window.__ModuleLoader__.load({
2
+ id: 'dsh-model-proxy-switch',
3
+ factory(require) {
4
+ const React = require('react')
5
+ const h = React.createElement
6
+
7
+ const PACKAGE = 'dsh-model-proxy-switch'
8
+ const PANEL_ID = 'model-proxy'
9
+ const NS = 'modelProxy'
10
+
11
+ const codec = (typeSymbol, parse) => ({
12
+ mode: 'strict',
13
+ typeSymbol: PACKAGE + '#' + typeSymbol,
14
+ create: () => ({ parse }),
15
+ })
16
+ const booleanValue = codec('Boolean', (value) => {
17
+ if (typeof value !== 'boolean') throw new Error('Expected a boolean, got ' + typeof value)
18
+ return value
19
+ })
20
+ const hostsValue = codec('Hosts', (value) => {
21
+ if (typeof value !== 'string') throw new Error('Expected a string, got ' + typeof value)
22
+ return value
23
+ })
24
+ const jsonResult = codec('ModelProxyStatus', (value) => value)
25
+
26
+ const methodDescriptor = (method, parameters) => ({
27
+ id: PACKAGE + '#' + NS + '/' + method,
28
+ service: NS,
29
+ namespace: NS,
30
+ method,
31
+ invocation: { kind: 'direct' },
32
+ result: jsonResult,
33
+ parameters,
34
+ })
35
+ const jsonParameter = (name, parameterCodec) => ({ name, wire: name, source: 'json', codec: parameterCodec })
36
+
37
+ const REMOTE = {
38
+ package: PACKAGE,
39
+ descriptors: [
40
+ methodDescriptor('status', []),
41
+ methodDescriptor('setEnabled', [jsonParameter('enabled', booleanValue)]),
42
+ methodDescriptor('setHosts', [jsonParameter('hosts', hostsValue)]),
43
+ ],
44
+ }
45
+
46
+ const STRINGS = {
47
+ zh: {
48
+ nav: '代理',
49
+ title: '模型代理',
50
+ summary: '让模型 API、获取模型列表等出网请求走 HTTP 代理;关闭即直连。',
51
+ stateOn: '已开启',
52
+ stateOff: '已关闭',
53
+ label: '使用代理',
54
+ proxy: '代理地址',
55
+ sourceSystem: '跟随系统代理',
56
+ sourceRegistry: '注册表记录(系统代理未开启)',
57
+ sourceConfig: '插件配置',
58
+ sourceNone: '未检测到系统代理',
59
+ hostsLabel: '只对这些域名走代理',
60
+ hostsHint: '逗号分隔,留空表示所有外部地址。例:api.wufannao.cc.cd',
61
+ save: '保存',
62
+ saved: '已保存',
63
+ scope: '开关作用于整个宿主进程的出网请求:获取模型列表、模型调用、网页搜索、MCP 都走同一条链路;本机回环始终直连。',
64
+ saving: '正在切换…',
65
+ readFailed: '读取状态失败',
66
+ writeFailed: '切换失败',
67
+ },
68
+ en: {
69
+ nav: 'Proxy',
70
+ title: 'Model proxy',
71
+ summary: 'Routes model API and model-list requests through an HTTP proxy while on; off means direct.',
72
+ stateOn: 'On',
73
+ stateOff: 'Off',
74
+ label: 'Use proxy',
75
+ proxy: 'Proxy URL',
76
+ sourceSystem: 'from system settings',
77
+ sourceRegistry: 'from registry (system proxy off)',
78
+ sourceConfig: 'from plugin config',
79
+ sourceNone: 'no system proxy detected',
80
+ hostsLabel: 'Only proxy these hosts',
81
+ hostsHint: 'Comma-separated; empty means every external address. Example: api.wufannao.cc.cd',
82
+ save: 'Save',
83
+ saved: 'Saved',
84
+ scope: 'Process-wide switch: model discovery, model calls, web search and MCP share this route. Loopback always stays direct.',
85
+ saving: 'Switching…',
86
+ readFailed: 'Could not read the state',
87
+ writeFailed: 'Switch failed',
88
+ },
89
+ }
90
+
91
+ function translator(ctx) {
92
+ let lang = 'en'
93
+ try {
94
+ const raw = String((typeof navigator !== 'undefined' && navigator.language) || 'en').toLowerCase()
95
+ lang = raw.startsWith('zh') ? 'zh' : 'en'
96
+ } catch {}
97
+ return (key) => {
98
+ try {
99
+ const bound = ctx.locale && typeof ctx.locale.bind === 'function' ? ctx.locale.bind(NS) : undefined
100
+ const value = bound ? bound(key) : undefined
101
+ if (typeof value === 'string' && value !== key) return value
102
+ } catch {}
103
+ return (STRINGS[lang] && STRINGS[lang][key]) || key
104
+ }
105
+ }
106
+
107
+ const styles = {
108
+ page: {
109
+ boxSizing: 'border-box',
110
+ height: '100%',
111
+ overflowY: 'auto',
112
+ padding: '28px clamp(24px, 4vw, 48px) 48px',
113
+ },
114
+ inner: { maxWidth: 720, margin: '0 auto', display: 'flex', flexDirection: 'column', gap: 12 },
115
+ title: { margin: 0, fontSize: 18, fontWeight: 600, lineHeight: '26px' },
116
+ summary: { margin: 0, fontSize: 13, lineHeight: '20px', color: 'var(--dsw-alias-label-secondary)' },
117
+ card: {
118
+ marginTop: 8,
119
+ border: '1px solid var(--dsw-alias-border-l2)',
120
+ borderRadius: 12,
121
+ background: 'var(--dsw-alias-bg-base)',
122
+ padding: '4px 16px',
123
+ },
124
+ row: {
125
+ display: 'flex',
126
+ alignItems: 'center',
127
+ justifyContent: 'space-between',
128
+ gap: 16,
129
+ padding: '14px 0',
130
+ borderBottom: '0.5px solid var(--dsw-alias-border-l2)',
131
+ },
132
+ rowLast: { borderBottom: 'none' },
133
+ label: { fontSize: 13, fontWeight: 500, color: 'var(--dsw-alias-label-primary)' },
134
+ desc: { marginTop: 2, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-secondary)' },
135
+ toggle: (on, busy) => ({
136
+ flex: 'none',
137
+ minWidth: 96,
138
+ height: 30,
139
+ padding: '0 14px',
140
+ borderRadius: 15,
141
+ border: '1px solid ' + (on ? 'transparent' : 'var(--dsw-alias-border-l2)'),
142
+ background: on ? 'var(--dsw-alias-state-business-primary, #4176e6)' : 'var(--dsw-alias-bg-layer-2)',
143
+ color: on ? '#fff' : 'var(--dsw-alias-label-primary)',
144
+ fontSize: 13,
145
+ fontWeight: 500,
146
+ cursor: busy ? 'wait' : 'pointer',
147
+ opacity: busy ? 0.6 : 1,
148
+ }),
149
+ hostsInput: {
150
+ boxSizing: 'border-box',
151
+ width: '100%',
152
+ height: 32,
153
+ marginTop: 8,
154
+ padding: '0 10px',
155
+ border: '1px solid var(--dsw-alias-input-border, var(--dsw-alias-border-l2))',
156
+ borderRadius: 8,
157
+ background: 'var(--dsw-alias-input-bg, var(--dsw-alias-bg-layer-2))',
158
+ color: 'inherit',
159
+ font: 'inherit',
160
+ fontSize: 13,
161
+ },
162
+ saveButton: (busy) => ({
163
+ flex: 'none',
164
+ height: 30,
165
+ padding: '0 14px',
166
+ borderRadius: 8,
167
+ border: '1px solid var(--dsw-alias-border-l2)',
168
+ background: 'var(--dsw-alias-bg-layer-2)',
169
+ color: 'var(--dsw-alias-label-primary)',
170
+ fontSize: 13,
171
+ cursor: busy ? 'wait' : 'pointer',
172
+ opacity: busy ? 0.6 : 1,
173
+ }),
174
+ error: { margin: '4px 0 0', fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-error-primary)' },
175
+ }
176
+
177
+ function NavIcon() {
178
+ return h(
179
+ 'svg',
180
+ { viewBox: '0 0 16 16', width: 16, height: 16, 'aria-hidden': true, focusable: 'false' },
181
+ h('path', {
182
+ d: 'M2.5 5.5h6a2 2 0 1 0-2-2M13.5 10.5h-6a2 2 0 1 0 2 2',
183
+ fill: 'none',
184
+ stroke: 'currentColor',
185
+ strokeWidth: 1.4,
186
+ strokeLinecap: 'round',
187
+ }),
188
+ h('circle', { cx: 12, cy: 4.5, r: 1.5, fill: 'currentColor' }),
189
+ h('circle', { cx: 4, cy: 11.5, r: 1.5, fill: 'currentColor' }),
190
+ )
191
+ }
192
+
193
+ function sourceText(status, t) {
194
+ if (!status || !status.proxy) {
195
+ return status && status.proxySource === 'none' ? t('sourceNone') : '…'
196
+ }
197
+ const tag =
198
+ status.proxySource === 'system'
199
+ ? t('sourceSystem')
200
+ : status.proxySource === 'registry'
201
+ ? t('sourceRegistry')
202
+ : status.proxySource === 'config'
203
+ ? t('sourceConfig')
204
+ : ''
205
+ return t('proxy') + ':' + status.proxy + (tag ? '(' + tag + ')' : '')
206
+ }
207
+
208
+ function Panel({ remote, t }) {
209
+ const [status, setStatus] = React.useState(null)
210
+ const [busy, setBusy] = React.useState(false)
211
+ const [error, setError] = React.useState('')
212
+ const [hosts, setHosts] = React.useState('')
213
+ const [savedFlash, setSavedFlash] = React.useState(false)
214
+ const hostsDirty = React.useRef(false)
215
+ const loaded = React.useRef(false)
216
+
217
+ const describe = (result, fallback) => {
218
+ const error = result && result.error
219
+ if (error && typeof error === 'object') return error.message || JSON.stringify(error)
220
+ if (typeof error === 'string') return error
221
+ return fallback
222
+ }
223
+
224
+ React.useEffect(() => {
225
+ let alive = true
226
+ const read = () => {
227
+ remote
228
+ .status()
229
+ .then((result) => {
230
+ if (!alive) return
231
+ if (result && result.ok) {
232
+ loaded.current = true
233
+ setStatus(result.value)
234
+ if (!hostsDirty.current) {
235
+ setHosts(result.value && typeof result.value.hosts === 'string' ? result.value.hosts : '')
236
+ }
237
+ } else if (!loaded.current) {
238
+ setError(describe(result, t('readFailed')))
239
+ }
240
+ })
241
+ .catch((cause) => {
242
+ if (alive && !loaded.current) setError((cause && cause.message) || t('readFailed'))
243
+ })
244
+ }
245
+ // No polling: refresh on open, on window focus, and after actions.
246
+ read()
247
+ const onFocus = () => read()
248
+ window.addEventListener('focus', onFocus)
249
+ return () => {
250
+ alive = false
251
+ window.removeEventListener('focus', onFocus)
252
+ }
253
+ }, [remote])
254
+
255
+ const toggle = () => {
256
+ if (!status || busy) return
257
+ setBusy(true)
258
+ setError('')
259
+ remote
260
+ .setEnabled(!status.enabled)
261
+ .then((result) => {
262
+ if (result && result.ok) {
263
+ setStatus(result.value)
264
+ return
265
+ }
266
+ setError(describe(result, t('writeFailed')))
267
+ })
268
+ .catch((cause) => setError((cause && cause.message) || t('writeFailed')))
269
+ .finally(() => setBusy(false))
270
+ }
271
+
272
+ const saveHosts = () => {
273
+ if (!status || busy) return
274
+ setBusy(true)
275
+ setError('')
276
+ remote
277
+ .setHosts(hosts)
278
+ .then((result) => {
279
+ if (result && result.ok) {
280
+ setStatus(result.value)
281
+ hostsDirty.current = false
282
+ setSavedFlash(true)
283
+ setTimeout(() => setSavedFlash(false), 1500)
284
+ return
285
+ }
286
+ setError(describe(result, t('writeFailed')))
287
+ })
288
+ .catch((cause) => setError((cause && cause.message) || t('writeFailed')))
289
+ .finally(() => setBusy(false))
290
+ }
291
+
292
+ const on = !!(status && status.enabled)
293
+ return h(
294
+ 'div',
295
+ { style: styles.page },
296
+ h(
297
+ 'div',
298
+ { style: styles.inner },
299
+ h('h1', { style: styles.title }, t('title')),
300
+ h('p', { style: styles.summary }, t('summary')),
301
+ h(
302
+ 'div',
303
+ { style: styles.card },
304
+ h(
305
+ 'div',
306
+ { style: styles.row },
307
+ h(
308
+ 'div',
309
+ null,
310
+ h('div', { style: styles.label }, t('label')),
311
+ h('div', { style: styles.desc }, sourceText(status, t)),
312
+ ),
313
+ h(
314
+ 'button',
315
+ {
316
+ type: 'button',
317
+ role: 'switch',
318
+ 'aria-checked': on,
319
+ 'aria-label': t('label'),
320
+ disabled: busy || !status,
321
+ style: styles.toggle(on, busy || !status),
322
+ onClick: toggle,
323
+ },
324
+ busy ? t('saving') : on ? t('stateOn') : t('stateOff'),
325
+ ),
326
+ ),
327
+ h(
328
+ 'div',
329
+ { style: styles.row },
330
+ h(
331
+ 'div',
332
+ { style: { flex: 1, minWidth: 0 } },
333
+ h('div', { style: styles.label }, t('hostsLabel')),
334
+ h('div', { style: styles.desc }, t('hostsHint')),
335
+ h('input', {
336
+ style: styles.hostsInput,
337
+ value: hosts,
338
+ placeholder: 'api.example.com, *.example.org',
339
+ disabled: busy || !status,
340
+ onChange: (event) => {
341
+ hostsDirty.current = true
342
+ setHosts(event.target.value)
343
+ },
344
+ onKeyDown: (event) => {
345
+ if (event.key === 'Enter') saveHosts()
346
+ },
347
+ }),
348
+ ),
349
+ h(
350
+ 'button',
351
+ { type: 'button', disabled: busy || !status, style: styles.saveButton(busy || !status), onClick: saveHosts },
352
+ savedFlash ? t('saved') : t('save'),
353
+ ),
354
+ ),
355
+ h('div', { style: styles.rowLast }, h('div', { style: styles.desc }, t('scope'))),
356
+ ),
357
+ error ? h('p', { style: styles.error, role: 'alert' }, error) : null,
358
+ ),
359
+ )
360
+ }
361
+
362
+ // The 代理 row's icon slot now carries a sliding switch: clicking the
363
+ // switch toggles the proxy in place (and stops the click from bubbling to
364
+ // the row, so the detail panel does not open); clicking the row's label
365
+ // still opens the detail panel.
366
+ function ProxySwitch({ remote, t, size }) {
367
+ const [status, setStatus] = React.useState(null)
368
+ const [busy, setBusy] = React.useState(false)
369
+
370
+ const read = React.useCallback(() => {
371
+ remote.status().then((result) => {
372
+ if (result && result.ok) setStatus(result.value)
373
+ }).catch(() => {})
374
+ }, [remote])
375
+ React.useEffect(() => { read() }, [read])
376
+ // Keep the switch honest when the detail panel changes the state.
377
+ React.useEffect(() => {
378
+ const onFocus = () => read()
379
+ window.addEventListener('focus', onFocus)
380
+ return () => window.removeEventListener('focus', onFocus)
381
+ }, [read])
382
+
383
+ const on = !!(status && status.enabled)
384
+ const toggle = () => {
385
+ if (!status || busy) return
386
+ setBusy(true)
387
+ remote.setEnabled(!on).then((result) => {
388
+ if (result && result.ok) setStatus(result.value)
389
+ }).catch(() => {}).finally(() => setBusy(false))
390
+ }
391
+
392
+ const s = typeof size === 'number' && size > 0 ? size : 18
393
+ const height = Math.max(12, Math.round(s * 0.72))
394
+ const width = Math.round(height * 1.8)
395
+ const knob = Math.max(8, height - 4)
396
+ const label = on ? t('stateOn') + ',点击关闭' : t('stateOff') + ',点击开启'
397
+ return h('span', {
398
+ role: 'switch',
399
+ 'aria-checked': on,
400
+ 'aria-label': label,
401
+ title: label,
402
+ tabIndex: 0,
403
+ onClick: (event) => {
404
+ if (event && typeof event.stopPropagation === 'function') event.stopPropagation()
405
+ toggle()
406
+ },
407
+ onKeyDown: (event) => {
408
+ if (event.key === 'Enter' || event.key === ' ') {
409
+ event.preventDefault()
410
+ if (typeof event.stopPropagation === 'function') event.stopPropagation()
411
+ toggle()
412
+ }
413
+ },
414
+ style: {
415
+ position: 'relative',
416
+ display: 'inline-block',
417
+ width: width,
418
+ height: height,
419
+ borderRadius: height / 2,
420
+ background: on
421
+ ? 'var(--dsw-alias-state-business-primary, #4176e6)'
422
+ : 'var(--dsw-alias-bg-layer-3, rgba(128,128,128,0.35))',
423
+ transition: 'background .18s ease',
424
+ cursor: busy || !status ? 'wait' : 'pointer',
425
+ opacity: busy ? 0.55 : 1,
426
+ flexShrink: 0,
427
+ verticalAlign: 'middle',
428
+ },
429
+ },
430
+ h('span', {
431
+ style: {
432
+ position: 'absolute',
433
+ top: 2,
434
+ left: on ? width - knob - 2 : 2,
435
+ width: knob,
436
+ height: knob,
437
+ borderRadius: '50%',
438
+ background: '#fff',
439
+ boxShadow: '0 1px 2px rgba(0,0,0,.3)',
440
+ transition: 'left .18s ease',
441
+ },
442
+ }),
443
+ )
444
+ }
445
+
446
+ return {
447
+ inject: ['slots', 'remote'],
448
+ async apply(ctx) {
449
+ const t = translator(ctx)
450
+ const unmount = await ctx.remote.$mount(REMOTE)
451
+ const remote = ctx.get('remote.' + NS)
452
+ if (!remote) throw new Error('model-proxy: remote is not mounted')
453
+
454
+ // One 代理 row. Its icon slot renders the sliding switch, so the
455
+ // proxy can be toggled right here; the row itself still opens the
456
+ // detail panel (the switch stops propagation on its own clicks).
457
+ ctx.slots.inject('sidebar.panellist', () =>
458
+ ctx.slots.register(
459
+ { name: 'sidebar.panellist', id: PANEL_ID, order: 6, label: () => t('nav') },
460
+ (ownerProps) => h(ProxySwitch, { ...(ownerProps || {}), remote, t }),
461
+ ),
462
+ )
463
+ ctx.slots.inject('main', () =>
464
+ ctx.slots.register({ name: 'main', key: PANEL_ID }, () => h(Panel, { remote, t })),
465
+ )
466
+
467
+ return () => {
468
+ if (typeof unmount === 'function') unmount()
469
+ }
470
+ },
471
+ }
472
+ },
473
+ })
@@ -0,0 +1,7 @@
1
+ - insert:
2
+ - id: model-proxy
3
+ name: dsh-model-proxy-switch
4
+ config:
5
+ proxy: auto
6
+ bypass: localhost,127.0.0.1,::1
7
+ hosts: ''
package/icon.svg ADDED
@@ -0,0 +1,13 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
2
+ <defs>
3
+ <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
4
+ <stop offset="0" stop-color="#4c8dff"/>
5
+ <stop offset="1" stop-color="#7b5cff"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <rect x="4" y="4" width="56" height="56" rx="14" fill="url(#g)"/>
9
+ <path d="M14 24h18a7 7 0 1 0-7-7" fill="none" stroke="#fff" stroke-width="4" stroke-linecap="round"/>
10
+ <path d="M50 40H32a7 7 0 1 0 7 7" fill="none" stroke="#fff" stroke-width="4" stroke-linecap="round"/>
11
+ <circle cx="45" cy="20" r="5" fill="#fff"/>
12
+ <circle cx="19" cy="44" r="5" fill="#fff"/>
13
+ </svg>