dsh-bridge
已验证@opencode-compat/dsh-bridge · v0.4.3 · MPL-2.0
Run unmodified OpenCode AI SDK provider plugins as DeepSeek Harness LLM adapters (Cordis LlmAdapter)
安装
dsh plugin add @opencode-compat/dsh-bridge 用 dsh --profile default --dump-config 确认 layer 已生效 —— 参见安装指南。
源码
作者
说明文档
@opencode-compat/dsh-bridge
Runs unmodified OpenCode aisdk-type plugins as LLM adapters on
DeepSeek Harness (dsh /
dsh web).
DSH is not an OpenCode fork and has no @opencode-ai/plugin-shaped package, so
the facade / ocp setup path does not apply. This package is a Cordis plugin
that loads the OpenCode plugin dynamically and registers it with
ctx.llm.registerAdapter(...), translating AI-SDK doStream to DSH
StreamChunk.
User install and verify: docs/hosts/dsh-family.md.
This file is the package contract.
Adding a provider
Cordis patch ($DSH_HOME/profiles/web/cordis.patch.yml), not a JSON file:
- id: ocp-dsh-bridge
config:
providers:
- package: cursor-opencode-provider
apiKey: CURSOR_API_KEY
- package: devin-opencode-provider
apiKey: DEVIN_API_KEY
Only package is required. Optional fields match the Pi-family spec shape
(providerName, apiKey, createOptions, disableOAuth,
preferAuthMethod, splitDimensions, directory).
| Discovered | From the plugin's own… |
|---|---|
| provider id | auth.provider (else the package name; de-collided against reserved DSH ids such as cursor → cursor-opencode) |
| model catalog | config hook — config.provider[id].models |
| API key | apiKey CredentialRef env name via ctx.credentials.resolve, then the plugin auth.loader, before the catalog read and each factory call |
| streaming | createXxx() AI-SDK V3 factory (doStream) |
| session affinity | DSH GenerateOptions.sessionId → V3 headers["x-opencode-session-id"] |
| effort variants | plugin variants / effort → LlmResolvedModelInfo.reasoning |
The Models list is the dsh-bridge settings section (same shape as
llm-pi-ai.providers.<id>). The bridge seeds dsh-bridge.providers.<route>
from the patch so a registered adapter shows as a configured row.
Install
dsh plugin --profile web add @opencode-compat/dsh-bridge
# then set config.providers[] as above; restart dsh web
Local checkout:
./scripts/ocp-dev.sh run dsh
node /path/to/deepseek-harness/apps/cli/lib/bin.js web
dsh plugin add file: copies the package into $DSH_HOME/profiles/web/node_modules.
A later tsc that emits new dist files does not update that copy — live DSH
keeps loading the profile tree, not the checkout dist. ocp-dev.sh run dsh
rebuilds and syncs that profile copy.
@opencode-compat/opencode-loader in this package's package.json must stay an
exact train pin (never workspace:*). DSH profile pnpm installs via file:
and cannot see the OCP Bun workspace — workspace:* fails with
ERR_PNPM_WORKSPACE_PKG_NOT_FOUND (0.4.1). bump-version.ts rewrites the pin.
peerDependencies on @deepseek-ai/dsh-* are checked against the dsh product
runtime version (today 0.1.x / 0.2.x), not the published major of that
package. Use a product-compatible range (e.g. >=0.1.0); >=1.0.0 or a
^0.1-only pin makes dsh plugin add reject newer 0.2.x runtimes.
Do not run ocp setup against DSH.
Tool names
The live DSH catalog is restated as OpenCode names before the plugin sees it, and
provider calls are mapped back on block-end:
| Host | Advertised | Notes |
|---|---|---|
todo_write |
todowrite |
Strip id/priority/merge; omit cancelled |
ask_user_question |
question |
Canonical schema; missing id is filled; multiple ↔ multi_select; JSON answers rewritten to OpenCode prose |
System text and system-role messages name those tools by their OpenCode names
too: a code span that is exactly `todo_write` or `ask_user_question`
becomes `todowrite` / `question`, for every provider. Prose is left
alone.
File tools still advertise filePath and map path/filePath → file_path.
Bash still drops required description and fills it from command.
For Cursor packages, the optional plan integration registers plan_enter
and invokes the calling agent's native /plan command at execution. This
uses DSH's own plan service even when it is isolated inside an agent preset.
Cursor's native
SwitchMode therefore selects the actual host state before the next step.
The native exit_plan_mode catalog entry is advertised to Cursor as
cursor_plan_stage; its content becomes the native {plan} argument.
Calls still execute as exit_plan_mode through DSH's normal tool pipeline,
including its review UI, approval/refinement, cancellation, and next-step exit.
If Cursor ends a plan-mode turn with a titled markdown plan in prose instead
of calling the advertised tool, OCP submits that completed text to the same
native review before the turn can finish. This fallback checks the calling
agent's logged plan projection; it never runs outside plan mode or after an
ordinary tool call. The host-owned call id starts with host_plan_stage_.
DSH retains the full plan in its session transcript. OCP does not write mode
events, parse approval-question wording, or queue an execution follow-up.
DSH tool-role result messages stay tool results in the AI SDK prompt, so
Cursor's held Run receives their answers instead of starting a new turn.
System guidance uses the translated tool name; review errors remain errors
on replay. Both tools stay advertised across mode changes. Other providers
keep the native exit_plan_mode name/schema and do not see OCP's synthetic
plan_enter. Cursor also receives cursor_image_save for generated binary
images. It accepts only the provider's single-use staged image ID and uses
DSH's current sandbox policy and approval service before the provider commits
the file. Other providers do not see this tool. If the calling agent lacks the
native plan service, plan entry fails closed.
See the upstream plan-mode contract.
Continuable children also send_message their result (agent-message relay) and
DSH then posts a subagent-settled notice. Each wakes a generate when the
parent is idle. For Cursor only, after a text-only stop, the adapter finishes
those child-notice generates without opening the model and repeats the prior
text as the final visible reply. This prevents DSH's completed-Turn folding
from hiding a request for user input. The display copy is omitted from later
model prompts. Helper results claimed in the same turn as a spawn
tool-call still generate. Plan results and later child notices remain ordinary
DSH history; OCP does not reinterpret or remove them.
Cursor's large discovered-tool catalogs may spill through a normal write
call to <host-cache>/projects/<slug>/agent-tools/<uuid>.txt. For this exact
metadata path, the bridge adds DSH's advertised danger-full-access escalation
fields so the native write tool asks for approval before saving outside the
workspace. Other write paths and providers retain their original arguments.
DSH delivers AGENTS.md and scoped instruction files as user-role messages
(source.kind: "agent-instructions"). For Cursor only, the bridge moves them
into system, in request order: cursor-opencode-provider reaches the model
with host system context only through an always-apply rule built from the
system prompt, and sends just the latest user message as a turn's live text, so
an instruction message elsewhere in the request would be lost, and one after
tool results would read as a new user turn. Devin and generic providers keep
DSH's native user-role messages.
Path bridge
On apply, installs Symbol.for("opencode.host.path-bridge"):
globalDataDir—$DSH_HOMEor~/.dshglobalCacheDir—$XDG_CACHE_HOME/opencodeor~/.cache/opencodeprojectConfigDirs—<workspace>/.dshand<workspace>/.opencode
License
MPL-2.0