实现浏览器和 node 客户端的 HTTP 上行/WebSocket 下行连接,包含 ConnectionController 双流重连和浏览器信任 fence,为 dsh 插件提供统一连接抽象和 settings/credentials/agentPreset 等 API 路由
ⓘ 此插件是大仓库 deepseek-ai/deepseek-harness 的子包,星数与活跃度统计的是整个仓库。
- 语言
- TypeScript
- License
- MIT
- 分支
- master
安装
$ dsh plugin --profile web add npm:@deepseek-ai/dsh-client-connection在终端中运行以上命令,通过 dsh CLI 安装此插件。可在右上角切换 Profile。 第一次用 dsh?看这篇新手教程
对话式安装
帮我安装 DeepSeek Harness 插件 deepseek-ai/deepseek-harness/packages/client/connection:先查看仓库 https://github.com/deepseek-ai/deepseek-harness 确认安全性,然后执行安装命令并验证插件加载成功。
把这段指令粘贴给 DSH Web GUI 里的助手,由它代你完成安装与验证。
English | 中文
Wire consumer layer: the client plugin's apply mounts ctx.connection (shared api client + current-page loopback state + observable generation-scoped hostDescription + single-consumer stream-loop starter); the export face carries the wire contract types, the AbstractApiClient abstraction, and the loop's sink/config types. Each successful readiness handshake publishes the exact host.describe value before onConnected; generation loss and explicit stop clear it, so native-capability consumers never retain a disconnected answer. The browser carrier uses HTTP POST for unary and respond operations and opens one downlink-only WebSocket each for events.mux and events.host; the in-process carrier satisfies the same two-stream abstraction. The Host half owns the single /api route and its Fetch bridge; a registered Typert interceptor claims its Remote endpoints before the API Proxy fallback. Loopback hostname classification stays package-internal: the /api Host fence and WebSocket upgrades use it directly, while other client plugins consume the derived ctx.connection.isLoopback state. The node half's /api route pins the privileged method set (host.pickDirectory, host.openPath, and the whole configuration plane — settings.describe/openDocument/update/replace/mutate and credentials.describe/set/unset; reads and native actions included, since describing returns the exposed configuration, opening acts on the Host desktop, and probing an arbitrary reference reports where a credential comes from — and the agent-preset authoring plane, agentPreset.read/copy/openDocument/remove, since a composition names the plugins a session runs, so reading one is reconnaissance, and copy/remove/openDocument manage the roster and drive the host desktop (authoring is copy-only, so none of them accepts composition text or a path); agentPreset.list and agentPreset.select stay out — the roster carries only ids and trust, and choosing a preset grants nothing session.create's own agentPreset did not, over a default that already carries bash) to loopback by passing the trust fence with an empty trust list — a declared trustedHosts authority reaches every other method, while these stay loopback-local until a real authentication layer exists. The platform carriers and ConnectionController loop are package-internal; apply selects and drives them. The downlink boundary is documented in the WebSocket downlink carrier Agent Note.
/api browser-trust fence
The node half guards every entry under /api before bridging or upgrading (src/api-request-trust.ts). Every request — browser-marked or not — must present a Host that is a loopback authority or matches a trustedHosts entry: exact on host:port entries, any port on port-less entries, both sides compared through WHATWG normalization (DNS-rebinding defense). There is deliberately no shortcut for unmarked HTTP requests: over plain HTTP a browser attaches neither Origin nor Fetch-Metadata to image and navigation reads, so an unmarked request may still be a rebound browser read with a readable response, and Host is the one header rebinding cannot forge; a browser WebSocket handshake carries Origin and passes the same comparison. Non-browser clients pass the same fence via loopback, deployment-derived LAN IP literals, or a declared authority. When markers are present, an attached Origin must equal the Host authority, and an explicit sec-fetch-site: cross-site marker is refused. A trustedHosts entry that is not a bare, canonical host[:port] authority — one WHATWG parsing reads back exactly as written — fails the plugin load loudly: parsing would otherwise quietly authorize the hostname inside harness.internal/path, or broaden a dangling-colon or zero-padded port to an any-port grant. HTTP failures answer plain 403 before any RPC dispatch; upgrade failures reject the handshake before any event stream starts. Non-loopback compositions must trust their serving authorities explicitly: the Web runtime derives LAN IP literals from an all-interfaces server config, while trustedHosts in cordis.yml and the CLI's --trusted-host flag declare named authorities. dsh web --host 0.0.0.0 is intentionally unsupported until remote access has an authentication layer. The fence is a reachability policy, not authentication; the Web carrier provides no authentication layer. Decision record: the api browser-trust boundary Agent Note.
/api WebSocket downlinks
/api/events.mux and /api/events.host each accept a WebSocket upgrade and send only the corresponding ServerRequest text messages to the browser; the client sends no application data over these sockets. If either socket ends, the current connection generation fails and rebuilds both streams; readiness still requires both sockets to be open and the host.describe HTTP call to succeed. Host teardown terminates both sockets, aborts their sources, and waits for source cleanup before returning. Ordinary network GETs to these paths return 426 with no SSE fallback; toFetchHandler's SSE codec serves only the isomorphic in-process carrier.
Model Experience
None, as the wire consumer layer moves already-composed messages between browser and host; nothing here reaches a model request.
KV Cache effect
None; this package neither assembles nor sends a provider request.
Known Limitations and Deferred Work
- History resumes an unattached session — opening history may create the host-side agent and add latency to the first open; there is no persistence-only read path.
- The
/apibridge buffers each request body in memory —maxRequestBodyBytes(default 160 MiB, sized for the default 100 MiB aggregate image limit after base64 expansion plus envelope headroom) is therefore also the per-request resident bound; a streaming body path would be needed to lower it without shrinking the image limits.
收录徽章
[](https://deepseek-plugin.org/plugins/deepseek-ai/deepseek-harness/packages/client/connection)把这段 markdown 粘贴到你的 GitHub README,链接回本插件详情页。徽章只声明已被本站收录,不代表安全认证。