dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DeepSeek Harness LLM provider plugin for OpenCode Go (zen-go route)
An LLM provider plugin for DeepSeek Harness that serves OpenCode Go: it registers
the zen-go route and sends a stable per-conversation x-opencode-session
header on every outbound inference request.
Runtime: adapted to DeepSeek Harness
0.1.6-alpha.1and above (peer dependencies@deepseek-ai/dsh-*declared as^0.1.6-alpha.1). From 0.1.6 on, image offload is the adapter's job (see "Over-budget images" below); this plugin relies on that contract, which is what sets the floor.
chat/completions (mimo / deepseek-v4 / glm / kimi /
longcat / hy), responses (grok / gpt-5.6-luna / muse-spark, including the
standalone response.incomplete terminal state), and messages
(Anthropic-compatible: minimax / qwen, authenticated with x-api-key,
max_tokens defaults to 8192 when unset).x-opencode-session carries the conversation id, with a
per-request UUID fallback so the header is never missing.user-agent: deepseek-harness/<version> (+url) dsh-opencode-go/<version>,
identical on all surfaces so operators can identify and permit exactly this
traffic (never a generic string like Go-http-client/1.1).credentials.resolve layers process env over the
managed store ($DSH_HOME/.credentials.yaml, hot-reloaded) over
project/home .env files — storing or rotating the key needs no restart.
There is no plaintext key field anywhere.reasoning.effort); chat models offer Low/Medium/High
(reasoning_effort passthrough; deepseek-v4-flash/pro/flash-vision-exp
additionally offer Max — verified live against the upstream gateway, thanks
to @34262315716, see
#1); messages models
offer none (Anthropic thinking is budget-based — explicit values are
rejected). No defaults are declared: Default omits the field (the server
decides).deepseek-v4-flash-vision-exp, both
muse-spark-*-contributor) declare ["text", "image"] so the runtime passes
images through; other known models are text-only (the runtime substitutes
placeholders); unknown ids stay permissive and the server decides. Images
resolve from the durable attachment store to inline base64 data URLs per
surface (png/jpeg/webp/gif). One image's size is decided by the attachment
store's admission-time normalization, not by this plugin: this deployment
reduces every image to ≤ 4 MiB and ≤ 2048×2048 px (≤ 8192 per side; uploads
are capped at 20 MiB each and 200 MiB per message), and imageHostPath hands
back that normalized copy — so what gets inlined is never the original upload.
The plugin's own 20 MiB per-image check therefore only fires for an attachment
provider that does no normalization: it is a fallback, not the guard that
matters.maxRequestImageBytes (64 MiB by default) this plugin does not drop
images itself — it fails with IMAGE_OFFLOAD_REQUIRED naming how many of the
oldest occurrences must go, and dsh-compaction-image-offload records that
decision and retries the step. Dropping images is a durable session
decision, so only the harness may record it, and the check always happens
before dispatch (a durable ref carries its encoded size, so nothing is read).
Occurrences the harness already marked offloaded always travel as
placeholder text and are never re-inlined — that bookkeeping is the harness's,
not a route's to reverse. Note what triggers it: image count, not one image's
size — a normalized image is about 5.4 MiB once base64-encoded, so the 64 MiB
default is roughly a dozen images in one request./v1/models
discloses ids only, so a newly served model (say deepseek-flash) is
classified unknown — which is why neither a reasoning level nor a modality
can be set for it
(#4). The settings card
now lets you state, per model, its surface (chat / responses / messages),
its reasoning levels and its modality; leaving a control on default
follows the built-in table. The surface decides both routing and the level
vocabulary — chat sends reasoning_effort, responses sends reasoning.effort,
messages has no level vocabulary — so the level options follow the surface you
pick. A hand-declared image rewrites the declaration and the image-admission
check, so a table-known text-only id is not rejected after you declare it
multimodal.surface and efforts together, so
surface: responses carrying the chat-only max is refused as a whole. The
card therefore conforms the levels in the same settings write that changes
the surface: what the new surface can carry is kept, what it cannot is dropped
and named in a notice; switching to a surface with no level vocabulary at all
(messages) clears them; handing the choice back to default reconciles against
that model's own default level list (an unclassified id offers none, so the
list is cleared). The card can therefore never write a combination the host
would refuse — which would otherwise drop the whole section and silently revert
routing to the composition defaults.text/image)
only; every other block — harness annotations such as reasoning and
tool-call, plus merge-extensible additions — is dropped rather than
rejected. A settled background subagent expands the child's final assistant
content into a user-role notice, so such blocks legitimately appear in
history; because that history is durable, throwing on one untranslatable
block fails every later turn of the conversation
(#3). The same holds for
assistant turns; only image still fails loudly, since uploaded binary
content must not silently vanish.prompt_tokens and
input_tokens vocabularies), provider totals preserved verbatim, cache
reads/writes and reasoning tokens reported in separate buckets.x-opencode-session/
purpose/in/out/cache read/write/reasoning/finish) in
$DSH_HOME/plugin-data/dsh-opencode-go/usage.jsonl (append-only, archived
on clear, never leaves the machine); the card shows an Overview/Models tab
pair with a large-cell daily heatmap plus a per-model table, with one-click
clear. Click any heatmap day to expand that day's totals plus a
per-dsh-session breakdown (including the actually sent session header).The route is deliberately named zen-go, not opencode-go — the latter is the
natural name for user-owned pi-ai custom profiles, and the llm registry allows
exactly one owner per route. apply() also refuses to boot with a clear
DUPLICATE_ADAPTER error when the route is taken, so a collision can never
silently break the other party.
dsh plugin --profile web add <plugin-directory>
# restart dsh web, then pick zen-go/<model> in the session model picker
Local-source installs (dsh plugin add <dir>, the pnpm link: route) need
one extra step first — otherwise boot crashes outright (see
#2):
cd <plugin-source-dir>
node scripts/setup-local-deps.cjs # locate the host tree, bridge node_modules/@deepseek-ai
dsh plugin --profile web add <plugin-source-dir>
# restart dsh web
Why: a link: install is just a junction back at the source tree, so Node
resolves the host half's @deepseek-ai/* peer imports upward from the REAL
source path, where no @deepseek-ai/* exists. --host <dir> pins the host
tree explicitly (install root, node_modules, or the @deepseek-ai dir
itself); --dry-run only probes. If boot ever complains
Cannot find package '@deepseek-ai/xxx', check the bridge is still a
junction and re-run the script. Registry/github installs are unaffected —
skip this step.
Store the key (any one of these; effective immediately, no restart):
# $DSH_HOME/.credentials.yaml
version: 1
refs:
OPENCODE_GO_API_KEY: sk-your-key
Or export the OPENCODE_GO_API_KEY environment variable, or paste it in the
settings card.
llm-opencode-go settings section)| Field | Default | Notes |
|---|---|---|
apiKeyEnv | OPENCODE_GO_API_KEY | credential ref name |
apiBase | https://opencode.ai/zen/go | base URL without the trailing /v1 prefix |
requestTimeoutMs / streamIdleTimeoutMs | 60000 / 300000 | connect + first-byte timeout (timer stops at response headers; long streams aren't capped by total time) / stream idle watchdog |
maxRequestImageBytes | 67108864 (64 MiB) | inline image budget for one request, counted in the base64 bytes the body carries; above it the route fails with IMAGE_OFFLOAD_REQUIRED so the harness drops the oldest occurrences and retries. The operator publishes no request-size limit, so this is a deployment value: the default sits far above ordinary screenshot traffic (a normalized image is ~5.4 MiB encoded, so roughly a dozen in one request) — lower it toward a proxy's body cap |
enabledModels | whole table | which models are offered (the card checkboxes edit this) |
modelCaps | [] | [{id, contextWindow?, maxTokens?, surface?, image?, efforts?}], user-filled capacity and capability overrides |
The settings card reports its own refusals: when the section fails validation
(say modelCaps pairs surface: responses with the chat-only max level) the
plugin drops it whole and keeps the defaults, and the reason is shown above the
model list — otherwise the card's selection count and the request path would
each tell their own story.
node --test test/smoke.mjs # plugin self-consistency (host fully stubbed)
node test/host-compat.mjs # real host contracts (real dsh-llm + real stream invariant)
# live probes (spend a tiny amount of quota):
$env:OPENCODE_GO_API_KEY='<key>'; node --test test/smoke.mjs
test/host-compat.mjs imports the real @deepseek-ai/dsh-llm,
dsh-invariants and cordis from node_modules and drives the real llm
service plus the real stream-grammar invariant. Run it first after a host
upgrade: smoke.mjs stubs every host surface, so it cannot notice the host
tightening a contract (0.1.6 moving image offload from the runtime into the
adapter is exactly that kind of change).
/v1/models endpoint discloses no context windows, so
modelCaps is manual for now; first-class fields plug straight in once
the operator adds them.reasoning_effort=max and
contributed the Max tier for the three deepseek-v4 models
(#1); root-caused the
local-source link-install boot crash
(#2).