Introduction¶
DSH Desktop is itself an interactive client. “Letting other programs drive it” usually means either modifying the client source code or injecting custom scripts into the host environment, both of which carry high maintenance costs. WilShi/dsh-external-api takes a different path: it mounts an optional HTTP service inside the DSH process, which is disabled by default and listens only on 127.0.0.1 by default, uses Bearer Token for authentication, and exposes capabilities such as sessions, runs, and SSE events to the outside. The maintainer is WilShi. Below is the introduction.
What is this¶
One-sentence positioning: An optional external HTTP API for DSH Desktop, targeting local and trusted LAN integration scenarios.
The plugin package name is dsh-external-api, current version 0.2.4, MIT license, Node requirement ^22.19.0 || ^24.0.0. It is compatible with DSH 0.2.1-rc.5 (Host protocol v4) and Cordis 4.0.5. The plugin form is Bundle (dsh.bundle.patch) plus Client inject (the settings.section slot), meaning you need to click to enable it in the DSH settings page after installation, rather than modifying the configuration file.
It does not spin up a new host environment. All @deepseek-ai/* host packages are declared as peerDependencies and provided by the DSH runtime; the plugin itself does not install and will not bring a second set of cordis singleton. Required host services (inject) are sessionController, workspaceController, agentPresets, sessionSkillCatalog, and webServer. Runtime dependencies are only @deepseek-ai/dsh-atomic-write (atomic write) and @deepseek-ai/schemastery (config schema).
Core Features¶
- Disabled by default. The service listens on no ports until enabled.
- Listens only on localhost by default. After enabling, the
hostdefaults to127.0.0.1; only the local machine can access it. Changing it to0.0.0.0or a LAN address opens it to the network, at which point the startup log will print a network exposure warning. - Bearer Token authentication. All interfaces require
Authorization: Bearer <token>, except for health checks and CORS preflight. - Resource model is Session / Run / Event stream: list sessions, create sessions, initiate Run, query Run status, subscribe to SSE, cancel Run.
- Token-level isolation. Each Token can only access Sessions, Runs, and SSE streams it created.
- Fixed subscription event types:
run.queued,run.started,run.cancelling,message.delta,message.completed,tool.started,tool.completed,run.completed,run.cancelled,run.failed. SSE supportsLast-Event-IDfor reconnection. - Capability detection interface.
/api/v1/capabilitiesreturns what the current Token can see. The caller can read it first to decide on subsequent requests. - Revocation takes effect immediately. Token revocation immediately disconnects its SSE connection, attempts to cancel active Runs, and deletes its session ownership record.
Installation and Enablement¶
There is only one installation command, from the README:
dsh plugin --profile web add dsh-external-api
After installation, restart DSH, then enable it in “Settings -> API Service”. Then fill in the port (default 47831) as prompted by the page, create an independent Token for the caller, and immediately save the plaintext that only displays once.
Verify the Service First¶
Port defaults to 47831, Token is written to the environment variable DSH_API_TOKEN. The purpose of this step is to confirm that the Listener is up, the Token is valid, and to see what permissions this Token has.
curl.exe http://127.0.0.1:47831/api/v1/capabilities `
-H "Authorization: Bearer $env:DSH_API_TOKEN"
Create a Session and Initiate a Run¶
The easiest path is POST /api/v1/responses: a single request creates a session and initiates a Run. The following demonstrates using Windows PowerShell; for LAN devices, replace 127.0.0.1 with the DSH host’s LAN IPv4.
$body = @{ input = '请概括当前项目的结构' } | ConvertTo-Json -Compress
Invoke-RestMethod `
-Method Post `
-Uri 'http://127.0.0.1:47831/api/v1/responses' `
-Headers @{ Authorization = "Bearer $env:DSH_API_TOKEN" } `
-ContentType 'application/json' `
-Body $body
The response status is 202, containing session, run, and eventsUrl. The next step is to use this Run ID to read real-time events:
curl.exe -N http://127.0.0.1:47831/api/v1/runs/RUN_ID/events `
-H "Authorization: Bearer $env:DSH_API_TOKEN"
Interfaces and Scopes¶
Full interface table (from original README, Scope names copied verbatim):
| Method | Path | Purpose | Required Scope |
|---|---|---|---|
GET / HEAD |
/api/v1/health |
Listener health check | None |
GET |
/api/v1/capabilities |
Capabilities, limits, and current Token permissions | capabilities:read |
GET |
/api/v1/workspaces |
Workspaces available to the Token | capabilities:read |
GET |
/api/v1/agents |
Agent Presets available to the Token | capabilities:read |
GET |
/api/v1/models?sessionId=... |
Models available to the session | capabilities:read |
GET |
/api/v1/skills?sessionId=... |
Skills available to the session | capabilities:read |
GET |
/api/v1/sessions |
List sessions created by the current Token | sessions:read |
POST |
/api/v1/sessions |
Create session | sessions:write |
GET |
/api/v1/sessions/:id/messages |
Get sanitized message history | sessions:read |
POST |
/api/v1/sessions/:id/messages |
Initiate Run | runs:write |
POST |
/api/v1/sessions/:id/runs |
Initiate Run (semantically equivalent to the previous item) | runs:write |
POST |
/api/v1/responses |
Create session and initiate Run | sessions:write、runs:write |
GET |
/api/v1/runs/:id |
Query Run status | runs:read |
GET |
/api/v1/runs/:id/events |
Subscribe to Run’s SSE events | runs:read |
POST |
/api/v1/runs/:id/cancel |
Cancel Run | runs:write |
You can specify workspaceId and agentPreset from the whitelist when creating a session:
{
"workspaceId": "workspace-id",
"agentPreset": "agent-preset-id"
}
Request body for initiating a Run:
{
"input": "分析这个问题",
"clientTimeZone": "Asia/Shanghai"
}
Browser SSE Subscription¶
Browser native EventSource cannot set Bearer Headers, so fetch streams must be used. Note: The browser must also be within the allowed Origin whitelist of the service. The current settings page does not yet offer this configuration, and normal web pages will be rejected by default; the code below is only applicable to environments where the corresponding Origin has been enabled by the distribution or controlled configuration.
const response = await fetch(`${baseUrl}/api/v1/runs/${runId}/events`, {
headers: { Authorization: `Bearer ${token}` },
})
if (!response.ok || !response.body) throw new Error(`HTTP ${response.status}`)
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader()
for (;;) {
const { value, done } = await reader.read()
if (done) break
console.log(value) // Official clients should parse SSE frames, not assume one read is one event
}
Error Format¶
External interfaces use a stable JSON error structure and do not return internal exceptions or host error details. The client should use the HTTP status code and error.code as the basis for programmatic judgment.
{
"error": {
"code": "AUTH_INVALID",
"message": "The bearer token is invalid.",
"requestId": "..."
}
}
Security Boundaries¶
It is recommended to read this section before deciding whether to change the listening address.
- Binds to
127.0.0.1by default. Explicitly settinghostto0.0.0.0(or a LAN address) opens it to the network. It does not reuse or forward DSH’s host/apiroutes. - The Token plaintext is only returned once upon creation; SHA-256 hash is saved in persistent configuration.
- Workspace and Agent Preset cannot be specified by the caller by default; they must be explicitly added to the Token whitelist. If the whitelist is not empty, creating a Session must explicitly specify the allowed
workspaceId/agentPreset; omitting it results in 403. - There is a clear upper limit on the scope of exposure: Slash Commands, any internal RPC, tool parameters, tool result bodies, system prompts, and reasoning content will not be exposed.
- The Host must match the actual IPv4 and port the client accesses. Origin, Fetch Metadata, request body size, request rate, concurrent Runs, and SSE connection counts are also limited. The default limit for active Runs is 10 per Token and 20 globally; the old precise
2/4default combination is automatically upgraded on startup, other custom combinations remain unchanged. - Management interfaces are only attached to DSH’s own random loopback ports and validate same-origin browser requests to prevent cross-site calls from web pages; they do not treat malicious local processes under the same system account as an isolated security boundary.
- Configuration written by management interfaces is saved in
$DSH_HOME/external-api/config-v1.json(directory permission 0700, file 0600), separate from Token ownership recordsownership-v1.json; the loader uses the config issued viacordis.patch.ymlas the base layer, and edits from the management page overlay it. - The external API is plaintext HTTP. Enable it only in trusted dedicated networks, restricting inbound sources to required private networks via system firewall; do not map or expose ports directly to the public internet. When accessing via untrusted networks, use a VPN or a trusted TLS reverse proxy and restrict the source network.
Applicable Scenarios and Notes¶
Applicable scenarios: Want to hook up an external orchestration script, a web frontend, or another device on the LAN to DSH Desktop locally, and want to control who can create sessions and who can run Runs at a Token granularity.
Points to note:
- The plugin runs with the permissions of the current DSH process and can access sessions, workspaces, and tool execution results, equivalent to having the same capabilities as that process. Read the source code before installing to confirm that the MIT license is compatible with your distribution method, and check what configurations are issued via
cordis.patch.yml. - It is not an official product; the directory page is a community site and has no official affiliation with DeepSeek / Fangyuan.
- DSH Host protocol v4 starting from
0.2.1-rc.5is within this compatibility range; using it on other versions requires confirmation first. - The Token plaintext is displayed only once; if lost during creation, it must be regenerated.
Conclusion¶
The value of this interface lies in turning “driving DSH” into a common HTTP call, while keeping risks within a controllable range through default local binding, Token-level isolation, and clear upper limits on external exposure. The directory page is at https://www.skillhub.cn/plugins/WilShi/dsh-external-api, and the repository is at https://github.com/WilShi/dsh-external-api.