dsh-net-proxy-plugin
August 30, 2026 · View on GitHub
English | 简体中文
dsh-net-proxy-plugin
A fallback network proxy plugin for DeepSeek Harness (bundle): scans proxies already present on the system (environment variables, Windows system proxy, macOS/Linux system proxy, common local proxy ports), probes overseas reachability (Google 204 and GitHub by default), and when direct access fails while a local proxy works, wires that proxy into the running dsh — all in-process fetch goes through it and HTTPS_PROXY/HTTP_PROXY/NO_PROXY are set so child processes (git, pnpm) launched by the agent use it too.
This is a fallback, not a takeover: with no usable proxy or with direct access working, dsh behaves exactly as if the plugin were not installed; on uninstall the environment variables and the global dispatcher are restored precisely.
Changelog
Latest version only; full history in change-log.en.md.
0.1.0 (2026-08-31)
- Initial release Plug-and-play: no build step needed (built
lib/is committed).
Features
- Candidate discovery (deduped by priority):
extraProxiesconfig → environment variables (HTTPS_PROXY/HTTP_PROXY/ALL_PROXYand lowercase) → system proxy (Windows WinINet registry, macOSscutil --proxy, Linux GNOME gsettings) → local listening ports (default 7890/7897/7891/10809/10808/8888/8080, both http and socks5 attempted). - Reachability probing: direct plus each candidate, one GET per target; an HTTP status below 500 counts as reachable.
- Auto decision (
activation: autodefault): activate a proxy when it reaches all targets and at least one target fails directly;activation: alwaysactivates the first working proxy regardless. Re-probes every 5 minutes by default (reprobeIntervalMs: 0disables), so a dropped VPN switches back to direct and a restarted one reactivates;/net-proxy refreshre-probes immediately. - Activation scope: all in-process fetch (the undici global dispatcher is replaced by a hostname-routing
RoutingDispatcher; hosts inbypassHostsstay direct; covers LLM, web_search, etc.) plus child processes (standard proxy env vars +NO_PROXY). - Optional
net-proxyprovider: the built-inweb-fetch-httpprovider deliberately opens its own connection and bypasses the global fetch proxy; this plugin offers a semantically compatible provider, switchable in one line (see below). /net-proxycommand: check current plugin status in the Web UI;/net-proxy refreshre-probes immediately.
Verified on a real machine
During development, on Windows with a VPN system proxy at http://127.0.0.1:51266: the plugin detected direct GitHub connectivity failing and the system proxy reaching every target, then activated automatically; the model's first web_fetch failed on direct and the retry returned 204 through the proxy — exactly the "retry through the local proxy after a timeout" path. The dsh log shows dsh-net-proxy: probe done — direct blocked, working proxies: ..., active: http://127.0.0.1:51266.
A single machine/run record; results depend on the network that day. On other machines use the plugin's own probe output.
Install
Repository: https://github.com/minatoAI/dsh-net-proxy-plugin
The plugin is distributed as a bundle and installed into a profile with dsh plugin (use pnpm dsh instead of dsh when running from a source checkout):
From GitHub. The built
lib/is committed and there is no build script, so no allowBuilds authorization is needed
dsh plugin --profile web add github:minatoAI/dsh-net-proxy-plugin
More stable: pin to a commit so later pushes cannot change what you actually installed
dsh plugin --profile web add github:minatoAI/dsh-net-proxy-plugin#<commit-sha>
Or from a local folder (development; build first)
pnpm install
pnpm run build
dsh plugin --profile web add ./dsh-net-proxy-plugin
After installing, restart dsh (a new bundle takes effect on the next launch):
dsh --profile web
Installation fetches the only runtime dependency undici from npm; @deepseek-ai/cordis, @deepseek-ai/schemastery and @deepseek-ai/dsh-web are peer dependencies resolved from the dsh installation closure — no extra install needed.
Usage
Probing runs automatically at startup. The first probe takes a few seconds (direct + candidates × targets, each up to probeTimeoutMs); one direct request in this window may fail, and the next retry hits the activated proxy.
- On activation the dsh log emits
dsh-net-proxy: activated <url> (system, ...)and aprobe donesummary. - In the Web UI,
/net-proxyshows the current state (active proxy, source, activation time, direct reachability);/net-proxy refreshre-probes immediately.
Configuration
Override the net-proxy row in your profile's cordis.patch.yml:
- id: net-proxy
config:
targets: [https://www.google.com/generate_204, https://github.com]
probeTimeoutMs: 5000
reprobeIntervalMs: 300000
bypassHosts: [localhost, 127.0.0.1, ::1, api.deepseek.com, "*.deepseek.com"]
extraProxies: [http://127.0.0.1:7890]
probePorts: [7890, 7897, 10809]
activation: auto
| Field | Default | Description |
|---|---|---|
enabled | true | Master switch |
targets | https://www.google.com/generate_204, https://github.com | Reachability probe targets |
probeTimeoutMs | 5000 | Per-probe timeout |
reprobeIntervalMs | 300000 | Re-probe interval; 0 disables |
bypassHosts | localhost, 127.0.0.1, ::1, api.deepseek.com, *.deepseek.com | Hosts kept on direct connections while a proxy is active (exact / *.domain wildcard / *) |
extraProxies | [] | Explicit proxies, preferred over discovery |
probePorts | 7890, 7897, 7891, 10809, 10808, 8888, 8080 | Local ports scanned |
activation | auto | auto: enable only when a target fails directly; always: enable with any working proxy |
fetchTimeoutMs | 30000 | net-proxy web provider timeout |
maxResponseBytes | 5000000 | net-proxy web provider response cap |
maxRedirects | 5 | net-proxy web provider same-origin redirect cap |
Routing the web_fetch tool through the proxy
web_fetch (the built-in web-fetch-http provider) deliberately opens its own connection and pins the public IP, bypassing the global fetch proxy. This plugin provides a semantically compatible net-proxy provider (same WEB_FETCH_TIMEOUT/WEB_ABORTED/WEB_REDIRECT_BLOCKED/WEB_BLOCKED_URL/WEB_FETCH_TOO_LARGE error codes; with the proxy, public-IP validation is carried by the proxy you choose). Change one line in your profile's cordis.patch.yml to enable it:
- id: web
config:
searchProvider: deepseek-official
fetchProvider: net-proxy
Without it, web_fetch keeps its original direct path and is not affected by this plugin.
Network & proxy (mainland-China users)
The plugin reads proxies already present on the system and ships no proxy service of its own. When a VPN/proxy is on it is discovered and reused automatically; if the VPN restarts with a new port the next re-probe (5 minutes by default) or /net-proxy refresh heals it. Only use local proxies you trust — the proxy sees and forwards all traffic you send through it; the plugin never alters the Windows system proxy setting and only affects network access of the dsh process and its children.
Uninstall
dsh plugin --profile web remove dsh-net-proxy-plugin
Repository layout
dsh-net-proxy-plugin/
├── package.json # manifest: "dsh": { "bundle": {"patch": ...} }; built lib/ committed
├── cordis.patch.yml # composition: single row net-proxy (name: dsh-net-proxy-plugin)
├── src/ # TypeScript source (NodeNext ESM, strict)
│ ├── index.ts # plugin body: config schema, probe scheduling, activation decision, provider/command registration, lifecycle
│ ├── candidates.ts # candidate discovery (config→env→system proxy→ports); system-proxy readers (WinINet/scutil/gsettings)
│ ├── probe.ts # direct/per-proxy reachability probing
│ ├── dispatcher.ts # undici proxy dispatcher and hostname-routing RoutingDispatcher
│ ├── activate.ts # ProxyActivation: activate/deactivate/switch-and-restore of global dispatcher and proxy env vars
│ ├── bypass.ts # host matching (exact / *.domain wildcard) and NO_PROXY value generation
│ └── fetch-provider.ts # optional net-proxy WebFetchProvider (same-origin redirects/size cap/timeout/error codes)
├── lib/ # build output (committed; GitHub install works out of the box)
├── tests/ # vitest: 40 cases (parsing/routing/probing/activation/provider/system-proxy integration)
├── README.md # 简体中文说明
├── README.en.md # English README
├── change-log.md # 完整版本历史(简体中文)
└── change-log.en.md # Full change history (English)
Development notes
- Composition follows the dsh convention: a single row
net-proxywhosenameis the exact package namedsh-net-proxy-plugin. - The plugin
injectlist is empty: probing and activation depend on no service and start immediately;web/commandsare optional services registered viactx.inject, so without them in the composition the related features simply do not register and the loader startup never stalls. - Every registration is an effect (
ctx.effect/yieldreturning a disposer); the lifecycle effect clears the re-probe timer and callsactivation.dispose()(which restores the global dispatcher and proxy env vars). - Relative imports in
src/carry.jssuffixes (required by tsc NodeNext emit);tsconfig.jsonenablesstrict,noUncheckedIndexedAccess,exactOptionalPropertyTypes.
Tests
pnpm install
pnpm test # vitest: 40 cases
pnpm run build # tsc emits lib/
dsh plugin check . # validate the patch layer and plugin mount (use pnpm dsh from a fork checkout)
Tests use local mock proxies (absolute-form HTTP + CONNECT tunneling) and a real target-server router, covering candidate parsing, host matching/NO_PROXY, RoutingDispatcher, probing/decision, activate/deactivate/switch restore, and the WebFetchProvider contract — 40 cases in total.
Note:
dsh plugin checkreportsunknown inject dependencies: commands— that is a gap in the checker's own known-services list (commandsdoes exist in the composition); it does not affect install or runtime.