dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
OpenCode / OpenCode Go provider plugin for DeepSeek Harness (dsh)
|
⭐ If you like this plugin, please star it on GitHub — it shows me that the plugin is useful to you and motivates me to keep developing it.
🐛 If you find a bug or would like to request a feature, open a GitHub issue in any language — I will review your proposal and implement useful suggestions in a future plugin version. |
OpenCode / OpenCode Go provider plugin for DeepSeek Harness (dsh).
It registers two native LLM routes:
| Route | Product | Base |
|---|---|---|
opencode-direct | OpenCode Console / Zen (pay-as-you-go) | https://opencode.ai/inference |
opencode-go-direct | OpenCode Go / Go Plus subscription | https://opencode.ai/inference/go |
Requests resolve the key per call, so a changed credential reaches the next request without a restart.
The ids use the -direct suffix because the built-in llm-pi-ai catalog already owns
opencode and opencode-go. Reusing those ids makes the core registry reject the
duplicate (DUPLICATE_DIRECTORY) and drop the built-in catalog, which hides the other
providers and disables the add button. Existing sessions recorded under the old ids stay on
the built-in routes; select OpenCode (plugin) or OpenCode Go (plugin) for this
plugin's accounts, quotas, fallback and telemetry.
graph LR
subgraph DSH["DeepSeek Harness Core"]
Agent["DSH Agent / Chat"]
Creds["Credentials / auth.json"]
end
subgraph Plugin["dsh-opencode-provider"]
Router{"Route Selector"}
Pool["Multi-Account Pool<br/>(Auto-Rotation & Cooldowns)"]
Heatmap["Heatmap & Quota Telemetry<br/>(5h / Week / Month)"]
subgraph Transports["4 Wire Transports"]
T1["OpenAI Chat"]
T2["Anthropic Messages<br/>(Thinking Signatures)"]
T3["OpenAI Responses"]
T4["Google GenAI<br/>(thoughtSignature Replay)"]
end
end
subgraph OpenCode["OpenCode Gateway"]
Console["opencode-direct (Console / Zen)"]
Go["opencode-go-direct (Go / Go Plus)"]
end
Agent --> Router
Creds -.-> Pool
Router -->|opencode-direct| Pool
Router -->|opencode-go-direct| Pool
Pool --> Heatmap
Pool --> Transports
T1 & T2 & T3 & T4 --> Console & Go
thoughtSignature on historical function calls.Retry-After, a rate limit for 60s or Retry-After. A request already streaming is never replayed on another account; the next request uses the next usable key.{at, provider, model, purpose?, account, reason, ok, latencyMs, ttftMs?, error?} to ~/.dsh/storages/dsh-opencode/requests.jsonl; no prompt, response or key is stored. Auxiliary session-title/compaction calls are excluded from the heatmap and exact duplicate records collapse. The log compacts at 2 MB to the newest 5000 lines. Collection starts with this build — the projection cache only has lastPromptAt, not per-request timestamps.x-opencode-session derived from the durable session id, so retries and every turn of a conversation reuse the same routing key./v1/models with a generated models.dev fallback./opencode command for status, usage, accounts, models, rules, and test [model]./settings → OpenCode) with the key, endpoints, allowlist, active account, fallback model, sticky routing and timeouts.sticky.json (hashed session ids and key fingerprints only); explicit model rules and pinned accounts win.GET/POST /dsh-opencode/update.Go, Console or FREE.—: the local projection does not expose failed attempts or reasoning tokens.auth.json./dsh-opencode/* serve state and accept writes only from a loopback Host with same-origin and JSON checks; GET /dsh-opencode/health reports provider registration, accounts and model count.read, write, edit, bash, glob, grep, …) when a large MCP set is trimmed to OpenCode's 128-tool limit.Build and pack first:
npm install
npm run build
npm pack
Install the packed tarball into a profile:
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.14.tgz
# DSH Desktop:
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.14.tgz
# Or install the published package:
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.14
Restart dsh (or DSH Desktop), then pick OpenCode (plugin) or OpenCode Go (plugin) in the model picker.
Use the tarball, not the directory. On dsh 0.2.1-rc.2 the Desktop CLI aliases a local directory passed to
dsh plugin addas@deepseek-ai/dsh-desktop-runtimeand overwrites the target'spackage.json. Installing the packed.tgzavoids that path and installs the package under its real name.
Tested against DSH Desktop 0.2.1-rc.2:
/dsh-opencode/state returns 200;~/.local/share/opencode/auth.json;rolling / weekly / monthly;/dsh-opencode/analytics scanned 7 local sessions (0 OpenCode sessions yet, as expected before the first OpenCode chat);/dsh-opencode/test against the default flash model returned {"ok":true,"text":"OK"};/test rotated to the second account and still returned OK;QUOTA, marked the default as invalid-credential and Go #2 as quota until its reset, with no active account;default 1% / 5% / 2% and Go #2 9% / 4% / 100% monthly (rate-limited), so the exhausted account is visible without a failed request;/test requests produced Tue 13:00 = 2 in the heatmap and two lines in requests.jsonl; the 24-hour strip shows the same value at 13:00;Go #2 still showed its persisted quota cooldown while the restored default key was active;*free* → Go #2 through /config returned the normalized rule from /state, and clearing it returned []; the account list stayed unchanged;OpenCode Go with a 5% ring, 5-Hour Window 99% remaining · 3h, Weekly Window 95% remaining · 6d, Monthly Window 98% remaining · 29d; it uses the DSH theme variables and matches the meter's row/ring layout;settings.opencode is present in its code cache).Open Settings → OpenCode and enable Show the quota card in the sidebar if you want the sidebar meter.
- id: llm-opencode
name: "@goodandready/dsh-opencode-provider"
config:
apiKeyEnv: OPENCODE_API_KEY # default
# apiKey: oc_sk_... # composition only, never commit
openCodeAuthEntry: opencode-go
requestTimeoutMs: 300000
streamIdleTimeoutMs: 300000
visibleModels: []
modelTransports: {}
# contextWindowOverrides: { model-id: 65536 }
# maxOutputTokens: 32768
accounts:
- label: Go #2
apiKeyEnv: OPENCODE_API_KEY_2
activeAccount: '' # label or credential name to pin
Every field except apiKey is volatile and appears in dsh settings.
config.apiKey (composition only, role('secret')).apiKeyEnv.~/.local/share/opencode/auth.json → entry openCodeAuthEntry (default opencode-go), written by OpenCode's own /connect..env, $DSH_HOME/.env).Extra accounts resolve the same way (literal key or credential reference). If you already use OpenCode, the plugin picks up the stored Go key automatically.
Add a second account in Settings → OpenCode → Accounts: give it a name and a credential reference (for example OPENCODE_API_KEY_2), paste its key, then Save. The default account (the key card at the top) always serves first unless activeAccount pins another one.
Rotation happens before any content is streamed. When the serving key is rejected with an invalid credential, a rate limit, or an exhausted quota, the adapter marks that key and retries the same request with the next usable account. An exhausted pinned account still falls back to the others; it returns to service once its cooldown passes. A request that has already started streaming is not replayed on another account.
Each account row shows the current cooldown (Invalid key, Quota cooldown · 23h, Rate limited · 30s) and its own quota chips. The active marker moves to the first account that is not cooling down. The last account is marked too, so a failed turn explains which key ended it. Quota cooldowns honor the provider's reset (Retry-After) when present. The quota panel repeats the numbers in an accounts table, and the sidebar meter uses the active account.
Cooldowns survive restarts: deadlines and reasons are stored in ~/.dsh/storages/dsh-opencode/cooldowns.json keyed by a SHA-256 fingerprint — raw keys are never written. Expired entries are dropped on load, and the file keeps at most 50 entries.
Pin a model to a preferred account with a glob. The first matching rule wins; * and ? are supported and matching is case-insensitive. The named account is tried first, and the other accounts remain fallbacks if it is cooling, missing, or rejected — a rule is a preference, not a hard filter. An unknown account label is ignored, and rules apply to both opencode-direct and opencode-go-direct.
Configure them in Settings → OpenCode → Model → account rules, or in YAML:
modelAccountRules:
- pattern: "*free*"
account: "free"
- pattern: "your-provider-*"
account: "go"
The account label is the same one shown in Accounts (default or the extra account's name).
The quota panel has a Request heatmap: rows are Monday–Sunday, columns are local-time hours, and each cell counts calls made through this adapter (including tool-loop steps and Test connection). Auxiliary session-title and compaction calls are excluded, and exact duplicate records collapse; tool-loop steps are separate adapter requests, so one chat message is not necessarily one cell. Hover a cell to see the exact count. A 24-hour strip below the grid aggregates the same window by hour across all days, so the daily shape stays readable even when the grid is sparse. When more than one model or account has traffic, selectors switch the grid and strip to a single model or account; each option shows its total. The data is append-only metadata in ~/.dsh/storages/dsh-opencode/requests.jsonl; it never includes prompts, responses or keys. Because the local projection cache stores only the last prompt time per session, the heatmap starts collecting with this build and grows as you use the plugin.
Open Settings → OpenCode:
Go 34.7K/5h). Hover the estimate for the full Go / Go Plus monthly allowance and 5h/week/month request counts; expand Subscription limits for a table of every model with both plans.auth.json.dsh plugin add path, blocks a second click while installing, and reports when DSH must be restarted.The sidebar quota card is off by default. Enable Show the quota card in the sidebar under Sidebar; the card appears immediately after saving, and clicking it opens the full quota panel with the Usage by model table (7/30/90-day window).
The Host serves /dsh-opencode/state, /health, /analytics, /config, /key, /key/clear, /auth-import, /refresh, /models/refresh, /test and /update. Writes require JSON, are refused for cross-origin requests, and are restricted to loopback (127.0.0.1 / ::1); reads stay same-origin. This keeps key and configuration writes off a LAN-exposed web server until the host's own auth token can be integrated.
The Plugin update card in Settings → OpenCode calls GET /dsh-opencode/update, which compares the installed package.json with the npm registry. When a newer version exists, Update now sends the same-origin request with x-dsh-plugin-update: 1; the Host runs the normal DSH CLI:
dsh plugin --profile <profile> add --config.minimumReleaseAge=0 @goodandready/dsh-opencode-provider@<exact-version>
The update is blocked while another update is running, never uses --force, and never installs from a worktree or a file: path. The card reports that DSH must be restarted after a successful install. If the runtime does not expose the CLI entry, the card says that automatic update is unavailable and points to the DSH plugin manager; the status check still works.

Captured from the exact 0.5.0 release candidate in DSH Desktop in the dark theme.
/opencode status # accounts, per-account quotas, rules
/opencode usage # quota windows only
/opencode accounts # labels, source, active/cooling, 5h/wk/mo
/opencode models # ids, FREE/Go limits
/opencode rules # model → account rules
/opencode test [model] # minimal real request
GET /inference/go/v1/models is public and returns the live Go roster.GET /zen/go/v1/usage returns rolling (5h), weekly, monthly with percent and resetsAt. It is undocumented; the plugin treats it as fail-open.POST /zen/go/v1/chat/completions accepts OpenAI-compatible and Anthropic-backed models, but not Responses-only or Google models.thoughtSignature on historical function calls; the Google transport stores it in dsh's replay state.x-opencode-session is mandatory. A stable opaque value is enough for a Go key; the canonical ses_* shape and CLI impersonation are only needed for the anonymous free lane, which this plugin deliberately does not implement.403 FreeTierError; free models work normally with an active Go subscription.npm run typecheck
npm run build
npm test
npm run check # typecheck + tests + build
npm run release # check + packed tarball
npm run sync:models # regenerate src/models.generated.ts from models.dev
GitHub Actions runs npm run check and npm pack on push/PR (.github/workflows/ci.yml).
opencode-direct (Console) billing shows no per-model allowance; the Go route is the primary target.Bearer public + CLI impersonation) is not implemented.ctx.webServer); the TUI keeps the /opencode command and the model routes.MIT © GooDAnDReaDY. See LICENSE and NOTICE.