wilshi/dsh-external-api: Add an optional external HTTP API to DSH Desktop

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

  1. Disabled by default. The service listens on no ports until enabled.
  2. Listens only on localhost by default. After enabling, the host defaults to 127.0.0.1; only the local machine can access it. Changing it to 0.0.0.0 or a LAN address opens it to the network, at which point the startup log will print a network exposure warning.
  3. Bearer Token authentication. All interfaces require Authorization: Bearer <token>, except for health checks and CORS preflight.
  4. Resource model is Session / Run / Event stream: list sessions, create sessions, initiate Run, query Run status, subscribe to SSE, cancel Run.
  5. Token-level isolation. Each Token can only access Sessions, Runs, and SSE streams it created.
  6. 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 supports Last-Event-ID for reconnection.
  7. Capability detection interface. /api/v1/capabilities returns what the current Token can see. The caller can read it first to decide on subsequent requests.
  8. 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.

  1. Binds to 127.0.0.1 by default. Explicitly setting host to 0.0.0.0 (or a LAN address) opens it to the network. It does not reuse or forward DSH’s host /api routes.
  2. The Token plaintext is only returned once upon creation; SHA-256 hash is saved in persistent configuration.
  3. 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.
  4. 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.
  5. 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/4 default combination is automatically upgraded on startup, other custom combinations remain unchanged.
  6. 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.
  7. Configuration written by management interfaces is saved in $DSH_HOME/external-api/config-v1.json (directory permission 0700, file 0600), separate from Token ownership records ownership-v1.json; the loader uses the config issued via cordis.patch.yml as the base layer, and edits from the management page overlay it.
  8. 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:

  1. 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.
  2. It is not an official product; the directory page is a community site and has no official affiliation with DeepSeek / Fangyuan.
  3. DSH Host protocol v4 starting from 0.2.1-rc.5 is within this compatibility range; using it on other versions requires confirmation first.
  4. 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.

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

Xiaoye