@deepseek-ai/dsh-client-connection 0.0.1-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +28 -0
- package/README.i18n.yaml +6 -0
- package/README.md +25 -0
- package/README.zh.md +25 -0
- package/lib/client.js +9945 -0
- package/lib/index.js +585 -0
- package/lib/invariant.js +26 -0
- package/lib/types/api-path.d.ts +12 -0
- package/lib/types/api-request-trust.d.ts +43 -0
- package/lib/types/client/api.d.ts +20 -0
- package/lib/types/client/connection.d.ts +62 -0
- package/lib/types/client/fixture.d.ts +54 -0
- package/lib/types/client/index.d.ts +45 -0
- package/lib/types/client/random-uuid.d.ts +7 -0
- package/lib/types/client/rpc.d.ts +8 -0
- package/lib/types/client/web-api-client.d.ts +11 -0
- package/lib/types/http-bridge.d.ts +24 -0
- package/lib/types/index.d.ts +36 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/loopback-hostname.d.ts +12 -0
- package/lib/types/rpc-host.d.ts +33 -0
- package/lib/types/rpc.d.ts +51 -0
- package/lib/types/websocket-downlink.d.ts +43 -0
- package/package.json +67 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type { ApiProxy, SessionsApi, SessionSearchItem, SessionSummary, PromptContentPart, HostApi, EventsApi, MuxFrame, HostFrame, ApprovalResponsePayload, QuestionResponsePayload, HistoryEntry, ToolEventView, DirectoryEntry, DirectoryListing, ResponseValue, WorkspaceApi, WorkspaceId, WorkspaceView, CommandsApi, CommandDescriptor, SkillsApi, SkillEntry, ModelCatalogFailure, ModelCatalogModel, ModelProviderGroup, ModelReasoning, ModelReasoningEffort, ModelSelection, QueueAction, QueuedInboxItem, SessionModels, GoalsApi, GoalRef, SettingsApi, SettingsNamespaceView, SettingsPathOpView, SettingsSecretView, CredentialsApi, CredentialView, ConfigurableProviderView, DiscoveredModelView, LlmApi, SubagentsApi, SubagentAddress, SubagentCatalog, SubagentListEntry, SubagentPromptReceipt, } from '@deepseek-ai/dsh-host-apiproxy/api';
|
|
2
|
+
export type { ToolCallView, ToolResultView } from '@deepseek-ai/dsh-tools/presentation';
|
|
3
|
+
export type { RpcRequest, RpcResponse, RpcResult, RpcError, RpcErrorCode, ClientRequest, ServerResponse, ServerRequest, ClientResponse, RpcMessage, RpcReceipt, } from '@deepseek-ai/dsh-host-apiproxy/api';
|
|
4
|
+
export { RpcId, SESSION_SEARCH_RESULT_LIMIT, transportError, } from '@deepseek-ai/dsh-host-apiproxy/api';
|
|
5
|
+
export { AbstractApiClient } from '@deepseek-ai/dsh-host-apiproxy/client';
|
|
6
|
+
export type { IApiClient } from '@deepseek-ai/dsh-host-apiproxy/client';
|
|
7
|
+
export type { SessionId, SessionEvent } from '@deepseek-ai/dsh-session/types';
|
|
8
|
+
export type { MessageId } from '@deepseek-ai/dsh-llm/brand';
|
|
9
|
+
export type { ContentBlock, StreamChunk } from '@deepseek-ai/dsh-llm/types';
|
|
10
|
+
/** Successful value returned by the connection-generation host handshake. */
|
|
11
|
+
export type HostDescription = import('@deepseek-ai/dsh-host-apiproxy/api').ResponseValue<'host.describe'>;
|
|
12
|
+
import type { RpcResponse, RpcResult } from '@deepseek-ai/dsh-host-apiproxy/api';
|
|
13
|
+
/**
|
|
14
|
+
* Unwrap a unary response: RpcResponse<T> -> RpcResult<T> (business code only
|
|
15
|
+
* cares about the result slot).
|
|
16
|
+
* @param response - the unary response.
|
|
17
|
+
* @returns its result slot.
|
|
18
|
+
*/
|
|
19
|
+
export declare function resultOf<T>(response: RpcResponse<T>): RpcResult<T>;
|
|
20
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { IApiClient, HostFrame, MuxFrame, RpcRequest } from './api.ts';
|
|
2
|
+
/** Reconnect/backoff tunables (deployment-varying — no hardcoded tunables; these become the
|
|
3
|
+
* future `ctx.connection` plugin's Config). All fields optional; defaults below. */
|
|
4
|
+
export interface ConnectionConfig {
|
|
5
|
+
/** First-retry backoff cap in ms (jittered: actual delay is cap/2..cap). */
|
|
6
|
+
backoffBaseMs?: number;
|
|
7
|
+
/** Exponential growth factor per consecutive failed attempt. */
|
|
8
|
+
backoffFactor?: number;
|
|
9
|
+
/** Upper bound for the backoff cap in ms. */
|
|
10
|
+
backoffMaxMs?: number;
|
|
11
|
+
/** Cap on waiting for both streams' onOpen before onConnected, in ms. The strict handshake
|
|
12
|
+
* waits for mux+host stream establishment plus describe; a carrier that never
|
|
13
|
+
* fires onOpen (misbehaving proxy) must not wedge the connection forever — on timeout the
|
|
14
|
+
* generation proceeds as connected and the live-gap repair path covers stragglers. */
|
|
15
|
+
streamOpenTimeoutMs?: number;
|
|
16
|
+
}
|
|
17
|
+
/** Coarse connection state for the UI: 'connected' after each generation's handshake,
|
|
18
|
+
* 'reconnecting' the moment the generation fails (covers the whole backoff+retry span). */
|
|
19
|
+
export type ConnectionState = 'connected' | 'reconnecting';
|
|
20
|
+
/** Frame sink callbacks: the Controller owns the physical streams; business dispatch belongs to
|
|
21
|
+
* SessionManager. */
|
|
22
|
+
export interface ConnectionSinks {
|
|
23
|
+
onMuxEnvelope?: (envelope: RpcRequest<MuxFrame>) => void;
|
|
24
|
+
onHostEnvelope?: (envelope: RpcRequest<HostFrame>) => void;
|
|
25
|
+
/** After each connection generation is established (both streams open + describe succeeded), first connect included. */
|
|
26
|
+
onConnected?: () => void;
|
|
27
|
+
/** Coarse state transitions (deduplicated: fires only on change). The initial pre-connect
|
|
28
|
+
* span reports nothing — the UI treats "no state yet" as connecting, not as an outage. */
|
|
29
|
+
onStateChange?: (state: ConnectionState) => void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Opens both streams and keeps iterating (pull mode: nothing reads the socket and the tap
|
|
33
|
+
* never fires unless someone for-awaits), reconnecting with exponential backoff on loss.
|
|
34
|
+
* State (generation/attempt) is instance-private, never in the store.
|
|
35
|
+
* The pump body feeds each frame to a sink (sink exceptions must
|
|
36
|
+
* not kill the pump — a broken business layer must not drag down the connection layer).
|
|
37
|
+
*/
|
|
38
|
+
export declare class ConnectionController {
|
|
39
|
+
private readonly api;
|
|
40
|
+
private readonly sinks;
|
|
41
|
+
private generation;
|
|
42
|
+
private attempt;
|
|
43
|
+
private current;
|
|
44
|
+
private running;
|
|
45
|
+
private lastState;
|
|
46
|
+
private readonly config;
|
|
47
|
+
constructor(api: IApiClient, sinks?: ConnectionSinks, config?: ConnectionConfig);
|
|
48
|
+
/** Idempotent: begin the connect/pump/reconnect loop. */
|
|
49
|
+
start(): void;
|
|
50
|
+
/** Stop the loop and abort the current generation's streams. */
|
|
51
|
+
stop(): void;
|
|
52
|
+
private backoffDelay;
|
|
53
|
+
/** Read through a method: stop() flips the flag across awaits, so narrowing from the loop condition must not stick. */
|
|
54
|
+
private isRunning;
|
|
55
|
+
private loop;
|
|
56
|
+
/** Deduplicated state emission (sink isolation applies). */
|
|
57
|
+
private emitState;
|
|
58
|
+
private pumpStream;
|
|
59
|
+
/** Sink exception isolation: a business-layer throw is logged only, never affecting pump or reconnect semantics. */
|
|
60
|
+
private callSink;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=connection.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
2
|
+
import type { ApiProxy, ClientResponse, HostFrame, MuxFrame, RpcReceipt, RpcRequest, RpcResponse } from './api.ts';
|
|
3
|
+
import type { RequestPayload, ResponseValue, RpcMethodMap } from '@deepseek-ai/dsh-host-apiproxy/api';
|
|
4
|
+
import { AbstractApiClient } from './api.ts';
|
|
5
|
+
import type { ClientConnectionRpc } from '../rpc.ts';
|
|
6
|
+
/** Deterministic fixture branches used by keyless Web assembly tests. */
|
|
7
|
+
export interface FixtureOptions {
|
|
8
|
+
/** Start with no real Workspace or Session. */
|
|
9
|
+
empty?: boolean;
|
|
10
|
+
/** Reject every prompt before appending its user event. */
|
|
11
|
+
rejectPrompt?: boolean;
|
|
12
|
+
/** Publish the Session but fail its Workspace account write. */
|
|
13
|
+
failWorkspaceAttach?: boolean;
|
|
14
|
+
/** Publish and frame the Session, then throw instead of returning create. */
|
|
15
|
+
dropSessionCreateResponse?: boolean;
|
|
16
|
+
/** Order of the two successful create frames. */
|
|
17
|
+
createFrameOrder?: 'session-first' | 'workspace-first';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* In-memory fake host: fx-alpha carries history and replay scripts; fx-beta is fx-alpha's child session (lineage indent material).
|
|
21
|
+
* @param options - fixture branches for empty state and failure timing.
|
|
22
|
+
* @returns an ApiProxy backed entirely by in-memory state — no host process, no network.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createFixtureApi(options?: FixtureOptions): ApiProxy;
|
|
25
|
+
/**
|
|
26
|
+
* Fixture platform subclass: there is no HTTP at all, so instead of a doFetch transport it
|
|
27
|
+
* overrides the protocol-level virtuals (callUnary/openMux/openHost/respond) to dispatch
|
|
28
|
+
* straight into the in-memory ApiProxy — while still minting rpcIds, fabricating the four
|
|
29
|
+
* named full forms, and feeding the same tap as a real carrier. TODO: delete when the fixture
|
|
30
|
+
* moves to the isomorphic pipeline (InProcessApiClient over toFetchHandler(fixtureImpl)).
|
|
31
|
+
*/
|
|
32
|
+
export declare class FixtureApiClient extends AbstractApiClient {
|
|
33
|
+
private readonly api;
|
|
34
|
+
/** Generic Remote caller backed by the same in-memory state as the legacy fixture API. */
|
|
35
|
+
readonly rpc: ClientConnectionRpc;
|
|
36
|
+
constructor();
|
|
37
|
+
protected doFetch(): Promise<Response>;
|
|
38
|
+
protected callUnary<K extends keyof RpcMethodMap>(method: K, payload: RequestPayload<K>, signal?: AbortSignal): Promise<RpcResponse<ResponseValue<K>>>;
|
|
39
|
+
/** Method-key dispatch into the in-memory contract impl (a real carrier routes by URL path instead). */
|
|
40
|
+
private dispatch;
|
|
41
|
+
protected openMux(payload: {
|
|
42
|
+
since?: Record<SessionId, number>;
|
|
43
|
+
}, signal: AbortSignal, onOpen?: () => void): AsyncIterable<RpcRequest<MuxFrame>>;
|
|
44
|
+
protected openHost(payload: Record<never, never>, signal: AbortSignal, onOpen?: () => void): AsyncIterable<RpcRequest<HostFrame>>;
|
|
45
|
+
private tapStream;
|
|
46
|
+
/**
|
|
47
|
+
* Deliver a client response to the in-memory contract impl (no HTTP POST),
|
|
48
|
+
* echoing the envelope to the observation tap like every other path.
|
|
49
|
+
* @param message - the client-response envelope answering a server request.
|
|
50
|
+
* @returns the carrier receipt from the fixture impl.
|
|
51
|
+
*/
|
|
52
|
+
respond(message: ClientResponse): Promise<RpcReceipt>;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=fixture.d.ts.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser wire client. The plugin selects fixture or HTTP transport, provides
|
|
3
|
+
* the shared API client, and lets the runtime object layer start the stream
|
|
4
|
+
* controller with its sinks.
|
|
5
|
+
*/
|
|
6
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
7
|
+
import type { IApiClient } from './api.ts';
|
|
8
|
+
import { type ConnectionConfig, type ConnectionSinks, type ConnectionState } from './connection.ts';
|
|
9
|
+
import type { ClientConnectionRpc } from '../rpc.ts';
|
|
10
|
+
export type { ApiProxy, SessionsApi, SessionSearchItem, SessionSummary, PromptContentPart, HostApi, EventsApi, MuxFrame, HostFrame, ApprovalResponsePayload, QuestionResponsePayload, HistoryEntry, ToolEventView, DirectoryEntry, DirectoryListing, ToolCallView, ToolResultView, WorkspaceApi, WorkspaceId, WorkspaceView, CommandsApi, CommandDescriptor, SkillsApi, SkillEntry, ModelCatalogFailure, ModelCatalogModel, ModelProviderGroup, ModelReasoning, MessageId, ModelReasoningEffort, ModelSelection, QueueAction, QueuedInboxItem, SessionModels, SubagentsApi, SubagentAddress, SubagentCatalog, SubagentListEntry, SubagentPromptReceipt, RpcRequest, RpcResponse, RpcResult, RpcError, RpcErrorCode, ClientRequest, ServerResponse, ServerRequest, ClientResponse, RpcMessage, RpcReceipt, HostDescription, IApiClient, SessionId, SessionEvent, ContentBlock, StreamChunk, GoalsApi, GoalRef, SettingsApi, SettingsNamespaceView, SettingsPathOpView, SettingsSecretView, CredentialsApi, CredentialView, ConfigurableProviderView, DiscoveredModelView, LlmApi, } from './api.ts';
|
|
11
|
+
export { RpcId, AbstractApiClient, transportError, } from './api.ts';
|
|
12
|
+
export type { ConnectionConfig, ConnectionSinks, ConnectionState };
|
|
13
|
+
export type { ClientConnectionRpc } from '../rpc.ts';
|
|
14
|
+
/** Required services (none — this is the wire root). */
|
|
15
|
+
export declare const inject: string[];
|
|
16
|
+
/**
|
|
17
|
+
* The ctx.connection service surface: the api client plus a one-shot
|
|
18
|
+
* controller starter (the runtime plugin supplies sinks when its object layer
|
|
19
|
+
* is ready — connection stays consumer-agnostic).
|
|
20
|
+
*/
|
|
21
|
+
export interface ConnectionHandle {
|
|
22
|
+
/** Shared api client (fixture or real, decided at boot from the page URL). */
|
|
23
|
+
readonly api: IApiClient;
|
|
24
|
+
/** Whether the current page authority is loopback; non-browser contexts default to true. */
|
|
25
|
+
readonly isLoopback: boolean;
|
|
26
|
+
/** Generic logical RPC channels over the same Connection transport. */
|
|
27
|
+
readonly rpc: ClientConnectionRpc;
|
|
28
|
+
/**
|
|
29
|
+
* Start the connect/pump/reconnect loop with the consumer's frame sinks.
|
|
30
|
+
* One consumer owns the streams (the runtime object layer); a second call
|
|
31
|
+
* throws.
|
|
32
|
+
* @param sinks - frame/state callbacks.
|
|
33
|
+
* @param config - reconnect/backoff tunables.
|
|
34
|
+
* @returns stop handle for the loop.
|
|
35
|
+
*/
|
|
36
|
+
start(sinks: ConnectionSinks, config?: ConnectionConfig): {
|
|
37
|
+
stop(): void;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Client plugin body: pick the api by page mode and provide ctx.connection.
|
|
42
|
+
* @param ctx - client cordis context.
|
|
43
|
+
*/
|
|
44
|
+
export declare function apply(ctx: Context): void;
|
|
45
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Browser-safe UUID generation for client-side wire correlation. */
|
|
2
|
+
/**
|
|
3
|
+
* Generate an RFC 4122 version 4 UUID without requiring a secure context.
|
|
4
|
+
* @returns a UUID backed by `crypto.getRandomValues()`, which browsers expose on insecure origins.
|
|
5
|
+
*/
|
|
6
|
+
export declare function randomUuid(): string;
|
|
7
|
+
//# sourceMappingURL=random-uuid.d.ts.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Browser caller for generic Connection unary RPC channels. */
|
|
2
|
+
import type { ClientConnectionRpc } from '../rpc.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Create the browser-backed generic RPC caller.
|
|
5
|
+
* @returns caller that owns request correlation and response-envelope validation.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createWebConnectionRpc(): ClientConnectionRpc;
|
|
8
|
+
//# sourceMappingURL=rpc.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Browser API carrier: HTTP upstream plus one WebSocket per downstream event stream. */
|
|
2
|
+
import type { ApiProxy, HostFrame, MuxFrame, RpcRequest } from './api.ts';
|
|
3
|
+
import { AbstractApiClient } from './api.ts';
|
|
4
|
+
/** Browser platform subclass: unary/respond use fetch; mux/host use downlink-only WebSockets. */
|
|
5
|
+
export declare class WebApiClient extends AbstractApiClient {
|
|
6
|
+
protected doFetch(input: URL, init?: RequestInit): Promise<Response>;
|
|
7
|
+
protected openMux(_payload: Parameters<ApiProxy['events']['mux']>[0]['payload'], signal: AbortSignal, onOpen?: () => void): AsyncIterable<RpcRequest<MuxFrame>>;
|
|
8
|
+
protected openHost(_payload: Parameters<ApiProxy['events']['host']>[0]['payload'], signal: AbortSignal, onOpen?: () => void): AsyncIterable<RpcRequest<HostFrame>>;
|
|
9
|
+
private readWebSocket;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=web-api-client.d.ts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* node:http ↔ WHATWG fetch bridge for the /api transport (host side of the
|
|
3
|
+
* web carrier; the fetch-shaped handler itself is transport-agnostic).
|
|
4
|
+
*/
|
|
5
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
6
|
+
/** Transport-independent request handler consumed by the Host HTTP bridge. */
|
|
7
|
+
export interface FetchHandler {
|
|
8
|
+
/**
|
|
9
|
+
* Handle one standard Fetch request.
|
|
10
|
+
* @param request - request produced by the active transport bridge.
|
|
11
|
+
* @returns complete or streaming Fetch response.
|
|
12
|
+
*/
|
|
13
|
+
fetch(request: Request): Promise<Response>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Bridge one node:http request to the fetch-shaped handler (client close
|
|
17
|
+
* aborts; SSE bodies stream out chunk by chunk).
|
|
18
|
+
* @param req - incoming node:http request (fully read before dispatch).
|
|
19
|
+
* @param res - node:http response the bridge writes and owns to completion.
|
|
20
|
+
* @param apiHandler - fetch-shaped API carrier the request is dispatched to.
|
|
21
|
+
* @param maxRequestBodyBytes - maximum body bytes buffered before dispatch.
|
|
22
|
+
*/
|
|
23
|
+
export declare function bridge(req: IncomingMessage, res: ServerResponse, apiHandler: FetchHandler, maxRequestBodyBytes?: number): Promise<void>;
|
|
24
|
+
//# sourceMappingURL=http-bridge.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Host HTTP bridge for browser-client RPC. */
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
+
import z from '@deepseek-ai/schemastery';
|
|
4
|
+
export type { ConnectionRpcAuthority, ConnectionRpcEndpointMatcher, ConnectionRpcHandler, ConnectionRpcHandlerOptions, HostConnectionHandle, HostConnectionRpc, } from './rpc.ts';
|
|
5
|
+
export { HostConnectionService } from './rpc-host.ts';
|
|
6
|
+
export { API_PATH, HOST_EVENTS_PATH, MUX_EVENTS_PATH } from './api-path.ts';
|
|
7
|
+
/** Stable Cordis plugin name. */
|
|
8
|
+
export declare const name = "client-connection";
|
|
9
|
+
/** Services required before providing Connection; API Proxy is an optional `/api` fallback. */
|
|
10
|
+
export declare const inject: string[];
|
|
11
|
+
/** Plugin config: the deployment's non-loopback serving authorities. */
|
|
12
|
+
export interface ConnectionConfig {
|
|
13
|
+
/**
|
|
14
|
+
* Authorities this deployment serves beyond loopback: exact `host:port`, or
|
|
15
|
+
* port-less `host` matching any port. The /api trust fence refuses any
|
|
16
|
+
* request whose Host is neither loopback nor listed here, so a
|
|
17
|
+
* non-loopback (`0.0.0.0`) deployment must declare the names it is reached
|
|
18
|
+
* by (the dsh CLI derives the machine's LAN IP literals itself). An entry
|
|
19
|
+
* that is not a bare, canonical authority fails the plugin load.
|
|
20
|
+
*/
|
|
21
|
+
trustedHosts?: string[];
|
|
22
|
+
/** Maximum buffered JSON body for every `/api` request. */
|
|
23
|
+
maxRequestBodyBytes?: number;
|
|
24
|
+
}
|
|
25
|
+
export declare const Config: z<ConnectionConfig>;
|
|
26
|
+
/**
|
|
27
|
+
* Mounts the API gateway under the browser transport prefix. Every request on
|
|
28
|
+
* the prefix passes the browser-trust fence first (DNS-rebinding and
|
|
29
|
+
* cross-site defense — [api-request-trust](./api-request-trust.ts));
|
|
30
|
+
* privileged methods additionally pass it with an empty trust list, which
|
|
31
|
+
* pins them to loopback.
|
|
32
|
+
* @param ctx - Host plugin context.
|
|
33
|
+
* @param config - resolved plugin config (schema defaults applied).
|
|
34
|
+
*/
|
|
35
|
+
export declare function apply(ctx: Context, config?: ConnectionConfig): void;
|
|
36
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-client-connection`.
|
|
3
|
+
* @module @deepseek-ai/dsh-client-connection/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "client-connection-invariant";
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Register this package's invariant companion.
|
|
12
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
13
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
14
|
+
*/
|
|
15
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
16
|
+
//# sourceMappingURL=invariant.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-safe, zero-dependency loopback classification shared by the `/api`
|
|
3
|
+
* Host fence and the package's `ctx.connection` state. The predicate stays
|
|
4
|
+
* package-internal; client plugins consume the derived state through Cordis.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Whether a normalized URL hostname names the local loopback authority.
|
|
8
|
+
* @param hostname - WHATWG URL hostname (IPv6 literals retain brackets).
|
|
9
|
+
* @returns true for localhost, IPv6 loopback, or any IPv4 address in 127/8.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isLoopbackHostname(hostname: string): boolean;
|
|
12
|
+
//# sourceMappingURL=loopback-hostname.d.ts.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Host registry and HTTP adapter for generic Connection RPC channels. */
|
|
2
|
+
import { Context, Service } from '@deepseek-ai/cordis';
|
|
3
|
+
import { type FetchHandler } from './http-bridge.ts';
|
|
4
|
+
import type { HostConnectionHandle, HostConnectionRpc } from './rpc.ts';
|
|
5
|
+
declare module '@deepseek-ai/cordis' {
|
|
6
|
+
interface Context {
|
|
7
|
+
/** Host Connection transport and RPC registrations. */
|
|
8
|
+
connection: HostConnectionHandle;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/** Host Connection service whose channel registrations belong to the caller fiber. */
|
|
12
|
+
export declare class HostConnectionService extends Service implements HostConnectionHandle {
|
|
13
|
+
private readonly trustedHosts;
|
|
14
|
+
private readonly interceptors;
|
|
15
|
+
/**
|
|
16
|
+
* Provide the Host half over the active HTTP server.
|
|
17
|
+
* @param ctx - owning Connection plugin context.
|
|
18
|
+
* @param trustedHosts - deployment authorities accepted by trusted-host channels.
|
|
19
|
+
*/
|
|
20
|
+
constructor(ctx: Context, trustedHosts: readonly string[]);
|
|
21
|
+
/** Generic channel registry scoped to the Context reading this service. */
|
|
22
|
+
get rpc(): HostConnectionRpc;
|
|
23
|
+
/**
|
|
24
|
+
* Compose one shared-channel Fetch handler from its interceptor and fallback.
|
|
25
|
+
* @param channel - shared channel mounted by Connection.
|
|
26
|
+
* @param fallback - handler for endpoints not claimed by the interceptor.
|
|
27
|
+
* @returns Fetch handler that selects exactly one target for each request.
|
|
28
|
+
*/
|
|
29
|
+
createSharedFetchHandler(channel: '/api', fallback: FetchHandler): FetchHandler;
|
|
30
|
+
private register;
|
|
31
|
+
private registerInterceptor;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=rpc-host.d.ts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Generic unary RPC contracts shared by the Host and Client Connection halves. */
|
|
2
|
+
import type { RpcResult } from '@deepseek-ai/dsh-host-apiproxy/api';
|
|
3
|
+
/** Trust fence applied before a Host RPC channel reaches its handler. */
|
|
4
|
+
export type ConnectionRpcAuthority = 'trusted-host' | 'loopback';
|
|
5
|
+
/** Registration policy for one logical RPC channel. */
|
|
6
|
+
export interface ConnectionRpcHandlerOptions {
|
|
7
|
+
/** Browser authority accepted by every endpoint in this channel. */
|
|
8
|
+
readonly authority: ConnectionRpcAuthority;
|
|
9
|
+
}
|
|
10
|
+
/** Handler invoked after Connection has decoded the transport envelope. */
|
|
11
|
+
export type ConnectionRpcHandler = (endpoint: string, payload: unknown, signal: AbortSignal) => Promise<RpcResult<unknown>>;
|
|
12
|
+
/** Synchronous ownership test for one endpoint on a shared RPC channel. */
|
|
13
|
+
export type ConnectionRpcEndpointMatcher = (endpoint: string) => boolean;
|
|
14
|
+
/** Host registry for logical RPC channels carried by the current transport. */
|
|
15
|
+
export interface HostConnectionRpc {
|
|
16
|
+
/**
|
|
17
|
+
* Register one absolute channel prefix and its trust policy.
|
|
18
|
+
* @param channel - absolute logical channel such as `/rpc`.
|
|
19
|
+
* @param handler - decoded endpoint handler returning the existing RPC result shape.
|
|
20
|
+
* @param options - channel trust policy.
|
|
21
|
+
* @returns asynchronous disposer removing the channel and its physical route.
|
|
22
|
+
*/
|
|
23
|
+
handle(channel: string, handler: ConnectionRpcHandler, options: ConnectionRpcHandlerOptions): () => Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Intercept owned endpoints on the shared `/api` channel before its fallback.
|
|
26
|
+
* @param channel - reserved shared channel; currently `/api`.
|
|
27
|
+
* @param matches - synchronous endpoint ownership test.
|
|
28
|
+
* @param handler - decoded endpoint handler returning the existing RPC result shape.
|
|
29
|
+
* @param options - trust policy for every endpoint claimed by this interceptor.
|
|
30
|
+
* @returns asynchronous disposer removing the interceptor.
|
|
31
|
+
*/
|
|
32
|
+
intercept(channel: '/api', matches: ConnectionRpcEndpointMatcher, handler: ConnectionRpcHandler, options: ConnectionRpcHandlerOptions): () => Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
/** Host `ctx.connection` shape consumed by transport-independent adapters. */
|
|
35
|
+
export interface HostConnectionHandle {
|
|
36
|
+
/** Generic RPC channel registry. */
|
|
37
|
+
readonly rpc: HostConnectionRpc;
|
|
38
|
+
}
|
|
39
|
+
/** Client caller for logical RPC channels carried by the current transport. */
|
|
40
|
+
export interface ClientConnectionRpc {
|
|
41
|
+
/**
|
|
42
|
+
* Call one endpoint through an already registered logical channel.
|
|
43
|
+
* @param channel - absolute logical channel such as `/api`.
|
|
44
|
+
* @param endpoint - channel-relative endpoint such as `goals/create`.
|
|
45
|
+
* @param payload - channel-owned request payload.
|
|
46
|
+
* @param signal - optional caller cancellation.
|
|
47
|
+
* @returns the existing RPC success/error result; correlation stays inside Connection.
|
|
48
|
+
*/
|
|
49
|
+
call(channel: string, endpoint: string, payload: unknown, signal?: AbortSignal): Promise<RpcResult<unknown>>;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=rpc.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Host-side WebSocket carrier for the two server-to-browser event streams. */
|
|
2
|
+
import type { IncomingMessage } from 'node:http';
|
|
3
|
+
import type { Duplex } from 'node:stream';
|
|
4
|
+
import type { ApiProxy } from '@deepseek-ai/dsh-host-apiproxy/api';
|
|
5
|
+
/**
|
|
6
|
+
* Owns WebSocket negotiation and frame pumping for the connection plugin's
|
|
7
|
+
* two downlinks. Client messages are a protocol violation: upstream traffic
|
|
8
|
+
* remains on HTTP.
|
|
9
|
+
*/
|
|
10
|
+
export declare class WebSocketDownlinks {
|
|
11
|
+
private readonly api;
|
|
12
|
+
private readonly server;
|
|
13
|
+
private readonly pumps;
|
|
14
|
+
/** @param api - host API supplying the typed event streams. */
|
|
15
|
+
constructor(api: ApiProxy);
|
|
16
|
+
/**
|
|
17
|
+
* Upgrade one socket and pump the mux stream until either side closes.
|
|
18
|
+
* @param req - HTTP upgrade request.
|
|
19
|
+
* @param socket - Raw socket transferred by the HTTP server.
|
|
20
|
+
* @param head - Bytes already read after the upgrade headers.
|
|
21
|
+
*/
|
|
22
|
+
handleMux(req: IncomingMessage, socket: Duplex, head: Buffer): void;
|
|
23
|
+
/**
|
|
24
|
+
* Upgrade one socket and pump the host stream until either side closes.
|
|
25
|
+
* @param req - HTTP upgrade request.
|
|
26
|
+
* @param socket - Raw socket transferred by the HTTP server.
|
|
27
|
+
* @param head - Bytes already read after the upgrade headers.
|
|
28
|
+
*/
|
|
29
|
+
handleHost(req: IncomingMessage, socket: Duplex, head: Buffer): void;
|
|
30
|
+
/**
|
|
31
|
+
* Terminate owned sockets and await the no-server acceptor plus frame pumps.
|
|
32
|
+
* @returns A promise resolving after every socket and source iterator stops.
|
|
33
|
+
*/
|
|
34
|
+
close(): Promise<void>;
|
|
35
|
+
private upgrade;
|
|
36
|
+
private pump;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Reject an untrusted upgrade before protocol negotiation.
|
|
40
|
+
* @param socket - Raw HTTP socket that remains owned by the caller.
|
|
41
|
+
*/
|
|
42
|
+
export declare function rejectWebSocketUpgrade(socket: Duplex): void;
|
|
43
|
+
//# sourceMappingURL=websocket-downlink.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deepseek-ai/dsh-client-connection",
|
|
3
|
+
"description": "Wire consumer layer: HTTP-up/WebSocket-down client, ConnectionController dual streams with reconnect, and fixture api",
|
|
4
|
+
"version": "0.0.1-rc.1",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "restricted"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/client/connection"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./invariant": {
|
|
22
|
+
"types": "./lib/types/invariant.d.ts",
|
|
23
|
+
"default": "./lib/invariant.js"
|
|
24
|
+
},
|
|
25
|
+
"./client": {
|
|
26
|
+
"types": "./lib/types/client/index.d.ts",
|
|
27
|
+
"default": "./lib/client.js"
|
|
28
|
+
},
|
|
29
|
+
"./src/*": "./src/*",
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
"dsh": {
|
|
33
|
+
"client": {
|
|
34
|
+
"inject": [],
|
|
35
|
+
"platform": "web",
|
|
36
|
+
"immediately": true
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"license": "BSD-3-Clause",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"ws": "^8.21.0",
|
|
42
|
+
"@deepseek-ai/dsh-host-apiproxy": "^0.0.1-rc.1",
|
|
43
|
+
"@deepseek-ai/dsh-commands": "^0.0.1-rc.1",
|
|
44
|
+
"@deepseek-ai/dsh-attachment": "^0.0.1-rc.1",
|
|
45
|
+
"@deepseek-ai/dsh-session": "^0.0.1-rc.1",
|
|
46
|
+
"@deepseek-ai/dsh-tools": "^0.0.1-rc.1",
|
|
47
|
+
"@deepseek-ai/schemastery": "^3.18.1-rc.1",
|
|
48
|
+
"@deepseek-ai/dsh-llm": "^0.0.1-rc.1"
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"lib/index.js",
|
|
52
|
+
"lib/invariant.js",
|
|
53
|
+
"lib/client.js",
|
|
54
|
+
"lib/types/**/*.d.ts"
|
|
55
|
+
],
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
|
|
58
|
+
"@deepseek-ai/dsh-host-webserver": "^0.0.1-rc.1",
|
|
59
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@types/ws": "^8.18.1",
|
|
63
|
+
"@deepseek-ai/dsh-host-webserver": "^0.0.1-rc.1",
|
|
64
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.1",
|
|
65
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1"
|
|
66
|
+
}
|
|
67
|
+
}
|