KeepLost/harniverse--packages-client-connection ↗★ 1

@deepseek-ai/dsh-client-connection

Wire consumer layer: HTTP-up/WebSocket-down client, ConnectionController dual streams with reconnect, and fixture api 提供双向流连接、重连机制及会话导入 API,适合需要与主机进行稳定通信的客户端开发。

Package
@deepseek-ai/dsh-client-connection
Compatibility
Unverified
Harness peer range
workspace:^
Cordis peer range
workspace:^
Version
0.1.0-rc.5
License
BSD-3-Clause
Last updated
Sep 25, 2026

Other repositories with this package name

Install

This plugin has no verified bundle, or compatibility checks failed. Read the repository notes first. Read the full README ↗

@deepseek-ai/dsh-client-connection

The Host half also exposes authenticated POST /api/session/import. It accepts an official foreign-session artifact only for an existing workspace, lets the caller choose supervised or unsupervised archival posture, and never creates a live Agent for the imported session.

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 authenticates every network request, carries the accepted principal through HTTP and WebSocket dispatch, and checks each legacy or Typert endpoint's required capability before selecting a handler. Unknown endpoints and missing policy metadata are denied. Authentication bypass remains loopback-only and carries all capabilities. POST /api/attachment/upload streams one generic file behind the same fence and authentication: it additionally requires the harniverse.operate capability (an observer cannot inject session content), pre-checks Content-Length against the attachment store's byte cap, cuts a chunked body off the moment the cap is crossed, and answers with the durable FileAttachmentRef receipt; store admission failures map to 413/400/500. 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.

ctx.connection.authentication publishes the Host-verified stable identity of the active generation. Every unary call captures its initiating identity and requires the response or receipt to carry the same identity before any consumer can observe it; missing or mismatched metadata synchronously retracts authentication and hostDescription and aborts the generation. Each downlink begins with an intercepted connection.authenticated control message, and readiness requires unary, mux, and host identities to match exactly. Business frames received immediately after either stream opens remain in generation-local queues until all three identities agree; matching readiness flushes them in stream order, while mismatch or generation failure discards them without reaching a sink. Each queue is capped at 1,024 frames by default (preReadyBufferMaxFrames); overflow fails and rebuilds the generation even when host.describe is stalled. Every mutating or otherwise secret-bearing principal-bound unary also carries the current identity as its Host-enforced expected-principal precondition. Only kind, grantId, and grantRevision cross this boundary.

hostDescription.bootId identifies one API Proxy process lifetime. It stays stable across connection generations served by the same Host and changes after restart, so consumers can fence cached process-local state without treating a reconnect as a restart.

The browser half consumes client authentication for HTTP calls, upload admission, and event-connection recovery. health is a read-only projection of that authentication snapshot and the transport generation; refresh-required authentication overrides an open socket. The Host marks a pre-dispatch HTTP 401 with x-dsh-authentication: required and cache-control: no-store. Only that classified refusal permits one retry; network failures and 403 do not. Upload retries retain XHR progress and cancellation, and a repeated classified refusal requires manual reauthentication.

/api response encoding

The Host bridge negotiates content-encoding for every buffered /api reply: Brotli when the request offers br, otherwise gzip, and verbatim bytes when neither is offered. A token offered with q=0 counts as refused. Replies under 1 KiB stay verbatim because the encoding saves no round trip at that size. Every buffered reply declares vary: accept-encoding even when it goes out verbatim, so a shared cache cannot serve an encoded body to a client that never offered the encoding; an upstream vary is preserved rather than replaced, and content-length always describes the bytes actually written. Only application/json is buffered, and every other content type streams through untouched: event streams must reach the browser frame by frame, and the session-log ZIP export streams under its own bounded capacity gate so the Host never holds a whole archive. That allowlist keeps a future streaming content type correct without having to be remembered here. Encoding runs on the zlib thread pool rather than synchronously, because the Host answers every other request on the same event loop and a multi-megabyte history page would otherwise stall concurrent RPCs. Brotli quality is pinned below its default: the default spends roughly 60 ms on such a page for a few percent more ratio, which moves latency from the network onto the Host. This matters most for a cold history page, whose settled assistant/chunk events compress about ten to one (measurement).

/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 loopback or matches a canonical trustedHosts authority; attached Origin and Fetch Metadata must describe a same-origin request unless the exact Origin is in trustedOrigins. Host trust remains mandatory for an explicit cross-origin Origin. This remains a DNS-rebinding and confused-deputy defense rather than authentication. The independent authentication Provider then verifies a short Access Token or browser session, and rejected HTTP or WebSocket admission never reaches RPC dispatch. A non-loopback listener requires direct TLS certificate and key configuration, while loopback may remain plain HTTP; HTTPS browser sessions use a Secure __Host- cookie and auth responses are not cacheable. Non-loopback compositions still declare the names they serve through derived LAN literals or --trusted-host; the Web profile prints effective trust policy and non-secret accepted/rejected connection markers. Decision records: the api browser-trust boundary and public-key Grant authentication Agent Notes.

/api WebSocket downlinks

/api/events.mux and /api/events.host each accept a WebSocket upgrade and send only the transport authentication control followed by the corresponding ServerRequest text messages; the client sends no application data over these sockets. Before each generation, ConnectionController samples the runtime's current per-Session contiguous cursors and the browser encodes a non-empty map in the mux URL's since query; the Host validates it before upgrade. If either socket ends, the current connection generation fails and rebuilds both streams from a fresh cursor sample; readiness requires both controls, matching host.describe response identity, and stream establishment before buffered business frames can reach consumers. 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

  • The browser WebSocket inbox is not independently byte-bounded — Host stream queues have a frame-count limit and reconnect from durable cursors, but one very large frame or a browser callback that permanently falls behind can still retain significant Client memory.
  • The /api bridge 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.