Models & Providers

dsh-provider-proxy-routes

apolenkov/dsh-provider-proxy · ★ 1

apolenkov

Per-provider and per-model outbound proxy routing for model calls, configured from a Settings page.

License
MIT
Added
2026-10-30
Maintenance
active
Install kind
npm
npm
@apolenkov/dsh-provider-proxy
Manifest details
Manifest status
Declared
Manifest path
package.json#dsh
Manifest checked
2026-10-30T09:38:39.435Z
Lifecycle scripts
None found

Static manifest checks report declarations only; they are not compatibility or security review.

安装

dsh plugin --profile web add '@apolenkov/dsh-provider-proxy@0.3.0'

从 npm 安装预构建包

For install and environment setup, plus plugin development tutorials, see DSH community docs ↗

README

Source README · body: en · Read Chinese README

Table of contents
  1. Install
  2. Configure
  3. In the UI (recommended)
  4. In YAML
  5. Injected headers
  6. Proxy values
  7. Muse Spark example
  8. Verify it works
  9. Limits
  10. Walkthrough
  11. Publishing
  12. Development
  13. Marketplace
  14. License

CI npm License: MIT Node >= 22 DSH ^0.2.1-rc.2 Tests

English · Русский · 中文

dsh-provider-proxy: per-provider and per-model proxy routing

Per-provider and per-model outbound proxy routing for DeepSeek Harness.

DeepSeek Harness applies one proxy policy per process (HTTP_PROXY / HTTPS_PROXY / NO_PROXY / ALL_PROXY). This plugin layers two routing axes on top of it, configurable per provider host and per model ID — from a Plugins settings page, without touching YAML unless you want to.

flowchart LR
    A["ctx.llm.stream<br/>{provider, model}"] --> B["llm/stream waterfall<br/>AsyncLocalStorage per pull"]
    B --> C["adapter → fetch"]
    C --> D["RoutingDispatcher"]
    D -->|model rule| E["direct / proxy / inherit"]
    D -->|host rule| E
    D -->|loopback| F["always direct"]
    D -->|no match| G["process policy<br/>env + NO_PROXY"]
  • Host axis (providers): matches the request URL host — exact, subdomains, optional host:port. Applies to every fetch() in the process (LLM, web search, MCP).
  • Model axis (models): matches the provider/model pair of the model call that issued the request. Exact ids and trailing-* prefixes, with an optional provider pin.
  • Precedence: provider-pinned exact model → bare exact model → provider-pinned prefix → bare prefix → provider host rule → default (inherit or direct).

Install

Prerequisite: a DSH profile with a plugin manager (the Desktop app and dsh web both qualify).

From npm (recommended):

dsh plugin --profile <profile> add @apolenkov/dsh-provider-proxy

From git (a release tag, no registry involved):

dsh plugin --profile <profile> add git+https://github.com/apolenkov/dsh-provider-proxy.git#v0.1.0

From a local checkout:

dsh plugin --profile <profile> add /absolute/path/to/dsh-provider-proxy
# or: link the directory in the profile's package.json and run pnpm install there

Then restart DSH. The bundle inserts the provider-proxy row; routes live in that row's config.

npm note: the package is @apolenkov/dsh-provider-proxy; the unscoped name dsh-provider-proxy belongs to an unrelated package on the public registry, so never pnpm add dsh-provider-proxy.

Configure

Open Plugins in the sidebar → open the dsh-provider-proxy bundle → Configure. The page shows:

  • Default route (inherit or direct);
  • Provider rules: label, hosts (comma or newline separated), proxy (inherit / direct / custom http(s):// URL);
  • Model rules: model id (exact or trailing *), optional provider pin, proxy.

Edits are staged locally and applied on Save with revision fencing: if someone changed the config meanwhile, the save is refused instead of silently overwriting. Clearing the form is never written; leaving the page drops staged edits.

In YAML

Add or edit the row in the profile’s cordis.patch.yml (HMR applies it live, no restart):

- id: provider-proxy
  name: "dsh-provider-proxy"
  config:
    default: inherit
    providers:
      corp:
        hosts: ["llm.internal.example"]
        proxy: direct
      opencode-go:
        hosts: ["opencode.ai"]
        proxy: "http://127.0.0.1:1301"
    models:
      "muse-spark-1.3-contributor":
        provider: opencode-go
        proxy: "http://127.0.0.1:1301"
      "longcat-*": inherit

An invalid entry refuses the whole configuration (fail-closed): the plugin logs the reason, keeps the previous routes, and never partially applies them.

Injected headers

A rule can add headers to the requests it matches. The token {{opencodeSession}} becomes a fresh OpenCode-shaped session id (ses_…) on every dispatch — useful for anonymous gateways that demand a per-conversation header the model adapter does not send for hand-declared models:

    models:
      "beam-free":
        provider: opencode-go
        proxy: "http://127.0.0.1:1301"
        headers:
          x-opencode-session: "{{opencodeSession}}"

Proxy values

Value Meaning
inherit Use the launcher’s process proxy policy (env + NO_PROXY).
direct Connect to the origin directly, ignoring the process proxy policy.
http(s)://… Route through that proxy (ProxyAgent). SOCKS and PAC are rejected.

The UI additionally refuses proxy URLs with embedded credentials — keep secrets out of the profile patch.

Muse Spark example

Route one model through its own proxy while everything else follows the launcher policy:

- id: provider-proxy
  name: "dsh-provider-proxy"
  config:
    default: inherit
    models:
      "muse-spark-1.3-contributor":
        provider: opencode-go
        proxy: "http://127.0.0.1:1291"

See examples/models-muse-spark.yaml (sanitized: loopback endpoints, no credentials). The same rule can be created in the Plugins page — the walkthrough is captured with assets/scripts/record-proxy-ui.sh into assets/gifs.

Verify it works

The plugin writes its last apply outcome (never secrets) to $DSH_HOME/provider-proxy/state.json:

{
  "appliedAt": "2026-10-22T07:59:22Z",
  "pid": 42120,
  "status": "active",
  "routes": "muse-spark-1.3-contributor→http://127.0.0.1:1301, llm.internal.example→direct, default→inherit"
}

A live rule can be proven by pointing it at a dead proxy (http://127.0.0.1:9) and watching the request fail with ECONNREFUSED 127.0.0.1:9 instead of reaching its target — then restore the real proxy.

Limits

  • WebSocket transports are not guaranteed to pass through the undici dispatcher; use an http/https transport for strict routing.
  • web_fetch pins validated addresses through the launcher's own policy and may bypass these routes; model traffic is covered.
  • Model rules apply only inside llm/stream; web search and MCP use the host axis.
  • A model rule never fires for non-model traffic that happens to hit the same host, and a host rule never overrides localhost / loopback.
  • Proxy URLs with credentials are accepted by the transport but rejected by the UI; prefer credential-free proxy endpoints.

Walkthrough

Proxy routes settings page: provider and Muse Spark model rules, an inline validation error, and a successful save

Recorded in a scratch profile with a loopback proxy: open the bundle’s Configure page, add the Muse Spark model rule, trigger and fix a validation error, save. Individual frames live in assets/screenshots/en/; reproduce or re-record with assets/scripts/record-proxy-ui.sh.

Publishing

Owner-side checklist for the first public release (repository URL, npm scope, market index, hub listing): docs/publishing.md. Automated gates:

pnpm check:release   # strict manifest + secrets + generated market index + tests
pnpm release:market  # writes market/dsh-market.json from package.json + HEAD

Development

pnpm install --ignore-scripts   # or: pnpm install --frozen-lockfile
pnpm test                       # node:test, 44 tests: matching, precedence, ALS isolation, egress, wiring, volatile updates, agent retirement, client contract, UI form logic
pnpm check                      # manifest + secrets + market validation + full test suite

See CONTRIBUTING.md (workflow, commit style, release checklist), CODE_OF_CONDUCT.md, and docs/ — an index with architecture, configuration reference, UI guide, publishing checklist, marketplace notes, troubleshooting, and a glossary.

Marketplace

To list this plugin in a DSH market, ship a format-1 index validated with pnpm check:market. A documented example lives in market/dsh-market.example.json; production indexes must replace the example repository URL and commit. Listing on community hubs (e.g. dsh.market) additionally requires the dsh-plugin topic on the GitHub repository or a submission through the hub’s own flow — see docs/marketplace.md.

License

MIT — see LICENSE. DeepSeek Harness itself is a separate project by DeepSeek; this plugin is community work and is not affiliated with or endorsed by them.