import { $ZodType, output } from "zod/v4/core"; //#region src/protocol/jsonValue.d.ts /** * The JSON value lattice. Anything the wire can carry. * * Object values are `JsonValue | undefined` so `undefined`-valued fields * may be present in source and are simply omitted at canonicalisation * time (rather than serialised as `null`). */ type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue | undefined; }; //#endregion //#region src/protocol/jsonRpc.d.ts type RequestId = number | string; interface JsonRpcRequest { jsonrpc: "2.0"; id: RequestId; method: string; params?: TParams; } interface JsonRpcNotification { jsonrpc: "2.0"; method: string; params?: TParams; } interface JsonRpcSuccess { jsonrpc: "2.0"; id: RequestId | null; result: JsonValue; } interface JsonRpcError { jsonrpc: "2.0"; id: RequestId | null; error: { code: number; message: string; data?: JsonValue; }; } type JsonRpcResponse = JsonRpcSuccess | JsonRpcError; type JsonRpcMessage = JsonRpcRequest | JsonRpcNotification | JsonRpcResponse; declare const ErrorCode: { readonly parseError: -32700; readonly invalidRequest: -32600; readonly methodNotFound: -32601; readonly invalidParams: -32602; readonly internalError: -32603; /** Caller is authenticated but lacks a capability covering the call. */ readonly permissionRequired: -32401; /** The peer a request was routed to detached before it could respond. */ readonly peerDisconnected: -32402; /** The request exceeded the hub's idle timeout with no stream activity. */ readonly requestTimeout: -32403; /** The request was cancelled (by the caller, or by the hub on disconnect). */ readonly cancelled: -32800; }; declare function isRequest(m: JsonRpcMessage): m is JsonRpcRequest; declare function isNotification(m: JsonRpcMessage): m is JsonRpcNotification; declare function isResponse(m: JsonRpcMessage): m is JsonRpcResponse; //#endregion //#region src/schema/hubRpcJsonSchema.d.ts type HubRpcJsonSchema = true | false | NullSchema | BooleanSchema | NumberSchema | IntegerSchema | StringSchema | ConstSchema | EnumSchema | ArraySchema | TupleSchema | ObjectSchema | UnionSchema | OneOfSchema | RefSchema; interface SchemaBase { title?: string; description?: string; } interface NullSchema extends SchemaBase { type: "null"; } interface BooleanSchema extends SchemaBase { type: "boolean"; } interface NumberSchema extends SchemaBase { type: "number"; /** Opaque well-known refinement tag, e.g. "float32", "percentage". */ format?: string; } interface IntegerSchema extends SchemaBase { type: "integer"; /** Opaque well-known refinement tag, e.g. "int32", "uint8", "unix-time". */ format?: string; } interface StringSchema extends SchemaBase { type: "string"; /** Opaque well-known refinement tag, e.g. "email", "uri", "uuid", "date-time". */ format?: string; } /** Single literal value. */ interface ConstSchema extends SchemaBase { const: JsonValue; } /** Finite set of literal values. */ interface EnumSchema extends SchemaBase { enum: JsonValue[]; } /** Homogeneous array. */ interface ArraySchema extends SchemaBase { type: "array"; items: HubRpcJsonSchema; } /** Fixed-length head plus optional rest element type. */ interface TupleSchema extends SchemaBase { type: "array"; prefixItems: HubRpcJsonSchema[]; /** `false` => exact length; schema => typed rest; omitted => exact length. */ items?: HubRpcJsonSchema | false; } interface ObjectSchema extends SchemaBase { type: "object"; properties: Record; /** Names of required properties. Must all be keys of `properties`. */ required?: string[]; /** `false` => closed; schema => value type for unknown keys. */ additionalProperties: HubRpcJsonSchema | false; } /** Untagged union. Assignability distributes over branches. */ interface UnionSchema extends SchemaBase { anyOf: HubRpcJsonSchema[]; } /** * Tagged-or-disjoint union (`oneOf`). Structurally treated the same as * `UnionSchema` for assignability; the distinction is preserved so the * wire schema faithfully reflects whether the source was a * discriminated union (e.g. Zod's `z.discriminatedUnion`) or a plain * union. * * `discriminator`, when present, is a pure consumer hint: it names the * property that branches dispatch on. Every branch SHOULD be an object * with that property set to a distinct `const` value, but the subset * does not enforce this — broken discriminators are tolerated. */ interface OneOfSchema extends SchemaBase { oneOf: HubRpcJsonSchema[]; discriminator?: DiscriminatorSchema; } interface DiscriminatorSchema { propertyName: string; } /** Reference to a named entry in `SvcInterfaceSchema.components.schemas`. */ interface RefSchema extends SchemaBase { /** JSON Pointer, restricted to "#/components/schemas/". */ $ref: string; } //#endregion //#region src/schema/hubRpcInterfaceSchema.d.ts /** * Minimal interface schema for hubrpc. A strict subset of OpenRPC 1.x: * identity & addressing live in hubrpc, so this format only describes the * contract of a single interface (methods + reusable JSON Schemas). */ interface HubRpcInterfaceSchema { /** Stable interface id, e.g. "de.hediet.notification-target". */ id: string; /** * Content hash of the normalized schema. Pairs with `id` to form `id@hash`. * Normalization: serialize this object with `hash` omitted, object keys * sorted recursively, and no insignificant whitespace; hash the bytes. */ hash: string; /** * Normative human description (GitHub-flavored markdown). Part of the * interface hash — changing `description` is a contract change. Use it * for the canonical purpose / semantics of the interface. */ description?: string; /** * Non-normative implementation notes (markdown). Stripped from the hash, * so editing `comment` never changes the interface identity. Use it for * rationale, changelog notes, examples, etc. */ comment?: string; /** Methods keyed by local member name. */ methods: Record; /** Reusable schema definitions, referenced via `#/components/schemas/`. */ components?: { schemas?: Record; }; } interface MethodSchema { /** Schema for the user params object. */ params: HubRpcJsonSchema; /** Result schema. Omit to declare a notification-only method. */ result?: HubRpcJsonSchema; /** * Schema for client-emitted stream messages (`$stream::send` from * caller to callee) on an in-flight call. Absent means the client * may not stream on this method. */ clientStream?: HubRpcJsonSchema; /** * Schema for server-emitted stream messages (`$stream::send` from * callee to caller) on an in-flight call. Absent means the server * may not stream on this method. */ serverStream?: HubRpcJsonSchema; /** Application-level errors. Codes MUST be unique. */ errors?: ErrorSchema[]; summary?: string; /** * Normative description of this method's contract (markdown). Part of * the interface hash — changing it is a contract change. */ description?: string; /** Non-normative implementation notes. Stripped from the hash. */ comment?: string; deprecated?: boolean; /** * Behavioral claims about this method. All flags default to `false`; * setting one is always a positive refinement of the contract. * Part of the interface hash — these are normative claims callers * may rely on, so changing them is a contract change. */ annotations?: MemberAnnotations; } /** * Behavioral claims about a method. Every flag is a positive assertion * (default `false` ≡ "no claim"); setting one strengthens the contract * the caller may rely on. * * These are normative — they are included in the interface hash. */ interface MemberAnnotations { /** * The method does not modify any observable state on the callee * (or anywhere reachable from it). Pure query. * * Implies `idempotent` and `reversible` (a no-op has nothing to * undo and repeats trivially). */ readOnly?: boolean; /** * Calling N times with the same params has the same observable effect * as calling once. Safe to retry on transport failure. */ idempotent?: boolean; /** * Effects of this method are reversible — the caller (or operator) * can undo them with a follow-up call. Implies the method is not * `dangerous`. */ reversible?: boolean; /** * Calling this method is expensive (slow, costly, or rate-limited). * Callers should avoid unnecessary invocations and may want to * confirm / batch. */ expensive?: boolean; /** * Method has irreversible or destructive effects (data loss, money * spent, message sent, etc.). UIs should require confirmation. */ dangerous?: boolean; } interface ErrorSchema { /** JSON-RPC error code. -32768..-32000 are reserved. */ code: number; message: string; /** Optional schema describing the shape of `error.data`. */ data?: HubRpcJsonSchema; } //#endregion //#region src/schema/memberTypes.d.ts /** * The schema type hubrpc accepts everywhere: the zod *core* base shared by * both classic `zod` and `zod/mini`. Typing against it (instead of classic * `z.ZodType`) lets callers author interfaces with either flavour — classic * for ergonomics, mini for minimal bundle size — while hubrpc's own internal * interfaces use mini. `O` is the validated output type. */ type Schema = $ZodType; /** * Marker base for a typed method member. Carries enough metadata to: * - validate / parse params at runtime (via the zod schemas), * - emit a JSON schema for reflection, * - propagate TS types up to the interface definition. */ type MemberType = RequestType | NotificationType; /** * Optional documentation for a method member. * * - `description` is the **normative** contract of the method (included in the * interface hash — changing it changes `schemaHash`). * - `comment` is non-normative implementation notes (stripped from the hash). * - `annotations` are normative behavioral claims (e.g. `readOnly`, * `idempotent`); included in the hash. */ interface MemberDocs { description?: string; comment?: string; annotations?: MemberAnnotations; } declare class RequestType { readonly paramsSchema: Schema; readonly resultSchema: Schema; readonly errorSchema: Schema; readonly docs: MemberDocs; /** * Schema for stream messages the **client** may emit on an * in-flight call (e.g. cancellation, input). `undefined` means * the client may not stream on this method. */ readonly clientStreamSchema?: Schema | undefined; /** * Schema for stream messages the **server** may emit while * handling an in-flight call (e.g. progress, partial results). * `undefined` means the server may not stream on this method. */ readonly serverStreamSchema?: Schema | undefined; readonly kind: "request"; constructor(paramsSchema: Schema, resultSchema: Schema, errorSchema: Schema, docs?: MemberDocs, /** * Schema for stream messages the **client** may emit on an * in-flight call (e.g. cancellation, input). `undefined` means * the client may not stream on this method. */ clientStreamSchema?: Schema | undefined, /** * Schema for stream messages the **server** may emit while * handling an in-flight call (e.g. progress, partial results). * `undefined` means the server may not stream on this method. */ serverStreamSchema?: Schema | undefined); /** Phantom field — typed-only, do not access at runtime. */ readonly _params: TParams; readonly _result: TResult; readonly _error: TError; readonly _clientStream: TClientStream; readonly _serverStream: TServerStream; /** * Return a copy of this request type with stream payload schemas * attached. Pass `undefined` for either direction to leave it * closed. */ withStream(opts: { client?: Schema; server?: Schema; }): RequestType; } declare class NotificationType { readonly paramsSchema: Schema; readonly docs: MemberDocs; readonly kind: "notification"; constructor(paramsSchema: Schema, docs?: MemberDocs); readonly _params: TParams; } /** * Define a request method. * * @example * bar: requestType(z.object({ to: z.string() }), z.string(), { * description: "MUST resolve before the next call from the same caller.", * }) */ declare function requestType(params: Schema, result?: Schema, docsOrError?: MemberDocs | Schema, maybeDocs?: MemberDocs): RequestType; /** * Define a notification method. * * @example * foo: notificationType(z.object({ message: z.string() })) */ declare function notificationType(params: Schema, docs?: MemberDocs): NotificationType; /** Convert a zod schema to our restricted SvcJsonSchema subset. */ declare function zodToSvcJsonSchema(schema: Schema): HubRpcJsonSchema; //#endregion //#region src/connection/interfaceDefinition.d.ts /** * Per-call stream API handed to a request handler as its third argument. * Allows the handler to consume {@link STREAM_METHOD} notifications * emitted by the caller (`onMessage`) and emit stream notifications to * the caller (`send`) while the request is in flight. * * Members are typed by the originating method's * {@link RequestType.clientStreamSchema} / `serverStreamSchema`. * Streams whose schema is `undefined` make the corresponding member * effectively unusable: `send` is typed `(payload: never) => void` and * the registered `onMessage` listener can never fire because the * receiver-side validator rejects the wire payload. */ interface StreamApi { /** Emit a server→client stream message. */ send(payload: TServer): void; /** * Register a listener for client→server stream messages on this * request. Calling twice replaces the previous listener. */ onMessage(listener: (payload: TClient) => void): void; /** * Liveness probe toward the caller: resolves once the caller * acknowledges with a matching pong, rejects if the call settles * first. Independent of the channel's automatic keepalive ping. */ ping(): Promise; /** * Aborts when the caller cancels this in-flight request, or when the * hub cancels it on the caller's behalf (caller disconnect / idle * timeout). Observe it to stop work early and settle the request * (e.g. `signal.throwIfAborted()`), which surfaces as a `cancelled` * error to the caller. */ readonly signal: AbortSignal; } interface InterfaceInfo { id: string; /** * Normative description of the interface (markdown). Part of the * interface hash — changing it is a contract change. */ description?: string; /** Non-normative implementation notes. Stripped from the hash. */ comment?: string; /** * Optional expected content hash (see `computeInterfaceHash`). When * set, {@link InterfaceDefinition}'s constructor verifies that the * computed {@link InterfaceDefinition.schemaHash} matches and throws * otherwise — a guard against silent contract drift when the wire * shape changes but a pinned hash was not updated. */ hash?: string; } type MemberMap = Record; /** * Per-call options accepted by a streaming-enabled client method. Currently * only `onMessage` for consuming server→client stream notifications; the * caller addresses client→server stream messages via the returned * {@link StreamingCall}. */ interface StreamCallOptions { /** Listener for server→client stream messages on this in-flight call. */ onMessage?: (payload: TServer) => void; } /** * Return shape of a streaming-enabled client method. Behaves as a * `Promise` for the final response, and exposes `send` for * emitting client→server stream messages on the in-flight call. The * wire request id is also exposed (resolved once the request has * been allocated). * * For methods whose interface schema declares no client stream * (`TClient = never`), `send` is typed `(payload: never) => Promise` * and is effectively uncallable — matching the runtime behavior, where * the receiver would drop client-emitted stream messages anyway. */ interface StreamingCall extends Promise { /** Resolves to the wire request id once the call has been sent. */ readonly requestId: Promise; /** Emit a client→server stream message on this in-flight call. */ send(payload: TClient): Promise; /** * Ask the callee to abort this in-flight call. Advisory: the call * settles via its normal response (typically a `cancelled` error). * `reason` is an open-set diagnostic string (see `StreamControlReason`). */ cancel(reason?: string): Promise; /** * Liveness probe toward the callee: resolves once the callee * acknowledges with a matching pong, rejects if the call settles * first. Independent of the channel's automatic keepalive ping. */ ping(): Promise; } /** * `true` iff at least one stream direction is declared (either client or * server stream schema present). Used by {@link InterfaceClient} to pick * between the plain `Promise` shape and the {@link StreamingCall} * shape per method. */ type _HasStream = [TClient] extends [never] ? ([TServer] extends [never] ? false : true) : true; /** * Compile-time TypeScript shape of an interface — useful for typed * client/server implementations on top of the runtime definition. * * Methods whose `RequestType` declares stream payloads via * `.withStream({ client, server })` return a {@link StreamingCall} * augmented with `send`; all others return a plain `Promise`. * * @example * type Client = InterfaceClient; * // => { bar(p: {...}): Promise; foo(p: {...}): void; } */ type InterfaceClient> = { [K in keyof TDef['members']]: TDef['members'][K] extends RequestType ? (_HasStream extends true ? (params: P, opts?: StreamCallOptions) => StreamingCall : (params: P) => Promise) : TDef['members'][K] extends NotificationType ? (params: P) => void : never; }; /** * Compile-time shape of a server implementation for an interface — used by * `HubRpcConnection.register`. Request handlers may return synchronously or * via a promise; notification handlers return void. * * `TCtx` is the call-context type carried by the hosting connection — see * `HubRpcConnection`. For the default connection (`TCtx = undefined`) * handlers may take a single params arg; for ctx-aware connections (e.g. * the hub's self connection) they may take a second `ctx` arg of the * concrete type. A 1-arg handler remains assignable where a 2-arg handler * is expected, so existing handlers compile unchanged. */ type InterfaceHandlers, TCtx = undefined> = { [K in keyof TDef['members']]: TDef['members'][K] extends RequestType ? (params: P, ctx: TCtx, stream: StreamApi) => R | Promise : TDef['members'][K] extends NotificationType ? (params: P, ctx: TCtx) => void | Promise : never; }; /** * Options accepted by {@link InterfaceDefinition}'s constructor. * * `frozenSchema` lets callers supply an externally-authored * `HubRpcInterfaceSchema` (e.g. produced by codegen or, in the faker, by an * LLM at runtime) verbatim. When set, `toSchema()` returns that document * (with `hash` overlaid) and `schemaHash` is computed from it — the * `members` map is used only for runtime dispatch (param/result * validation, stream routing) and is no longer the source of truth for * the wire shape. */ interface InterfaceDefinitionOpts { frozenSchema?: HubRpcInterfaceSchema; } declare class InterfaceDefinition { readonly info: InterfaceInfo; readonly members: TMembers; constructor(info: InterfaceInfo, members: TMembers, opts?: InterfaceDefinitionOpts); /** Content hash of this interface (see `computeInterfaceHash`). */ get schemaHash(): string; /** Lower the definition to a wire-format `HubRpcInterfaceSchema`, hash filled in. */ toSchema(): HubRpcInterfaceSchema; } /** * Convenient builder for an interface definition. Tracks TypeScript types * through `requestType` / `notificationType` so client and server code can * derive their shapes from the definition. * * @example * const myInterface = defineInterface( * { id: "de.hediet.notification-target" }, * { * send: requestType(z.object({ to: z.string() }), z.string()), * notify: notificationType(z.object({ message: z.string() })), * }, * ); */ declare function defineInterface(info: InterfaceInfo, members: TMembers): InterfaceDefinition; /** * Build a runtime {@link InterfaceDefinition} from a previously-published * {@link HubRpcInterfaceSchema} — typically one received over the wire (e.g. * from `hubrpc.schemas::get`) or generated at runtime by tooling that does * not have the original zod sources at hand (codegen, faker / mock * services, dynamic gateways). * * The original schema is kept verbatim: `toSchema()` returns it (with * `hash` overlaid) and `schemaHash` is computed from it, so reflection * consumers see the real wire contract. Member-level params / results / * streams use `z.any()` because no zod source is available — call-site * validation is therefore a no-op and the caller is responsible for * shape-checking inputs and outputs. * * Methods with no `result` descriptor become notifications; methods with * `clientStream` / `serverStream` get pass-through stream payload * schemas attached. */ declare function interfaceFromSchema(schema: HubRpcInterfaceSchema): InterfaceDefinition; //#endregion //#region src/schema/hash.d.ts /** * Compute the interface hash: SHA-256 of the canonicalized schema, truncated * to 16 hex chars (64 bits of collision budget per id). * * Canonicalization: * 1. Strip every `comment` field (non-normative — must not affect identity). * `description` is NORMATIVE and kept in the hash. * 2. Omit the top-level `hash` field itself. * 3. RFC 8785 JCS encode (recursive key sort, no whitespace) via {@link jcsCanonicalize}. */ declare function computeInterfaceHash(schema: HubRpcInterfaceSchema): string; //#endregion //#region src/schema/normalize.d.ts /** * Normalize raw JSON Schema output (e.g. from `z.toJSONSchema`) into the * SvcJsonSchema subset: * * - drop annotation keys (`examples`, `default`, `$comment`, `readOnly`, ...) * - drop out-of-subset refinements (`pattern`, `minimum`, `multipleOf`, * `allOf`, `oneOf`, `if/then/else`, `patternProperties`, ...) * - empty schema `{}` ⇒ `true` (top) * - `{"not":{}}` ⇒ `false` (bottom); any other `not` is stripped * - `type:"object"` without `additionalProperties` ⇒ closed (`false`), * matching hubrpc's stricter contract * * The output is canonical: structurally equal inputs produce structurally * equal outputs, which is what `computeInterfaceHash` relies on. */ declare function normalizeJsonSchema(raw: unknown): HubRpcJsonSchema; //#endregion //#region src/schema/assignability.d.ts /** * Structural subtype check: returns `true` iff every JSON value matching * `sub` also matches `sup`, over the SvcJsonSchema subset. * * Cycles introduced by `$ref` are broken by assuming the recursive case * holds (standard coinductive subtyping). This is sound for productive * (non-degenerate) schemas; pathological inputs are out of scope. */ declare function isAssignable(sub: HubRpcJsonSchema, sup: HubRpcJsonSchema, components?: Components): boolean; interface Components { schemas?: Record; } //#endregion //#region src/schema/codegen/generateTsInterface.d.ts interface GenerateInterfaceOptions { /** * Module specifier from which `defineInterface`, `requestType`, * `notificationType` are imported. Defaults to `@vscode/hubrpc`. */ hubRpcImport?: string; /** * Identifier for the exported `InterfaceDefinition` const. Defaults to * a sanitized form of the interface id with an `Interface` suffix * (e.g. `hubrpc.directory` → `hubRpcDirectoryInterface`). */ exportName?: string; } /** * Render a {@link HubRpcInterfaceSchema} as a stand-alone TypeScript source * file that, when evaluated, reproduces the same canonical schema (and * therefore the same `schemaHash`). * * Components in `components.schemas` are emitted as named `const` * declarations referenced from the bodies via `$ref`. */ declare function generateTsInterface(schema: HubRpcInterfaceSchema, options?: GenerateInterfaceOptions): string; //#endregion //#region src/connection/streaming.d.ts /** * Direction a {@link STREAM_METHOD} message travels, relative to the * request it is correlated with. Made explicit on the wire so that a * middlebox (the hub) can *author* a stream message — e.g. inject a * cancel when a caller disconnects — without an inbound message whose * arrival link it could infer the direction from. */ declare const StreamDir: { /** callee → caller: progress, partial results. Routes like a response. */ readonly toCaller: "toCaller"; /** caller → callee: input, cancellation, keepalive ping. */ readonly toCallee: "toCallee"; }; type StreamDir = (typeof StreamDir)[keyof typeof StreamDir]; /** * Reserved control verbs. A control message carries no app `payload`; it * is interpreted by the runtime / hub itself, independently of the * originating method's stream schemas, so *any* request is cancellable * and keep-alive-able even when it declares no app stream. */ declare const StreamControlType: { /** * Ask the callee to abort the in-flight request. Always {@link * StreamDir.toCallee}. The callee surfaces this as an `AbortSignal` * and is expected to settle the request (typically with a * `cancelled` error response). */ readonly cancel: "cancel"; /** * Keepalive / liveness probe. Resets the hub's per-request idle timer * so a long-running call is not reaped, and lets either side actively * probe the peer. Carries a `nonce` the peer echoes in its {@link * StreamControlType.pong}. May travel in either direction. Emitted * automatically (without awaiting a pong) by the channel for streaming * calls; see {@link STREAM_METHOD}. */ readonly ping: "ping"; /** * Reply to a {@link StreamControlType.ping}, echoing the ping's * `nonce` so the prober can correlate it. Travels opposite the ping. * Carries no app effect — purely a liveness acknowledgement. */ readonly pong: "pong"; }; type StreamControlType = (typeof StreamControlType)[keyof typeof StreamControlType]; /** * Well-known `control.reason` strings. The set is **open** — any string * is valid on the wire; these are the reasons hubrpc itself emits. */ declare const StreamControlReason: { /** * The caller's transport dropped while the request was in flight; the * hub cancels the callee's now-orphaned work. */ readonly clientDisconnected: "clientDisconnected"; /** * The request exceeded the hub's idle timeout with no stream activity * (no ping, no stream message). See {@link STREAM_METHOD} docs. */ readonly idleTimeout: "idleTimeout"; }; type StreamControlReason = (typeof StreamControlReason)[keyof typeof StreamControlReason]; /** * Streaming sub-protocol. Long-running requests can emit additional * notifications correlated with the original call in either direction * (callee → caller progress, caller → callee input / cancellation / * keepalive). * * Wire form: a JSON-RPC notification whose method is * `${streamInterface.info.id}::send` and whose params match * `streamInterface.members.send.paramsSchema`. The hub routes purely on * `requestId` (following the originating request's established path, like * a response); the originating request's capability authorization covers * its full stream lifetime, so stream notifications carry no method / * interface namespace of their own. App `payload` contents are typed per * request via the originating method's * `RequestType.clientStreamSchema` / `serverStreamSchema`; reserved * `control` messages are schema-independent. * * Idle timeout: a request that never streams (no `control` ping, no * stream message) is subject to the hub's per-request idle timeout * (default 30 minutes) — at which point it is cancelled and its caller * gets a `requestTimeout` error. This bounds the hub's pending-request * table, which is also what protects it from a slow-loris / DDoS that * opens requests and never completes them. Streaming-enabled calls keep * themselves alive by emitting a periodic {@link StreamControlType.ping}. * * The interface is declarative: nobody calls * `HubRpcConnection.register(streamInterface, ...)`. The channel and the * hub intercept `$stream::send` directly. The definition exists so * reflection / consent UIs can describe the wire shape and so all wire * constants derive from one source. */ declare const streamInterface: InterfaceDefinition<{ send: NotificationType<{ requestId: string | number; dir: "toCaller" | "toCallee"; control?: { type: "cancel" | "ping" | "pong"; reason?: string | undefined; nonce?: string | undefined; } | undefined; payload?: unknown; }>; }>; /** * Full wire method name for stream notifications. Pulled from the * interface object so we have a single source of truth. */ declare const STREAM_METHOD: `${string}::send`; /** Wire-level shape carried on `params` of {@link STREAM_METHOD}. */ type StreamSendParams = output; //#endregion //#region src/protocol/jcs.d.ts /** * RFC 8785 JSON Canonicalization Scheme (JCS). * * Used as the byte-deterministic encoding under every hubrpc signature * (RPC calls, capabilities, hub-signed previews). Both signer and verifier * canonicalize the same value object and obtain byte-identical UTF-8 bytes. * * Implementation inlined from the `canonicalize` npm package (Apache-2.0, * https://github.com/erdtman/canonicalize) so this module stays * zero-dependency for browser bundlers that don't resolve transitive * npm specifiers (e.g. the in-house app-bundler). */ /** RFC 8785 canonical JSON string for `value`. */ declare function jcsCanonicalize(value: unknown): string; /** UTF-8 bytes of the RFC 8785 canonical JSON for `value`. The thing actually signed / hashed. */ declare function jcsCanonicalizeBytes(value: unknown): Uint8Array; //#endregion //#region src/crypto/cryptoProvider.d.ts /** * Identity primitives. hubrpc itself is identity-agnostic; this module * defines the shared types. No crypto code lives here — the crypto API is * `./crypto` (backed by the Web Crypto implementation in * `./ed25519CryptoProvider`). */ /** Raw Ed25519 public key (32 bytes). */ type PublicKey = Uint8Array; /** Raw Ed25519 private key (32 bytes — seed form). */ type PrivateKey = Uint8Array; /** Raw Ed25519 signature (64 bytes). */ type Signature = Uint8Array; /** * A **principal**: an identity's stable, opaque name. In Phase 1 every * principal is *perpetual* and self-describing — its genesis signing key is * embedded verbatim: * * principal = "id:" + keyId (e.g. "id:key:") * * So the genesis check is pure string equality (`"id:" + keyId === principal`) * and no key-binding records are ever needed. Treat as opaque; construct via * {@link principalForPublicKey} and resolve a verifying key via * {@link resolveSigningKey}. */ type PrincipalId = string; /** * A **keyId**: names the signing key that produced a signature. In Phase 1 the * only form is an *inline* key — the public key encoded verbatim: * * keyId = "key:" + b64url(pubKey) * * (Future forms, e.g. `"keydoc:" + b64url(sha256(jcs(doc)))`, are resolved * through bindings — see the design doc — but Phase 1 needs none.) */ type KeyId = string; interface Keypair { readonly publicKey: PublicKey; readonly privateKey: PrivateKey; } /** Raw X25519 keypair. Both halves are 32 bytes. */ interface X25519Keypair { readonly publicKey: Uint8Array; readonly privateKey: Uint8Array; } declare function bytesToBase64Url(bytes: Uint8Array): string; declare function base64UrlToBytes(s: string): Uint8Array; /** Role-tag prefix on a {@link KeyId}'s inline-key form. */ declare const KEY_ID_PREFIX = "key:"; /** Role-tag prefix that wraps a genesis {@link KeyId} into a {@link PrincipalId}. */ declare const PRINCIPAL_PREFIX = "id:"; /** The inline {@link KeyId} for a raw public key: `"key:" + b64url(pk)`. */ declare function keyIdForPublicKey(pk: PublicKey): KeyId; /** The perpetual {@link PrincipalId} for a raw public key: `"id:key:" + b64url(pk)`. */ declare function principalForPublicKey(pk: PublicKey): PrincipalId; /** * The genesis {@link KeyId} embedded in a perpetual {@link PrincipalId} * (`"id:" + keyId`). Throws if `principal` is not a well-formed `id:` name. */ declare function keyIdForPrincipal(principal: PrincipalId): KeyId; /** * The raw public key carried inline by a `"key:"` {@link KeyId}. Throws on a * missing prefix or malformed base64url. */ declare function publicKeyForKeyId(keyId: KeyId): PublicKey; /** Arguments to {@link resolveSigningKey}. */ interface ResolveSigningKeyArgs { /** The identity the signature claims to be from. */ readonly principal: PrincipalId; /** Which key (per `$hubrpcSignature[domain].keyId`) is claimed to have signed. */ readonly keyId: KeyId; /** Presented key-binding records. Unused in Phase 1 (perpetual ids need none). */ readonly bindings?: Record; /** Time the signature was made (e.g. `signedAtMs`). Unused in Phase 1. */ readonly timeMs?: number; } /** * Resolve the verifying public key for `(principal, keyId)`, or `undefined` * to **reject** (fail closed). This is the single seam every verifier goes * through, replacing the old "the id *is* the key" decode. * * Phase 1 supports only inline genesis keys: the `keyId` must be a `"key:"` * form and the principal must be exactly `"id:" + keyId` (genesis * self-certification by string equality). `keydoc:`/rotation forms resolve * through `bindings`/`timeMs` later and are intentionally not handled here. */ declare function resolveSigningKey(args: ResolveSigningKeyArgs): { publicKey: PublicKey; } | undefined; //#endregion //#region src/protocol/signedObject.d.ts /** An open signature domain. Each gets a distinct {@link signingDomainValue}. */ type SignDomain = string; /** Reserved wire-key: the signature map `{ [domain]: SignatureEnvelope }`. */ declare const HUBRPC_SIGNATURE_KEY = "$hubrpcSignature"; /** Reserved wire-key: extrinsic unsigned attachments (e.g. the capability bag). */ declare const HUBRPC_UNSIGNED_KEY = "$hubrpcUnsigned"; /** Reserved wire-key: signed call meta, present only on calls (dodges the JSON-RPC param namespace). */ declare const HUBRPC_META_KEY = "$hubrpc"; /** * One domain's signature on the wire: which key signed ({@link KeyId}, an * **unsigned** routing hint) plus the raw `base64url(sig)`. Lying about * `keyId` only makes verification fail (the resolver rejects or the key * mismatches), so it carries no integrity claim and is excluded from the * signed bytes along with the rest of `$hubrpcSignature`. */ interface SignatureEnvelope { /** The key the signer used (`"key:..."`). Resolved against the principal. */ readonly keyId: KeyId; /** `base64url` of the raw signature over {@link signingInput}. */ readonly sig: string; } /** Per-domain signature map carried under {@link HUBRPC_SIGNATURE_KEY}. */ type Signatures = { readonly [D in SignDomain]?: SignatureEnvelope; }; /** Documentary brand for `base64url(sha256(signingInput(domain, T)))`. */ type Base64Sha256 = string & { readonly __sha256Of?: T; }; /** * The domain-separation key. It includes the frozen signature-suite version * and becomes the sole key of the object passed to JCS. */ declare function signingDomainValue(domain: SignDomain): string; /** * THE bytes a signature commits to (and a content hash hashes) for `obj` in * `domain`: `jcs({ [domainValue(domain)]: obj minus the two reserved keys })`. */ declare function signingInput(domain: SignDomain, obj: object): Uint8Array; /** * Content identity of a signed object: `base64url(sha256(signingInput(...)))`. * The single operation behind both a capability's `callBind.payloadHash` * (domain `"call"`) and a child capability's `parentHash` (domain * `"capability"`). */ declare function signedHash(domain: SignDomain, obj: T): Base64Sha256; /** Read the signature envelope for `domain` off an object's `$hubrpcSignature` map. */ declare function readSignature(obj: object, domain: SignDomain): SignatureEnvelope | undefined; /** The {@link KeyId} the signer used for `domain` (the unsigned routing hint), if any. */ declare function getKeyId(obj: object, domain: SignDomain): KeyId | undefined; /** * Return a copy of `obj` with `$hubrpcSignature[domain]` set to `envelope`, * preserving any sibling-domain signatures already present. */ declare function withSignature(obj: T, domain: SignDomain, envelope: SignatureEnvelope): T & { $hubrpcSignature: Record; }; //#endregion //#region src/protocol/capability.d.ts /** * A single axis matcher. Empty prefix matches anything. Non-empty prefix `p` * matches `value` iff: * - `value === p`, OR * - `value` starts with `p + delimiter` (delimiter depends on the axis). * * For axes without a delimiter (`member`), prefix degrades to `startsWith`. */ type Pattern = { exact: string; } | { prefix: string; }; /** * Per-field matcher used by {@link Permission.params}. The set of declared * keys is a strict allowlist — a call whose params carry an undeclared key * is rejected, so the only way to permit a free value is to say * `{ any: true }` explicitly. Values are compared via canonical-JSON * equality, so anything JSON-serializable is acceptable. * * - `exact` — canonical-JSON equality against a single value. * - `enum` — canonical-JSON equality against any value in the list. * - `prefix` — value MUST be a string and `startsWith(prefix)`. Used to * pin e.g. a URL to a base-path subtree. * - `subsetOf` — value MUST be a `string[]` whose every element is in this * set. Order-independent; the empty array is a valid subset. * Used to bound a requested scope set. * - `any` — matches anything (the explicit wildcard). */ type ParamMatcher = { exact: unknown; } | { enum: unknown[]; } | { prefix: string; } | { subsetOf: string[]; } | { any: true; }; /** * The exact, point-precision narrowing of a {@link Permission}: it admits * **one** signed RPC call — the one whose call content-hash * ({@link import("./signedObject").signedHash}`("call", signedCall)`, i.e. * `base64url(sha256(jcs({ "hubrpc-sig/v1/call": userParams + $hubrpc })))`) * equals `payloadHash`. No field-by-field comparison; pure hash equality. * This is the *same bytes the call signature commits to*, so the host can * pre-compute it at consent time exactly as the consumer will sign. * * Because it binds the entire signed payload, `callBind` pins **every** * signed field at once — method, user params, `nonce`, `signedAtMs`, * `signer`, and `interfaceHash?`. Since the bound bytes include the * per-request `nonce`, a `callBind` grant is intrinsically single-use: * the request-nonce ledger rejects any replay of the one call it names. * * Used for "Allow once" flows where the user approved a single concrete call. */ interface CallBind { /** Hash algorithm. Currently always "sha256". */ alg: "sha256"; /** * `signedHash("call", signedCall)` — the SHA-256 of exactly the bytes the * RPC signature commits to, for the one call this permission is bound to. */ payloadHash: Base64Sha256; } /** * The address of a class of calls, expressed as patterns. A * {@link CallTarget} is admitted iff every axis pattern admits it. */ interface TargetPattern { /** Delimiter `/`. `"github"` matches `"github/repos"` but not `"githubclone"`. */ serviceId: Pattern; /** Delimiter `.`. `"hubrpc"` matches `"hubrpc.directory"` but not `"hubrpcx.foo"`. */ interfaceId: Pattern; /** * Optional schema-version pin. When set, the call's asserted * `interfaceHash` (carried in `$hubrpc.interfaceHash`) must equal this * string. Omit to accept any version. */ interfaceHash?: string; /** * Any-of: the call's member must match at least one pattern. Empty * list matches nothing. `[{ prefix: "" }]` is the universal wildcard. */ members: Pattern[]; } /** * One grant clause of a {@link Capability}. Denotes a set of calls the * holder may make, described at up to three zoom levels (coarse → exact): * * - `target` — *which endpoint* (always present); * - `params` — *which argument values* (optional value allowlist); * - `callBind` — *which exact call* (optional collapse to a single call). * * The {@link canInvoke}/{@link canDelegate} flags say what the holder may * *do* with that set: invoke calls in it, and/or delegate (re-issue a * narrowed grant) onward. Both default to `false` (fail closed) — a * permission that grants neither admits nothing. */ interface Permission { /** Which endpoints this clause talks about. */ target: TargetPattern; /** The holder may invoke calls in the set. Default `false`. */ canInvoke?: boolean; /** The holder may delegate (re-issue a narrowed grant) of the set. Default `false`. */ canDelegate?: boolean; /** * Optional value-level narrowing. Strict allowlist by top-level param * key: the call's param key-set must equal the declared set; each value * must satisfy its matcher. Omit to accept any params. */ params?: Record; /** Optional collapse to a single exact signed call. See {@link CallBind}. */ callBind?: CallBind; } interface Capability { /** Issuer principal (signer of this cap). */ issuer: PrincipalId; /** Audience principal (holder allowed to wield it). */ audience: PrincipalId; /** What the holder may do. A call is permitted by the cap iff it is permitted by **any** permission. */ permissions: Permission[]; /** Unix milliseconds. Absent = never expires. */ expiresAtMs?: number; /** * The single parent this cap delegates from, referenced by its content * hash (`signedHash("capability", parent)`). Linearized — at most one * parent. The parent itself travels out-of-band in the call's * `$hubrpcUnsigned.capabilities` bag and is resolved by this hash. * Effective authority is the intersection over the chain: a call must be * permitted by every link. Absent = this cap is a root. */ parentHash?: Base64Sha256; /** Per-cap distinguisher (base64url). Identifies the capability for audit/logging. */ nonce: string; } /** * A capability with its issuer's signature attached under * `$hubrpcSignature.capability`. It IS a {@link Capability} (the fields are * top-level) plus the signature map — no separate wrapper object. The * signature commits to `signingInput("capability", cap)`, i.e. the cap with * `$hubrpcSignature`/`$hubrpcUnsigned` stripped. */ type SignedCapability = Capability & { readonly [HUBRPC_SIGNATURE_KEY]: Signatures; }; /** * The address of a concrete call — *which endpoint*, with no arguments. * This is what {@link TargetPattern} matches against. The full concrete * call (with params/nonce/signer/bytes) is `Call` in `identity/capability`. */ interface CallTarget { serviceId: string; interfaceId: string; member: string; /** * Schema-version assertion the caller stamps from its compile-time * knowledge of the interface (typed clients use `iface.schemaHash`). * Compared against `TargetPattern.interfaceHash` by the matcher. Absent * means the caller didn't pin a version — the matcher then requires * `target.interfaceHash` to also be absent. */ interfaceHash?: string; } /** * Addressing match for a single permission: do its * `serviceId`/`interfaceId`/`interfaceHash`/`members` patterns admit * `target`? */ declare function permissionMatchesTarget(target: CallTarget, perm: Permission): boolean; /** * Strict allowlist match for {@link Permission.params}. The set of * top-level keys in the call MUST equal the declared set; values must * satisfy their matchers. Nested objects/arrays are compared as opaque * JSON values via canonical-JSON equality. Pure (no crypto). */ declare function matchParams(declared: Record, actualParams: unknown): { ok: true; } | { ok: false; reason: string; }; /** What a holder may do with a permission's call-set. */ type Ability = "invoke" | "delegate"; /** * A concrete, authenticated RPC call — the thing {@link permits} judges. * It is the call's {@link CallTarget} (which endpoint) plus the arguments * and the authenticated request metadata. Built by `verifyCall` from a * signed wire envelope. */ interface Call { /** Which endpoint is being called. */ target: CallTarget; /** User params with identity envelopes stripped. */ params: unknown; /** Per-request replay nonce (the unit the gate dedups on). */ nonce: string; /** Unix milliseconds the call was signed. */ signedAtMs: number; /** The authenticated signer — must equal the leaf capability's audience. */ signer: PrincipalId; /** The call's content hash (`signedHash("call", signedCall)`) — what `callBind` compares against. */ callHash: Base64Sha256; } type Verdict = { ok: true; } | { ok: false; reason: string; }; /** * Does `permission` admit `call` for the given `ability`? Checks, in * order: the ability flag (`canInvoke`/`canDelegate`, both default * `false`), the target address, the optional `params` allowlist, and the * optional `callBind` hash-binding. Pure. */ declare function permissionPermits(call: Call, permission: Permission, ability: Ability): Verdict; /** * Does `cap` admit `call` for the given `ability`? True iff **any** of its * permissions does (the union over permissions). This is one *link's* * judgment; chain intersection is enforced by {@link verifyChain}. */ declare function capabilityPermits(call: Call, cap: Capability, ability: Ability): Verdict; //#endregion //#region src/protocol/methodName.d.ts /** * Parsed JSON-RPC method string. The hubrpc dialect uses `::` to * separate up to three segments: * * - `"member"` — bare (preset-bound dispatch) * - `"interfaceId::member"` — interface form (root service) * - `"::interfaceId::member"` — explicit root form * - `"serviceId::interfaceId::member"` — fully-qualified form * * The hub does not accept the `bare` form (it has no interface context * to route on). Connection-level dispatch accepts all three. */ type ParsedMethodName = { kind: "bare"; member: string; } | { kind: "interface"; interfaceId: string; member: string; } | { kind: "full"; serviceId: string; interfaceId: string; member: string; }; /** * Parse a JSON-RPC method string. Returns `undefined` if any segment is * empty or the segment count is out of range. */ declare function parseMethodName(method: string): ParsedMethodName | undefined; /** * Convert a parsed method string to a {@link CallTarget} for capability * matching. Interface-form calls are addressed to the root service — * represented as an empty `serviceId`. Throws on `bare` form (no * interface context) and on malformed input. */ declare function methodNameToTarget(method: string): CallTarget; //#endregion //#region src/protocol/hubRpcEnvelope.d.ts /** * Signed call meta. Lives under `params.$hubrpc` so it can't collide with * the user's top-level param keys. Present on signed AND unsigned calls * (the latter omit {@link CallMeta.principal} and carry no `$hubrpcSignature`). * * Included verbatim in the bytes a call signature commits to — the * signature covers `signingInput("call", { ...userParams, $hubrpc })`. */ interface CallMeta { /** Fully-qualified wire method. The verifier asserts it equals the JSON-RPC `method`. */ readonly method: string; /** Replay-protection nonce (base64url). The gate dedups on this. */ readonly nonce: string; /** Unix milliseconds. Skew window enforced by the verifier. */ readonly signedAtMs: number; /** Identity wielding the call. Present iff signed; equals the audience of every presented cap. */ readonly principal?: PrincipalId; /** Optional schema hash assertion. Matched against `TargetPattern.interfaceHash`. */ readonly interfaceHash?: string; } /** * Extrinsic, unsigned attachments carried under `params.$hubrpcUnsigned`. * Not covered by the call signature (they aren't authored by the signer). */ interface HubRpcUnsigned { /** Caller's presented capability bag. Parents are resolved out of this by hash. */ readonly capabilities?: readonly SignedCapability[]; } /** Reserved wire-key meta carried on a hubrpc call's params object. */ interface HubRpcWireMeta { readonly [HUBRPC_META_KEY]?: CallMeta; readonly [HUBRPC_SIGNATURE_KEY]?: Signatures; readonly [HUBRPC_UNSIGNED_KEY]?: HubRpcUnsigned; } /** * Wire shape of `params` on a hubrpc JSON-RPC call. * * The user's params object (if any) is spread as-is, with the * {@link HubRpcWireMeta} keys attached on top. `TUserParams` carries the * user-visible param shape; defaults to an open object. User params MUST be * a plain object — enforced at signing/verification via * {@link requireObjectParams}. */ type HubRpcWireParams = TUserParams & HubRpcWireMeta; /** JSON-RPC request whose `params` carry the hubrpc wire meta. */ type HubRpcJsonRpcRequest = JsonRpcRequest>; /** JSON-RPC notification whose `params` carry the hubrpc wire meta. */ type HubRpcJsonRpcNotification = JsonRpcNotification>; /** Either of the two hubrpc-bearing JSON-RPC message shapes. */ type HubRpcJsonRpcMessage = HubRpcJsonRpcRequest | HubRpcJsonRpcNotification; /** * The hubrpc signing/cap system accepts only plain-object user params (or * none). Reject arrays/primitives at the boundary so "strip `$hubrpc*`, the * rest is the signed user params" stays unambiguous. */ declare function requireObjectParams(userParams: JsonValue | undefined): Record; //#endregion //#region src/disposable.d.ts /** Minimal disposable handle; calling `dispose` releases the resource. */ interface IDisposable { dispose(): void; } //#endregion //#region src/transport/messageTransport.d.ts interface IMessageTransport { send(message: TOutgoing): void | Promise; /** * Sets the listener for incoming messages. Setting `undefined` detaches. * The transport buffers messages received before a listener is attached * and delivers them the next tick when one is set. * Forgetting to set a listener will cause the queue to leak. */ setListener(listener: ((message: TIncoming) => void) | undefined): void; dispose(): void; } type MessageWithContext = JsonRpcMessage & { context: TCtx; }; type MessageTransportWithContext = IMessageTransport>; type MessageTransportDirection = 'send' | 'receive'; type MessageTransportTrace = (direction: MessageTransportDirection, message: JsonRpcMessage) => void; /** * Observe every message crossing a transport without changing its buffering or * lifecycle behavior. */ declare function traceMessageTransport(transport: IMessageTransport, trace: MessageTransportTrace): IMessageTransport; /** * Pipe two transports together: every message one receives is forwarded to * the other's `send`. Returns a disposable that detaches both listeners. * * Neither transport is disposed — the caller owns their lifecycle. This is * the building block for relays (e.g. bridging a `WindowMessageTransport` * for an iframe to a multiplexer channel). */ declare function connectTransports(a: IMessageTransport, b: IMessageTransport): IDisposable; /** * Two in-memory transports wired back-to-back. Useful for tests and for * same-process bridges. * * Generic in the per-direction payload type so asymmetric pairs (e.g. one * side sends plain `JsonRpcMessage`, the other side sends * `JsonRpcMessage & { context: Participant }`) are expressible at the type * level. At runtime both halves just hand objects through by reference. */ declare class TransportPair { readonly a: IMessageTransport; readonly b: IMessageTransport; constructor(); } //#endregion //#region src/transport/multiplexedTransport.d.ts interface MuxEnvelope { readonly $mux: "v1"; readonly ch: string; readonly m: JsonRpcMessage; } declare class MultiplexedTransport> implements IDisposable { static create>(base: IMessageTransport, channels: TChannels): MultiplexedTransport; /** Logical transports keyed by the friendly channel names. */ readonly transports: { readonly [K in keyof TChannels]: IMessageTransport; }; private readonly _base; private readonly _byId; private readonly _usedIds; private _disposed; constructor(base: IMessageTransport, channels: TChannels); /** * Add a logical channel after the multiplexer has started. * * Channel ids are permanently retired when disposed. This prevents a late * envelope for an old iframe from being delivered to a replacement iframe. */ addChannel(id: string): IMessageTransport; dispose(): void; private _createChannel; } //#endregion //#region src/connection/channel.d.ts declare class Channel { readonly sender: IRequestSender; private readonly _setHandler; constructor(sender: IRequestSender, _setHandler: (h: IRequestHandler | undefined) => void); /** Bind the inbound request/notification handler. May be called before or after construction of {@link HubRpcConnection}. */ setRequestHandler(handler: IRequestHandler | undefined): void; /** * Compose this channel with a sender-side decorator (typically the * signing layer). The decorator wraps {@link sender} only; the * {@link setRequestHandler} binding is shared with the original channel * so the receive side is bound exactly once regardless of decoration depth. */ withSender(decorate: (raw: IRequestSender) => IRequestSender): Channel; } interface IRequestHandler { handleRequest(call: IncomingCall): Promise; handleNotification(call: IncomingCall): void; } /** * Inbound call observed by an {@link IRequestHandler}. `TInCtx` is the * per-call out-of-band context the transport attached on the receiving * side (e.g. the hub stamps `Participant` on overlay-bound messages). * For wire transports it's `undefined`. */ interface IncomingCall { method: string; params: JsonValue | undefined; /** Out-of-band context attached by the transport, if any. */ context: TInCtx; /** * Per-call streaming handle. Both directions live on this object, * scoped to the request's lifetime. The inbound listener is auto- * detached when the handler's response settles. For notifications * (no request id) the handle is a no-op stub. */ stream: IncomingStream; /** * Aborts when the caller cancels this in-flight request (a * `toCallee` `cancel` control), or when the hub cancels it on the * caller's behalf (caller disconnect / idle timeout). The handler * should observe this and settle promptly — typically by throwing, * which surfaces as a `cancelled` error response. For notifications * this never aborts. */ signal: AbortSignal; } /** * Per-call streaming handle attached to {@link IncomingCall.stream}. * Scoped to a single in-flight request; the channel automatically * detaches the inbound listener when the handler's response settles. * * For notifications (which have no request id), `requestId` is * `undefined`, `send` is a no-op, and `onMessage` is a no-op. */ interface IncomingStream { /** Emit a server→client stream notification correlated with this call. */ send(payload: JsonValue): Promise; /** * Register a listener for client→server stream messages correlated * with this call. Pass `undefined` to detach. Replaces any prior * listener. The channel detaches the listener automatically when * the handler's response settles. */ onMessage(listener: ((payload: JsonValue) => void) | undefined): void; /** * Liveness probe toward the caller: emit a `toCaller` ping and * resolve once the caller's `pong` (matching nonce) returns. Rejects * if the request settles first. Independent of the channel's * automatic keepalive ping. */ ping(): Promise; } type Result = { result: JsonValue; } | { error: { code: number; message: string; data?: JsonValue; }; }; /** * What callers use to send. The {@link Channel} factory binds an * {@link IRequestHandler} at construction (immutable for the channel's * lifetime) and hands the caller back an `IRequestSender`. * * `TOutCtx` is the per-call context bag this sender understands. For * the base {@link JsonRpcChannel} it's `undefined` (the channel * doesn't read any overrides). Decorators like `SigningSender` * parametrise it on their own ctx shape. */ interface IRequestSender { sendRequest(method: string, params: JsonValue | undefined, opts?: SendOpts): Promise; sendNotification(method: string, params: JsonValue | undefined, opts?: SendOpts): Promise; /** * Issue a request that participates in the streaming sub-protocol. * Returns synchronously with the wire request id, the result * promise, and a `send` for emitting client→server stream messages. */ sendRequestWithStream(method: string, params: JsonValue | undefined, opts?: StreamSendOpts): RawStreamingCall; close(): void; } /** * Handle for a request issued via * {@link IRequestSender.sendRequestWithStream}. `send` emits * client→server stream messages correlated with this call. `result` * resolves with the call's response. */ interface RawStreamingCall { readonly result: Promise; /** Emit a client→server (`toCallee`) stream message correlated with this call. */ send(payload: JsonValue): void; /** * Ask the callee to abort this in-flight request (a `toCallee` * `cancel` control). Advisory: the request settles via its normal * response (typically a `cancelled` error). `reason` is an open-set * diagnostic string (see `StreamControlReason`). */ cancel(reason?: string): void; /** * Liveness probe toward the callee: emit a `toCallee` ping and * resolve once the callee's `pong` (matching nonce) returns. Rejects * if the request settles first. Independent of the channel's * automatic keepalive ping. */ ping(): Promise; } /** * Per-send options. * * `interfaceHash` is interface-level call metadata: the schema hash of * the interface a typed proxy is calling. It is independent of * `TOutCtx` — the connection stamps it from the interface definition, * the base {@link JsonRpcChannel} ignores it, and signing decorators * bake it into the `$hubrpc` envelope. * * `ctx` is the sender's `TOutCtx`-typed override / extension bag for * per-call decorator overrides (e.g. `signerOverride`, `capsOverride`). * The base {@link JsonRpcChannel} ignores `ctx` entirely; decorators * like `SigningSender` interpret it. */ interface SendOpts { readonly ctx?: TOutCtx; /** Interface schema hash for this call (interface-level metadata). */ readonly interfaceHash?: string; } interface StreamSendOpts extends SendOpts { readonly onStreamMessage?: (payload: JsonValue) => void; } /** * Convenience: the transport type a {@link JsonRpcChannel} expects. * Outbound is plain `JsonRpcMessage` — the base channel does not * attach context to outgoing wire messages. */ type ChannelTransport = IMessageTransport>; /** * Type of incoming messages on the channel's transport. With * `TInCtx = undefined` (default) this is just `JsonRpcMessage`. With a * concrete `TInCtx`, the transport carries a `context` field alongside * the message — out-of-band, never sent over a wire, set by the * in-process producer. */ type MessageWithCtx = [TInCtx] extends [undefined] ? JsonRpcMessage : JsonRpcMessage & { context: TInCtx; }; declare class RpcError extends Error { readonly code: number; readonly data?: JsonValue | undefined; constructor(message: string, code: number, data?: JsonValue | undefined); } //#endregion //#region src/connection/channelConnector.d.ts /** * A raw channel that signals when it closes (and, when possible, can be torn * down). Both {@link openHubChannel}'s `HubChannel` and `openStdioChannel`'s * `StdioChannel` satisfy this shape. */ type ConnectableChannel = Channel & { /** * Fires once when the channel closes. Returns a disposable to unsubscribe. * * Ordering contract: when a close causes a sending method (e.g. a request * or notification) to reject/throw, {@link onClose} must fire *before* that * rejection surfaces to the caller. This lets consumers reliably tell a * close-induced failure (the channel is already observably closed) apart * from a genuine error (the channel is still open). See * {@link ChannelConnector.keepConnected}. */ onClose(listener: () => void): IDisposable; /** Tear the channel down. Optional — stdio channels have no explicit close. */ close?(): void; }; /** Handle returned by {@link ChannelConnector.keepConnected}. */ interface KeepConnectedHandle { /** * Resolves when the loop exits: the channel closed and the connector does * not redial, or {@link stop} (or the supplied signal) fired. */ readonly done: Promise; /** Stop redialing and close the current channel (if any). */ stop(): void; } /** Callback run on every (re)connect. The channel is the raw, unsigned channel. */ type OnChannelConnect = (ctx: { channel: T; }) => void | Promise; interface ExpBackoffOptions { readonly initialBackoffMs?: number; readonly maxBackoffMs?: number; } /** * Drives a connect / (re)connect loop over a raw {@link ConnectableChannel}. * * Unlike connection-level helpers, this works at the channel layer: the * caller composes identity / signing on top of the channel handed to * {@link keepConnected} (e.g. `SigningSender.wrapChannel(channel, { principal })`). * * Construct via {@link ChannelConnector.once} (a single, already-open channel * that never redials) or {@link ChannelConnector.expBackoff} (re-open via a * factory with exponential backoff after each close). */ declare class ChannelConnector { private readonly _open; private readonly _redial; private readonly _initialBackoffMs; private readonly _maxBackoffMs; private constructor(); /** * A connector over a single channel (or a promise of one). Never redials; * the loop ends when the channel closes or {@link KeepConnectedHandle.stop} * fires. */ static once(channel: T | Promise): ChannelConnector; /** * A redialing connector: `open` is called once per attempt, and the loop * reconnects with exponential backoff after each close (or failed open). */ static expBackoff(open: () => Promise, opts?: ExpBackoffOptions): ChannelConnector; /** * Run `onConnect` on every (re)connect with the freshly opened channel. * The callback typically wraps the channel with signing and registers * handlers. Returns once the loop exits (see {@link KeepConnectedHandle}). */ keepConnected(onConnect: OnChannelConnect, opts?: { signal?: AbortSignal; }): KeepConnectedHandle; } //#endregion //#region src/connection/jsonRpcChannel.d.ts /** * Minimal JSON-RPC 2.0 channel: correlates requests with responses, * dispatches incoming requests/notifications to a handler. * * Construct via {@link JsonRpcChannel.create}: it returns a * {@link Channel} factory which materialises the live channel once a * handler is supplied via `.connect(handler)`. The handler is fixed for * the channel's lifetime — no setter, no mid-flight swap. * * The outbound `TOutCtx` of the produced sender is `unknown` — this * channel ignores per-call ctx entirely. Decorators (e.g. * `SigningSender`) lift it to a concrete shape. */ declare class JsonRpcChannel implements IRequestSender { private readonly _transport; /** * Wrap a transport in a {@link Channel}. The channel's sender is live * immediately; call {@link Channel.setRequestHandler} (or pass the channel * to {@link HubRpcConnection}) to bind the inbound handler. */ static create(transport: ChannelTransport): Channel; private _nextId; private readonly _pending; /** * Per-request callbacks invoked when a {@link STREAM_METHOD} * notification arrives with a matching `requestId`. Used in both * directions: outgoing-request callers register here keyed by the * id they sent; per-call incoming-request stream handles register * keyed by the id they observed. Entries are removed when the * corresponding request completes (response received for outgoing, * response sent for incoming). */ private readonly _streamListeners; /** * Per-incoming-request handlers for reserved `control` messages * (cancel / ping / pong) arriving as stream notifications. Keyed * by the id observed; removed when the request settles. */ private readonly _streamControl; private _handler; setRequestHandler(handler: IRequestHandler | undefined): void; private constructor(); sendRequest(method: string, params: JsonValue | undefined, _opts?: SendOpts): Promise; sendNotification(method: string, params: JsonValue | undefined, _opts?: SendOpts): Promise; sendRequestWithStream(method: string, params: JsonValue | undefined, opts?: StreamSendOpts): RawStreamingCall; /** * Build the symmetric ping/pong machinery for one in-flight request. * * `outboundDir` is the direction *this* side emits controls in * (`toCallee` for the caller, `toCaller` for the callee). A received * ping is answered with a pong in that same direction, echoing the * ping's nonce; a received pong resolves the matching outstanding * {@link ping} probe. */ private _makePinger; /** * Emit a stream notification ({@link STREAM_METHOD}) associated with * an in-flight request. Internal: outgoing-side callers reach this * via {@link RawStreamingCall.send} / `cancel`; incoming-side handlers * reach it via {@link IncomingStream.send}. */ private _sendStream; close(): void; private _onMessage; private _handleStreamNotification; private _handleRequest; private _handleNotification; } //#endregion //#region src/connection/endpointUri.d.ts /** * A strict, RFC-3986 URI vocabulary for "where the hubrpc server lives, and * how to reach (or start) it". Every endpoint round-trips through * {@link parseEndpointUri} / {@link formatEndpointUri} and is a valid `new URL()` * — safe to put in env vars, logs, and config. * * Supported schemes: * - `unix:/path/to.sock?token=…` → {@link SocketEndpoint} * - `npipe://./pipe/name?token=…` → {@link SocketEndpoint} (Windows) * - `ws://host:port?token=…` / `wss:…` → {@link WsEndpoint} (HubRPC handshake) * - `ws-no-init://host:port?…` → {@link WsNoInitEndpoint} * - `cmd-stdio:?command=…` / `…?argv=…` → {@link CmdStdioEndpoint} * - `cmd:?command=…` / `…?argv=…` → {@link CmdEnvEndpoint} * * The command payload is `{ command: string } | { argv: string[] }`: * - `?command=node%20server.js` — one verbatim string, split by the OS shell. * - `?argv=node&argv=server.js` — repeated `argv` params, structure-preserving. * * A bare string with no scheme (legacy `HUBRPC_ENDPOINT`) is auto-detected: a * `ws://`/`wss://` URL stays WebSocket, anything else is a socket path. */ /** Verbatim command line (`{ command }`) or pre-split argv (`{ argv }`). */ type EndpointCommand = { readonly command: string; } | { readonly argv: readonly string[]; }; /** Named pipe / unix-domain-socket the server already listens on. */ interface SocketEndpoint { readonly kind: 'socket'; readonly path: string; readonly token?: string; /** * Present on sockets created by `hub connect`. Local framing still uses the * HubRPC transport handshake; this describes whether forwarded application * calls target a HubRPC or plain JSON-RPC peer. */ readonly brokerMode?: 'hubrpc' | 'raw'; } /** A running WebSocket hub; `token` rides in the `hubrpc::initialize` handshake. */ interface WsEndpoint { readonly kind: 'ws'; readonly url: string; readonly token?: string; } /** * A running plain-JSON-RPC WebSocket endpoint. Unlike {@link WsEndpoint}, CLI * consumers use it without the `hubrpc::initialize` handshake or HubRPC signing. * Query parameters are preserved verbatim for protocols that authenticate * during the WebSocket upgrade (for example AHP's `tkn` parameter). */ interface WsNoInitEndpoint { readonly kind: 'ws-no-init'; /** Actual `ws://` URL passed to the WebSocket constructor. */ readonly url: string; } /** Spawn a child and talk hubrpc over its stdin/stdout. */ interface CmdStdioEndpoint { readonly kind: 'cmd-stdio'; readonly command: EndpointCommand; /** Extra environment variables injected into the spawned child. */ readonly env?: Readonly>; /** Working directory for the spawned child. */ readonly cwd?: string; } /** * Spawn a child against a freshly-started *local hub*: the parent listens on a * private socket, hands the child its address + token via `HUBRPC_ENDPOINT` / * `HUBRPC_TOKEN`, and the child dials in as a hub participant (registering its * services), exactly as it would against a remote hub. */ interface CmdEnvEndpoint { readonly kind: 'cmd-env'; readonly command: EndpointCommand; /** * When set, the local hub provisions (or reuses) a *persistent* managed * identity under this slot id, so the child's HPKE wrap/unwrap keys survive * across runs (sealed archives re-open). */ readonly provisionSlot?: string; /** Extra environment variables injected into the spawned child. */ readonly env?: Readonly>; /** Working directory for the spawned child. */ readonly cwd?: string; } type ResolvedEndpoint = SocketEndpoint | WsEndpoint | WsNoInitEndpoint | CmdStdioEndpoint | CmdEnvEndpoint; /** * Parse a strict endpoint URI into an {@link ResolvedEndpoint}. Throws on an * unknown scheme or a malformed command endpoint. A bare (scheme-less) string * is auto-detected as `ws`/`wss` URL or a socket path. */ declare function parseEndpointUri(uri: string): ResolvedEndpoint; interface FormatEndpointOptions { /** Emit the real token instead of redacting it. Default: redact. */ readonly revealToken?: boolean; } /** * Render an {@link ResolvedEndpoint} back to a canonical strict URI. The token is * redacted (`***`) unless `revealToken` is set, so the result is paste-safe for * logs. Round-trips with {@link parseEndpointUri} when `revealToken` is true. */ declare function formatEndpointUri(spec: ResolvedEndpoint, options?: FormatEndpointOptions): string; /** True for endpoints that connect to existing, possibly-remote infrastructure. */ declare function isHubEndpoint(spec: ResolvedEndpoint): spec is SocketEndpoint | WsEndpoint; //#endregion //#region src/identity/identity.d.ts /** JSON-serializable form of a {@link KeypairSigningIdentity} (base64url keys). */ interface SerializedKeypairSigningIdentity { readonly privateKey: string; readonly publicKey: string; } /** * The public face of a signing identity: its stable {@link PrincipalId} name * and the {@link KeyId} its signatures are stamped with. In Phase 1 (perpetual * identities) the keyId is the genesis key embedded in the principal, so it is * derived by stripping the `id:` prefix. */ declare class PublicSigningIdentity { readonly principal: PrincipalId; constructor(principal: PrincipalId); /** The {@link KeyId} this identity signs with (its genesis key, in Phase 1). */ get keyId(): KeyId; } interface SigningIdentity { readonly publicSigningIdentity: PublicSigningIdentity; sign(bytes: Uint8Array): Promise; } /** * {@link SigningIdentity} backed by an in-process keypair. For the common * case where the signing material lives in memory. For external HSM-style * backends, implement {@link SigningIdentity} directly. */ declare class KeypairSigningIdentity implements SigningIdentity { private readonly _privateKey; readonly publicSigningIdentity: PublicSigningIdentity; constructor(principal: PrincipalId, _privateKey: PrivateKey); sign(message: Uint8Array): Promise; static fromKeypair(keypair: Keypair): KeypairSigningIdentity; /** Generate a fresh signing identity (Ed25519 keypair). */ static generateNew(): Promise; /** Restore an identity from its {@link toJson} form. */ static fromJson(json: SerializedKeypairSigningIdentity): KeypairSigningIdentity; /** Serialize the keypair to a JSON-friendly form (base64url keys). */ toJson(): SerializedKeypairSigningIdentity; } declare class PublicWrappingIdentity { readonly wrapPublicKey: Uint8Array; constructor(wrapPublicKey: Uint8Array); } interface WrappingIdentity { readonly publicWrappingIdentity: PublicWrappingIdentity; wrap(domain: string, bytes: Uint8Array): Promise; unwrap(domain: string, blob: Uint8Array): Promise; } interface Identity extends SigningIdentity, WrappingIdentity { /** The identity's stable {@link PrincipalId} name. */ readonly principal: PrincipalId; /** @deprecated */ readonly wrapPublicKey: Uint8Array; } /** * Full {@link Identity} backed by in-process Ed25519 (signing) and X25519 * (wrapping) keypairs. The local counterpart to a keystore/executor-backed * identity — used by self-managed principals that hold their own key * material (e.g. loaded from disk). For external HSM-style backends, * implement {@link Identity} directly. */ declare class KeypairIdentity implements Identity { private readonly _privateKey; private readonly _wrap; readonly principal: PrincipalId; readonly wrapPublicKey: Uint8Array; readonly publicSigningIdentity: PublicSigningIdentity; readonly publicWrappingIdentity: PublicWrappingIdentity; constructor(principal: PrincipalId, _privateKey: PrivateKey, _wrap: X25519Keypair); sign(message: Uint8Array): Promise; wrap(domain: string, bytes: Uint8Array): Promise; unwrap(domain: string, blob: Uint8Array): Promise; /** Generate a fresh identity (Ed25519 + X25519 keypairs). */ static generate(): Promise; } //#endregion //#region src/identity/capability.d.ts /** * Pure freshness check for a single capability link: `true` when the cap never * expires, or its `expiresAtMs` is still in the future at `now + marginMs`. * * Mirrors the gate's expiry rule in {@link verifyChain}'s link check * (`expiresAtMs < nowMs` ⇒ expired) so a producer can decide, *before signing*, * whether to re-acquire a cap rather than attach one the gate will reject as * `expired`. The `marginMs` safety window absorbs in-flight transit time and * client/hub clock skew — pass the call's `signedAtMs` as `now` so the check * evaluates expiry against the same instant baked into the envelope. * * This checks ONE link. Effective authority requires every link in a * delegation chain to be unexpired, so a producer attaching a bag should apply * this to every cap it would attach (see {@link capBagFreshAt}). */ declare function capabilityFreshAt(cap: Capability, now: number, marginMs?: number): boolean; /** * Freshness over a whole presented bag: `true` only when every cap is fresh at * `now + marginMs` (see {@link capabilityFreshAt}). An empty bag is trivially * fresh. Use this at the cap-production point to decide whether to re-acquire. */ declare function capBagFreshAt(caps: readonly Capability[], now: number, marginMs?: number): boolean; type PermitResult = { ok: true; capabilityNonce: string; rootIssuer: PrincipalId; } | { ok: false; reason: string; }; /** * A trust anchor accepted as a capability-chain root, plus whether it may be * named in authorization error messages. */ interface AcceptedRootIssuer { /** The principal accepted as a chain root for the queried service. */ principal: PrincipalId; /** * Whether this issuer may be disclosed in `permits` rejection reasons. * Public anchors (e.g. a hub's well-known admin identity) are surfaced to * help diagnose "wrong root" failures; private ones are withheld so the * error never leaks the set of trusted issuers. */ isPublic: boolean; } /** * **The** authorization predicate. A call is permitted iff some presented * capability (1) addresses the call, (2) has a genuine, well-delegated * chain whose leaf audience is the caller (`call.signer`), and (3) roots * at an issuer the verifier accepts **for the call's service**. * * Pure: it reports the verdict (and the `capabilityNonce` / `rootIssuer` * for audit) but performs no consumption. Replay is the caller's job — the * gate dedups `call.nonce`, so `callBind` grants are single-use for free. * * `acceptedRootIssuers` is consulted with the call's `serviceId` and returns * the accepted {@link AcceptedRootIssuer} anchors; an empty result rejects * every capability (fail closed) — trust is the verifier's, never the * token's. Anchors flagged `isPublic` are named in the rejection reason when * a chain roots at an unaccepted issuer. */ declare function permits(call: Call, capabilities: readonly SignedCapability[], acceptedRootIssuers: (serviceId: string) => readonly AcceptedRootIssuer[], nowMs: number, opts?: { maxDepth?: number; }): Promise; /** * Sign a `Capability` with a {@link SigningIdentity}. The private key never * leaves the identity. The result verifies via {@link verifyChain}. */ declare function signCapability(capability: Capability, issuer: SigningIdentity): Promise; //#endregion //#region src/identity/managedIdentity.d.ts /** * Executor-side backing for `identity.storage::*`. Lifecycle tied to a * single managed-identity slot; the executor decides where bytes are * stored. */ interface ManagedIdentityStorageBackend { get(key: string): Promise; set(key: string, value: unknown): Promise; /** Returns `true` iff the key existed before this call. */ delete(key: string): Promise; list(prefix?: string): Promise; } /** * In-process storage backend. Used by tests and by any executor that * doesn't need at-rest persistence. */ declare class InMemoryManagedIdentityStorage implements ManagedIdentityStorageBackend { private readonly _data; get(key: string): Promise; set(key: string, value: unknown): Promise; delete(key: string): Promise; list(prefix?: string): Promise; } /** * In-process `ManagedIdentity` that holds the private key in memory and * uses the package crypto API. Useful for tests and for hub-side features * that want a short-lived identity without touching disk. */ declare class InMemoryManagedIdentity implements Identity { private readonly _ed; private readonly _wrap; readonly principal: PrincipalId; readonly wrapPublicKey: Uint8Array; constructor(_ed: Keypair, _wrap: X25519Keypair); get publicSigningIdentity(): PublicSigningIdentity; get publicWrappingIdentity(): PublicWrappingIdentity; sign(bytes: Uint8Array): Promise; wrap(domain: string, bytes: Uint8Array): Promise; unwrap(domain: string, blob: Uint8Array): Promise; /** Generate a fresh in-memory identity (Ed25519 + X25519 keypairs). */ static generate(): Promise; } /** * Register the `identity` interface on `overlay` so the bound participant * can call `identity::sign` etc. on its root overlay. The overlay is * private to one participant — no other peer can reach these methods. * * When `storage` is supplied, the `identity.storage` interface is * registered alongside `identity::*`. Lifetime/scope of the backend is * the executor's responsibility — for keystore-backed identities the * backend lives as long as the identity slot does. * * Typical caller: the executor (e.g. the VS Code extension), right after * `Hub.attachParticipant`, on the `rootOverlay` returned from the attach * handle. */ declare function registerIdentityOnOverlay(overlay: HubRpcConnection, identity: Identity, storage?: ManagedIdentityStorageBackend): void; /** * Like {@link registerIdentityOnOverlay}, but resolves the identity lazily on * first use via `resolveIdentity`. The identity is materialized only when the * bound participant actually calls `identity::*` — registering the overlay * does NOT create or load any identity. * * This is essential for the consent model: a host can serve `identity::*` on a * keystore slot without making the slot privileged (`hasState`) until the app * genuinely opts into an identity. `resolveIdentity` is expected to be cheap on * repeat calls (the keystore caches in memory), and to reflect lifecycle * changes — e.g. after a slot is wiped and re-created ("Reset Identity"), the * next call resolves the fresh identity. * * `storage` is registered eagerly because reading/writing storage is itself * the privileged act the app must perform to gain state — exposing the * interface costs nothing until used. */ declare function registerLazyIdentityOnOverlay(overlay: HubRpcConnection, resolveIdentity: () => Promise, storage?: ManagedIdentityStorageBackend): void; /** * Client-side proxy for `identity.storage::*`. Methods round-trip through * the participant's root overlay; the executor enforces key-shape rules * server-side. * * Calls are unsigned (same recursion-guard rationale as * {@link createManagedIdentity} — the overlay is private to one * participant, so transport-level routing already authenticates). */ interface ManagedIdentityStorage { get(key: string): Promise; set(key: string, value: unknown): Promise; delete(key: string): Promise; list(prefix?: string): Promise; } /** * A managed identity resolved from an executor's `identity::*` overlay: a * full {@link Identity} (signing + HPKE wrap/unwrap, all round-tripping * through the executor — private keys never leave it) plus the * per-identity {@link ManagedIdentityStorage}. */ interface ManagedIdentity extends Identity { /** * Per-identity persistent key/value store. Backed by the executor's * `identity.storage::*` overlay; calls throw when the executor did not * register storage for this slot (e.g. an in-process identity for tests). */ readonly storage: ManagedIdentityStorage; } /** * Bootstrap a {@link ManagedIdentity} from a sender (typically the raw * unsigned sender obtained via `channel.sender` before wrapping with * {@link SigningSender}). Makes `identity::*` calls unsigned — the * executor's overlay is private to one participant so routing already * identifies the caller, preventing signing recursion. * * The returned identity signs / wraps / unwraps by round-tripping through * the executor's `identity::*` overlay; the caller wires it into their * {@link SigningSender} configuration (e.g. as a {@link Principal}). No * side effects on any existing connection or holder. */ declare function createManagedIdentity(sender: IRequestSender): Promise; //#endregion //#region src/identity/capBag.d.ts interface CapBagOptions { /** * Per-identity persistent storage (e.g. * {@link ManagedIdentityHandle.storage}). Omit for a memory-only bag * that forgets its caps when the process exits. */ readonly storage?: ManagedIdentityStorage; /** Storage key. Defaults to `hubrpc.caps.v1`. */ readonly storageKey?: string; /** Optional sink for non-fatal storage warnings. */ readonly onWarn?: (message: string) => void; } /** * A process-lifetime bag of hub-issued capabilities. Plug * {@link CapBag.provider} straight into a connection's cap provider so every * outbound signed call carries whatever caps have been granted so far. The * hub picks whichever cap matches the call per dispatch. * * When constructed with {@link CapBagOptions.storage} the bag hydrates from * (and persists to) storage, so the next process spawn for the same identity * reuses its grants without re-prompting. */ declare class CapBag { /** Create a bag, hydrating from storage when one was supplied. */ static load(options?: CapBagOptions): Promise; private readonly _caps; private readonly _storage?; private readonly _storageKey; private readonly _onWarn; private constructor(); get capabilities(): readonly SignedCapability[]; /** * A {@link CapProvider} reflecting the bag at call-time. Assign it to * `HubClientHandle.signing.capProvider` or pass it through * `SigningSenderConfig.capProvider`. */ readonly provider: CapProvider; /** Append capabilities and persist (when backed by storage). */ add(...caps: readonly SignedCapability[]): Promise; /** Drop all capabilities and persist the empty bag. */ clear(): Promise; private _hydrate; private _persist; } //#endregion //#region src/identity/principal.d.ts /** * A call identity bundled with its durable capability set. The {@link Identity} * is fixed for the life of the principal; the {@link CapBag} is mutable and * extensible — caps accumulate as the peer grants them. * * Transient, per-call (one-shot) grants are deliberately NOT part of a * principal: that is a separate policy, see `OneShotCapStaging`. * * Lives in its own module (rather than alongside `SigningSender`) so the * `SigningSender` ⇄ `createManagedPrincipal` factory cycle does not run through * a top-level `class … extends Principal`: such a cycle would hit a TDZ * ("Class extends value undefined") depending on module evaluation order. */ declare class Principal { readonly identity: Identity; readonly capBag: CapBag; constructor(identity: Identity, capBag: CapBag); } //#endregion //#region src/identity/managedPrincipal.d.ts /** * A {@link Principal} that also exposes the executor-backed per-identity * {@link ManagedIdentityStorage}. Returned by {@link createManagedPrincipal} * so callers can persist their own app state (e.g. a resolved serviceId) * alongside the granted capabilities, using the very same durable store the * {@link CapBag} hydrates from — no second storage wiring required. */ declare class PrincipalWithStore extends Principal { readonly store: ManagedIdentityStorage; constructor(identity: Identity, capBag: CapBag, store: ManagedIdentityStorage); } /** * Executor-managed principal: the peer signs for us through its * `identity::sign` overlay (we never hold a private key). Bootstrapped purely * from the outbound `sender` — no side effects on any connection — and caps * persist in the executor-backed per-identity storage. Works over any * transport whose peer serves the identity overlay (hub *or* stdio). * * Call this exactly once per channel: it performs the `identity::*` handshake * and hydrates the {@link CapBag} from storage. The returned * {@link PrincipalWithStore} additionally surfaces that same durable * {@link ManagedIdentityStorage} as `store`. */ declare function createManagedPrincipal(sender: IRequestSender): Promise; //#endregion //#region src/identity/signingSender.d.ts /** * Per-outbound-call hook. Sees every signed call this sender makes, * and gets a chance to: * - attach capabilities (the common case), * - override `method` / `params` / `interfaceHash` (rewriting, * auditing, schema-pinning), * - advance `signedAtMs` (e.g. after a slow consent prompt that needs * to issue a cap bound to a fresher timestamp than the sender's * initial wall-clock). * * `signer` and `nonce` are NOT overridable. `signer` is the call's * identity; `nonce` ties any cap to this specific call attempt and must * not drift between the cap-issuance request and the call itself. */ type CapProvider = (req: { readonly method: string; readonly params: JsonValue | undefined; readonly signer: PrincipalId; readonly nonce: string; readonly signedAtMs: number; readonly interfaceHash?: string; }) => Promise; interface CapProviderResult { readonly capabilities?: readonly SignedCapability[]; /** Override the wire method this sender signs. Default: the original. */ readonly method?: string; /** Override the params this sender signs. Default: the original. */ readonly params?: JsonValue; /** Override the interfaceHash baked into the signed envelope. */ readonly interfaceHash?: string; /** Advance the Unix-ms timestamp baked into the signed envelope. */ readonly signedAtMs?: number; } /** * Per-call ctx the {@link SigningSender} consumes. All fields are * optional; the sender's persistent {@link SigningSenderConfig.principal} * (identity + capabilities) is the default. * * To install a {@link SigningIdentity} after the sender is already constructed * (e.g. a managed-identity bootstrap), mutate the {@link SigningSenderConfig} * object's `principal` field — it is read fresh on every call. No setter on * the sender itself. */ interface SigningCallCtx { /** * Override the signer for this call only. Pass `null` to skip * signing entirely (plain unsigned JSON-RPC). Bootstrap flows that * must talk to `identity::*` before a managed signer is available * use `signerOverride: null` for those calls. */ readonly signerOverride?: SigningIdentity | null; /** * Replace whatever caps the persistent cap provider would attach. * Skips the persistent provider for this call. */ readonly capsOverride?: readonly SignedCapability[]; /** * Reserved for the perm-denied / consent retry path. Currently a * passthrough — sender does not act on it yet. */ readonly requestPermissionWhenDenied?: boolean; } /** * Persistent config for a {@link SigningSender}. * * `principal` is the call identity plus its durable, extensible capability * set. `oneShotCaps` is an optional side-policy that may stage a single-use * capability onto the very next call without touching the principal. Both * fields are read fresh on every call, so callers may install or swap them * after the sender has been constructed. * * A missing `principal` means "no signing / no caps" for that call, unless * overridden via {@link SigningCallCtx}. */ interface SigningSenderConfig { /** Identity + persistent (extensible) capabilities. */ readonly principal?: Principal; /** * Optional per-call capability provider. Runs at sign-time with the * concrete method/params/nonce/timestamp and may return caps and/or * signing overrides. */ readonly capProvider?: CapProvider; /** * Optional one-shot capability staging policy. Independent of the * principal; drained per outbound signed call. */ readonly oneShotCaps?: OneShotCapStaging; } /** * Side-policy for staging *one-shot* capabilities onto the next outbound * signed call, independent of any {@link Principal}. The {@link SigningSender} * drains the staged caps after attaching them once, so a spent cap never * leaks into a later call. * * Requesting the one-shot grant (prompting the user / hub) lives next to — * not inside — the principal: the principal owns identity and durable * capabilities; this owns the transient per-call grant. */ declare class OneShotCapStaging { private _staged; /** Stage caps for the next signed call. Replaces any still-pending caps. */ stage(caps: readonly SignedCapability[]): void; /** * Take (and clear) the staged caps. Called by {@link SigningSender} once * per outbound signed call. */ take(): readonly SignedCapability[]; } /** * Outbound sender decorator that wraps every call in a `$hubrpc` signed * envelope and (optionally) attaches capabilities. Stateless w.r.t. signing * config — the {@link Principal} and {@link OneShotCapStaging} are resolved * per-call from {@link SigningSenderConfig}, so the caller can install or * swap them after construction by mutating that object. */ /** * The result of {@link SigningSender.fromChannelWithManagedPrincipal} / * {@link SigningSender.fromTransportWithManagedPrincipal}: a signing * {@link Channel} ready for `HubRpcConnection`, plus the executor-managed * {@link PrincipalWithStore} that drives it (caps, per-identity storage, nodeId). */ interface ManagedSigningChannel { readonly channel: Channel; readonly principal: PrincipalWithStore; } declare class SigningSender implements IRequestSender { private readonly _inner; private readonly _config; /** * Wrap an inner channel with signing. Composes via {@link Channel.withSender} * so the resulting `Channel` plugs into * `HubRpcConnection`. */ static wrapChannel(inner: Channel, config: SigningSenderConfig): Channel; /** * Bootstrap an executor-managed {@link Principal} over the **unsigned** * `channel`, then wrap that same channel with signing driven by it. * * Crucially, the `identity::*` handshake that mints the managed principal * rides the *raw* channel (`channel.sender`), so those bootstrap calls are * themselves never signed — there is no "sign the sign call" recursion and * no reliance on threading `signerOverride: null` through every transport * layer. The returned signing {@link Channel} is what callers hand to * `HubRpcConnection`; the {@link PrincipalWithStore} is returned alongside * for caps / per-identity storage / nodeId. */ static fromChannelWithManagedPrincipal(channel: Channel, config?: Omit): Promise>; /** * Convenience over {@link fromChannelWithManagedPrincipal}: build the * unsigned {@link JsonRpcChannel} from `transport` first. The common entry * point for app hosts / services that own a raw {@link IMessageTransport}. */ static fromTransportWithManagedPrincipal(transport: IMessageTransport, config?: Omit): Promise>; constructor(_inner: IRequestSender, _config: SigningSenderConfig); sendRequest(method: string, params: JsonValue | undefined, opts?: SendOpts): Promise; sendNotification(method: string, params: JsonValue | undefined, opts?: SendOpts): Promise; sendRequestWithStream(method: string, params: JsonValue | undefined, opts?: StreamSendOpts): RawStreamingCall; close(): void; private _prepareOutbound; private _resolveSigner; private _createSigningCoordinates; private _resolveIntentAndCapabilities; private _signPreparedCall; } declare namespace crypto_d_exports { export { generateKeypair, generateX25519Keypair, hpkeOpen, hpkeSeal, keypairFromSeed, sha256, sign, verify, x25519KeypairFromSeed }; } /** Generate a fresh Ed25519 keypair (raw 32-byte seed + 32-byte public key). */ declare function generateKeypair(): Promise; /** * Deterministically derive an Ed25519 keypair from a raw 32-byte seed. For * tests / reproducible identities only — production keys must be random * (use {@link generateKeypair}). */ declare function keypairFromSeed(seed: Uint8Array): Promise; /** * Deterministically derive an X25519 keypair from a raw 32-byte scalar. For * tests / reproducible identities only. */ declare function x25519KeypairFromSeed(seed: Uint8Array): Promise; /** Ed25519 sign `message` with a raw 32-byte seed private key. */ declare function sign(privateKey: PrivateKey, message: Uint8Array): Promise; /** Ed25519 verify. Returns `false` (never throws) on malformed input. */ declare function verify(publicKey: PublicKey, message: Uint8Array, sig: Signature): Promise; /** Generate a fresh X25519 keypair (raw 32-byte scalar + 32-byte public key). */ declare function generateX25519Keypair(): Promise; /** * HPKE-base-mode single-shot seal. Output blob layout is * `enc (32 B) || ciphertext || tag (16 B)`; `domain` is bound into both the * HPKE `info` parameter and the AEAD AAD. */ declare function hpkeSeal(args: { readonly recipientPublicKey: Uint8Array; readonly domain: Uint8Array; readonly plaintext: Uint8Array; }): Promise; /** * HPKE-base-mode single-shot open. Throws on any tag failure (wrong domain, * wrong recipient key, or tampered blob); the error deliberately does NOT * distinguish these cases. */ declare function hpkeOpen(args: { readonly recipientPrivateKey: Uint8Array; readonly domain: Uint8Array; readonly blob: Uint8Array; }): Promise; /** SHA-256 digest (synchronous, dependency-free). */ declare function sha256(bytes: Uint8Array): Uint8Array; //#endregion //#region src/identity/identity.interfaces.d.ts /** * Per-participant key oracle. Reached on the participant's root overlay * (form-2 method names like `identity::sign`). Private key material lives * in the executor; the participant only sees the operations. * * Wire shape: * sign / wrap / unwrap all take and return base64url-encoded bytes. * `domain` is a caller-chosen string bound into HPKE info and the AEAD * AAD. `unwrap` succeeds only when invoked with the exact `domain` that * `wrap` was called with. Use a stable, namespaced string (e.g. * `secrets.vault.master-key.v1`). */ declare const identityInterface: InterfaceDefinition<{ getPrincipal: RequestType, { principal: string; }, any, never, never>; getWrapPublicKey: RequestType, { wrapPublicKey: string; }, any, never, never>; sign: RequestType<{ bytes: string; }, { signature: string; }, any, never, never>; wrap: RequestType<{ domain: string; bytes: string; }, { blob: string; }, any, never, never>; unwrap: RequestType<{ domain: string; blob: string; }, { bytes: string; }, any, never, never>; }>; /** * Per-identity persistent key/value store. Reached on the participant's * root overlay as `identity.storage::get` etc. — same overlay that serves * `identity::*`, same single-participant addressability. * * Storage scope is the executor's managed-identity slot (e.g. * `service:demo`). Lifecycle is bound to the identity: when the executor * deletes the identity for a slot, the storage for that slot is wiped in * the same operation. New identity issued for the same slot starts empty. * * The executor encrypts the backing file at rest with the same secret it * uses for identity files — see {@link IdentityKeystore} in * `@vscode/hubrpc/node`. * * Keys must match `^[A-Za-z0-9._/-]{1,256}$`. `/` is conventional for * namespacing (`caps.v1`, `prefs/foo`) and `list({ prefix })` honours * that. Values are arbitrary JSON. */ declare const identityStorageInterface: InterfaceDefinition<{ get: RequestType<{ key: string; }, { value?: unknown; }, any, never, never>; set: RequestType<{ key: string; value: unknown; }, Record, any, never, never>; delete: RequestType<{ key: string; }, { existed: boolean; }, any, never, never>; list: RequestType<{ prefix?: string | undefined; }, { keys: string[]; }, any, never, never>; }>; //#endregion //#region src/identity/signedRpcEnvelope.d.ts interface SignRpcCallOptions { readonly method: string; /** The user's params. Must be a plain JSON object or `undefined` (else throws). */ readonly params?: JsonValue; /** Identity that produces the signature. */ readonly signingIdentity: SigningIdentity; /** Unix milliseconds. Defaults to `Date.now()`. */ readonly nowMs?: number; /** Override the random nonce (testing). */ readonly nonce?: Uint8Array; /** Optional schema hash assertion. */ readonly interfaceHash?: string; } interface SignedRpcCall { /** * Wire params for the JSON-RPC request: the user's params merged with * `$hubrpc` (signed meta) and `$hubrpcSignature` (the `call` signature). * Capabilities are attached separately via {@link attachCapabilities} * since they are unsigned authority hints, not part of what the * signature commits to. */ readonly wireParams: HubRpcWireParams; /** The signed call meta embedded under `$hubrpc`. */ readonly callMeta: CallMeta; /** The call's content hash (`signedHash("call", signedParams)`) — what `callBind` binds to. */ readonly callHash: Base64Sha256; } /** * Sign a single JSON-RPC call. Produces the wire-form params with the * `$hubrpc` (signed meta) and `$hubrpcSignature.call` envelopes attached, * plus the call content hash. */ declare function signRpcCall(opts: SignRpcCallOptions): Promise; /** * Attach capabilities to a {@link SignedRpcCall.wireParams}. Capabilities * ride in `$hubrpcUnsigned.capabilities` — they are NOT part of what the * signature commits to (which is why this is a separate step). Returns a * new wireParams object; the input is not mutated. */ declare function attachCapabilities(wireParams: HubRpcWireParams, capabilities: readonly SignedCapability[]): HubRpcWireParams; interface VerifyRpcCallOptions { readonly wireMethod: string; /** Raw params object received on the wire. */ readonly wireParams: unknown; /** Defaults to `Date.now()`. */ readonly nowMs?: number; /** Max clock skew in milliseconds. Default 300000 (5 min). */ readonly maxSkewMs?: number; /** When `true`, reject calls lacking a signed envelope + signature. */ readonly requireSigned?: boolean; } type VerifyRpcCallResult = { readonly ok: true; /** `undefined` when the call had no signed (`$hubrpc` + `$hubrpcSignature.call`) envelope. */ readonly identity: undefined | { readonly callMeta: CallMeta; /** The authenticated principal (`callMeta.principal`). */ readonly signer: PrincipalId; /** The call's content hash — input to `callBind.payloadHash`. */ readonly callHash: Base64Sha256; readonly capabilities: SignedCapability[]; }; /** Params the handler should see (user params with the reserved keys stripped). */ readonly params: JsonValue | undefined; } | { readonly ok: false; readonly reason: string; }; /** * Verify the identity envelope on a JSON-RPC call. Does **not** evaluate * capability chains or caveats — that is the hub / authoriser's job. * * A signed call carries `$hubrpc` (with `principal`) and a `call` signature * under `$hubrpcSignature`. The signature is checked against * `signingInput("call", wireParams)` (the reserved keys are stripped by the * signed-object standard). Bare/unsigned calls return `identity: undefined`. */ declare function verifyRpcCall(opts: VerifyRpcCallOptions): Promise; //#endregion //#region src/identity/metaEnvelope.d.ts /** Plain JSON object with optional values — the wire shape JSON-RPC params take. */ type JsonObject = { [key: string]: JsonValue | undefined; }; interface SignParamsOptions { method: string; /** Caller-supplied params (without identity envelopes). Must be an object or undefined. */ params: JsonObject | undefined; /** Identity that produces the signature. */ signingIdentity: SigningIdentity; /** * Caps to attach. They are NOT signed — they ride in `$hubrpcUnsigned`. */ capabilities?: readonly SignedCapability[]; /** Unix milliseconds; defaults to `Date.now()`. */ nowMs?: number; /** Override nonce (testing); otherwise 16 random bytes. */ nonce?: Uint8Array; /** Stamped into the signed envelope as `interfaceHash`. */ interfaceHash?: string; } /** * Returns a wire-form params object carrying the `$hubrpc` envelope, the * `call` signature under `$hubrpcSignature`, and (optionally) capabilities * under `$hubrpcUnsigned`. */ declare function signParams(opts: SignParamsOptions): Promise; interface VerifyCallOptions { method: string; /** Raw params as received on the wire. Carries `$hubrpc` + `$hubrpcSignature` when signed. */ params: unknown; /** Decode the method string into the call's target address. */ parseMethod: (method: string) => CallTarget; nowMs?: number; /** Max clock skew in milliseconds. Default 300000 (±5min). */ maxSkewMs?: number; /** Reject if no capability is presented. */ requireCapability?: boolean; } type VerifyResult = { ok: true; signer: PrincipalId; /** * The resolved, authenticated call — the unit `permits()` judges. * Authorization is intentionally NOT done here; the gate owns it * (and the replay ledger). */ call: Call; /** Params with the reserved keys stripped — pass this to the actual handler. */ strippedParams: Record | undefined; /** Leaf + parent capabilities to hand to `permits()`. */ capabilities: SignedCapability[]; /** Per-call nonce extracted from the envelope (for replay-protection ledgers). */ nonce: string; /** The call's content hash — input to `callBind.payloadHash`. */ callHash: Base64Sha256; /** The `$hubrpc` envelope as received. */ callMeta: CallMeta; /** Schema-version assertion the caller stamped. */ interfaceHash: string | undefined; } | { ok: false; /** * Failure category: * - `"capability"` — the call shape was fine; the caller needs to * acquire (or present) a capability. Hub should surface this as * `permissionRequired` so consumers know to negotiate access. * - `"envelope"` — anything else (missing/bad signature, replay, * malformed nonce, etc). Hub should surface as `invalidRequest`. */ kind: "capability" | "envelope"; reason: string; }; declare function verifyCall(opts: VerifyCallOptions): Promise; //#endregion //#region src/identity/seededPrincipal.d.ts interface SeededPrincipalOptions { /** Numeric seed; the same value always yields the same identity / principal. */ readonly seed: number; } /** * Build a **random**, in-memory {@link Principal}: a fresh Ed25519 signing * identity + X25519 wrapping key, paired with an empty memory-only * {@link CapBag}. Full-entropy (unlike {@link createSeededMemoryPrincipal}), so * it is safe to sign trusted calls — e.g. an in-process "agent" identity a hub * admin delegates read authority to (via a minted capability) for directory * discovery. The identity is ephemeral: it vanishes with the process. */ declare function createMemoryPrincipal(): Promise; /** * Build a deterministic {@link Principal} from a numeric seed: a reproducible * Ed25519 signing identity (so the nodeId is stable across runs) plus a matching * X25519 wrapping key, paired with an empty, memory-only {@link CapBag}. * * For tests and reproducible local setups ONLY — the key material is derived * from a low-entropy seed and must never be used to sign anything trusted. */ declare function createSeededMemoryPrincipal(opts: SeededPrincipalOptions): Promise; /** * Derive a deterministic {@link KeypairSigningIdentity} from a numeric seed: a * reproducible Ed25519 signing identity (so the nodeId is stable across runs), * with no wrapping key. Use it where only signing/issuing is needed — e.g. a * hub admin `issuer` that mints capabilities in tests. * * For tests and reproducible local setups ONLY — the key material is derived * from a low-entropy seed and must never be used to sign anything trusted. */ declare function createSeededSigningIdentity(opts: SeededPrincipalOptions): Promise; //#endregion //#region src/hub/common/reflection.interfaces.d.ts declare const defaultsInterface: InterfaceDefinition<{ get: RequestType, { serviceId?: string | undefined; interfaceId?: string | undefined; interfaceHash?: string | undefined; }, any, never, never>; }>; /** * A single root-principal requirement. `transitive: true` means the requirement * also applies to every service reachable *through* this one — i.e. when this * listing is a `hubrpc.directory` reference, everything it lists inherits the * requirement (also as transitive). */ declare const zRootPrincipalReq: import("zod/mini").ZodMiniObject<{ principal: import("zod/mini").ZodMiniString; transitive: import("zod/mini").ZodMiniOptional>; }, import("zod/v4/core").$strip>; /** One OR-set. The set is satisfied by holding *any one* of its principals. */ declare const zRootPrincipalSet: import("zod/mini").ZodMiniArray; transitive: import("zod/mini").ZodMiniOptional>; }, import("zod/v4/core").$strip>>; type RootPrincipalReq = output; type RootPrincipalSet = output; declare const directoryInterface: InterfaceDefinition<{ list: RequestType<{ interfaceId?: string | undefined; serviceId?: string | undefined; cursor?: string | undefined; limit?: number | undefined; timeoutMs?: number | undefined; }, { items: { serviceId: string; interfaceId: string; interfaceHash: string; serviceDescription?: string | undefined; rootPrincipalSets?: { principal: string; transitive?: boolean | undefined; }[][] | undefined; }[]; nextCursor?: string | undefined; truncated?: boolean | undefined; }, any, never, never>; /** * Coarse change tap on the directory. * * `watch` is a long-lived streaming request that emits an **empty tick** * whenever the (optionally filtered) directory *might* have changed. The * tick carries no delta and no payload — its only meaning is "re-`list` * now". The consumer reconciles against its own last snapshot. * * This keeps the server stateless: it never computes or replays * per-item deltas, never does an initial-sync replay. Over-emission is * allowed (the consumer re-lists and finds nothing new); under-emission * is not. Ticks are coalesced. The `interfaceId` / `serviceId` filters * mirror `list` and are a relevance hint, not a guarantee. * * The request resolves when the caller cancels (or the connection * drops); the runtime auto-detaches the stream when it settles. */ watch: RequestType<{ interfaceId?: string | undefined; serviceId?: string | undefined; }, Record, void, any, Record>; }>; /** * A `directoryInterface.watch` handler for **static** directories that never * change (every plain reflection connection, e.g. {@link createHubServiceInterfaces} * participants and {@link HubRpcConnection.enableReflection}). It opens the * stream, never ticks, and resolves when the caller cancels — semantically * correct because a static directory's listing never changes. Only the hub's * global directory replaces this with a real, routing-driven implementation. */ declare function directoryWatchNever(_params: { interfaceId?: string; serviceId?: string; }, _ctx: unknown, stream: StreamApi>): Promise>; declare const schemasInterface: InterfaceDefinition<{ get: RequestType<{ interfaceId: string; hash?: string | undefined; }, { schema: unknown; }, any, never, never>; }>; //#endregion //#region src/connection/hubRpcConnection.d.ts /** * High-level hubrpc connection. Layers method-name routing and zod-driven * validation on top of a plain JSON-RPC channel. * * Typed proxies built by this class merely forward each call's * `interfaceHash` hint to the channel so the signer stamps it into the * `$hubrpc` envelope. * * `TInCtx` is the per-call out-of-band context the connection's transport * carries. Default `undefined` covers cross-process and ordinary in-process * transports. The hub's self/overlay connections instantiate with a * concrete `TInCtx` (e.g. `Participant`) so handlers can see who originated * the call. * * `TOutCtx` is the per-call override / extension bag the outbound sender * understands (see {@link SendOpts.ctx}). It only ever flows into the * sender as input, so it is a contravariant type parameter. Default `any` * keeps the bare `HubRpcConnection` a valid supertype for holders that do * not care about the outbound ctx; pass a concrete shape (e.g. * `SigningCallCtx`) to get precise {@link get} typing. The connection * itself is agnostic to its contents — it merely forwards the `ctx` * defaults supplied to {@link get}. */ declare class HubRpcConnection { /** * Convenience: build a {@link JsonRpcChannel} `Channel` from the * given transport and wrap it in an `HubRpcConnection`. Use this * when you have a transport at hand and don't need a decorator * stack (e.g. signing). */ static fromTransport(transport: IMessageTransport, JsonRpcMessage>): HubRpcConnection; /** Underlying JSON-RPC sender — useful for callers that need raw access (e.g. to call hub-served methods that bypass the interface registry). */ readonly channel: IRequestSender; /** key = `${serviceId ?? ""}::${interfaceId}` */ private readonly _registry; /** Descriptions for services that have been registered with a `serviceDescription`. */ private readonly _serviceDescriptions; /** Root-node-id requirement sets recorded per serviceId. */ private readonly _serviceRootPrincipalSets; private _preset; /** * Construct from a {@link Channel} (binds the inbound handler and uses * `channel.sender` for outbound calls) or from a bare * {@link IRequestSender} (send-only — no inbound handler is registered, * useful for bootstrap flows like `createManagedIdentity`). */ constructor(channel: Channel | IRequestSender); /** Get a typed client for `iface`, routed to the implicit (root) service. */ get>(iface: TDef, opts?: GetOptions): InterfaceClient; /** Get a service-scoped handle; all interfaces obtained from it route via `serviceId` (form 3). */ service(serviceId: string): ServiceHandle; /** Register handlers for an interface on this connection's server side. */ register>(iface: TDef, handlers: InterfaceHandlers, opts?: RegisterOptions): InterfaceRegistration; /** * Declare the preset interface for form-1 (bare-method) dispatch. The * interface must already be registered under the root (no serviceId). * Surfaced via `hubrpc.defaults::get`. */ setPreset(iface: InterfaceDefinition): void; /** Snapshot of every interface currently registered on this connection. */ listRegisteredInterfaces(): readonly { readonly serviceId: string; readonly interfaceId: string; readonly interfaceHash: string; readonly serviceDescription?: string; readonly rootPrincipalSets?: readonly RootPrincipalSet[]; }[]; /** * Look up a registered interface definition by id (and optional content * hash). Returns `undefined` if no registered interface matches. */ findRegisteredInterface(interfaceId: string, hash?: string): InterfaceDefinition | undefined; /** * Register the three hubrpc reflection interfaces (`defaults`, * `directory`, `schemas`), backed by this connection's live registry. * * By default they live under the root service (form-2 reachable as * `hubrpc.directory::list`). Pass `serviceId` to additionally mount * them under a specific service — useful for participants that live * behind a hub, so callers can reach reflection via form-3 * `::hubrpc.directory::list`. * * Idempotent: re-registering the same `(serviceId, interfaceId)` pair * is a no-op. */ enableReflection(opts?: { serviceId?: string; }): InterfaceRegistration; close(): void; private _buildClient; private _handleRequest; private _buildStreamApi; private _handleNotification; private _parseRouted; } /** * Options when obtaining a typed client for an interface. * * - `serviceId`: route to a specific service (form 3). Omit for form 2 * (implicit / root service on the connection). * - any `TOutCtx` property: a per-client default merged into the `ctx` * of every call issued through the returned proxy (e.g. * `signerOverride`, `capsOverride` for a signing channel). The * connection forwards these verbatim; it does not interpret them. * * Schema-version pinning travels as interface-level call metadata * ({@link SendOpts.interfaceHash}); the typed proxy stamps * `iface.schemaHash` automatically, independent of `TOutCtx`. */ type GetOptions = { serviceId?: string; } & Partial; interface RegisterOptions { /** * If set, this interface is mounted under this service id (form 3). */ serviceId?: string; /** * Optional human description recorded for `serviceId` and surfaced * through `hubrpc.directory::list`. Requires `serviceId`. The first * registration's description wins; any later registration that * supplies a *different* non-undefined description throws. */ serviceDescription?: string; /** * Root node ids required to access `serviceId`, in CNF (AND of OR-sets): * the caller must satisfy **every** set, and a set is satisfied by **any * one** of its node ids. Surfaced through `hubrpc.directory::list`. * Requires `serviceId`. Recorded per service; a later registration that * supplies a *different* value for the same `serviceId` throws. */ rootPrincipalSets?: readonly RootPrincipalSet[]; } /** A live interface registration. Disposing it removes dispatch and reflection state. */ interface InterfaceRegistration { /** Remove this exact registration. Idempotent. */ dispose(): void; } /** Service-scoped handle returned by `connection.service(id)`. */ declare class ServiceHandle { private readonly _connection; private readonly _serviceId; constructor(_connection: HubRpcConnection, _serviceId: string); get>(iface: TDef, opts?: Partial): InterfaceClient; register>(iface: TDef, handlers: InterfaceHandlers, opts?: Omit): InterfaceRegistration; } //#endregion export { PermitResult as $, jcsCanonicalize as $n, JsonRpcError as $r, HubRpcJsonRpcMessage as $t, identityStorageInterface as A, getKeyId as An, requestType as Ar, ChannelTransport as At, createManagedPrincipal as B, PrincipalId as Bn, EnumSchema as Br, StreamSendOpts as Bt, SignedRpcCall as C, Base64Sha256 as Cn, interfaceFromSchema as Cr, JsonRpcChannel as Ct, signRpcCall as D, SignDomain as Dn, RequestType as Dr, KeepConnectedHandle as Dt, attachCapabilities as E, HUBRPC_UNSIGNED_KEY as En, NotificationType as Er, ExpBackoffOptions as Et, OneShotCapStaging as F, withSignature as Fn, MethodSchema as Fr, MessageWithCtx as Ft, InMemoryManagedIdentityStorage as G, X25519Keypair as Gn, ObjectSchema as Gr, MessageTransportTrace as Gt, CapBag as H, PublicKey as Hn, IntegerSchema as Hr, MuxEnvelope as Ht, SigningCallCtx as I, KEY_ID_PREFIX as In, ArraySchema as Ir, RawStreamingCall as It, ManagedIdentityStorageBackend as J, keyIdForPrincipal as Jn, SchemaBase as Jr, TransportPair as Jt, ManagedIdentity as K, base64UrlToBytes as Kn, OneOfSchema as Kr, MessageTransportWithContext as Kt, SigningSender as L, KeyId as Ln, BooleanSchema as Lr, Result as Lt, CapProvider as M, signedHash as Mn, ErrorSchema as Mr, IRequestSender as Mt, CapProviderResult as N, signingDomainValue as Nn, HubRpcInterfaceSchema as Nr, IncomingCall as Nt, verifyRpcCall as O, SignatureEnvelope as On, Schema as Or, OnChannelConnect as Ot, ManagedSigningChannel as P, signingInput as Pn, MemberAnnotations as Pr, IncomingStream as Pt, AcceptedRootIssuer as Q, resolveSigningKey as Qn, ErrorCode as Qr, CallMeta as Qt, SigningSenderConfig as R, Keypair as Rn, ConstSchema as Rr, RpcError as Rt, SignRpcCallOptions as S, permissionPermits as Sn, defineInterface as Sr, parseEndpointUri as St, VerifyRpcCallResult as T, HUBRPC_SIGNATURE_KEY as Tn, MemberType as Tr, ConnectableChannel as Tt, CapBagOptions as U, ResolveSigningKeyArgs as Un, NullSchema as Ur, IMessageTransport as Ut, Principal as V, PrivateKey as Vn, HubRpcJsonSchema as Vr, MultiplexedTransport as Vt, InMemoryManagedIdentity as W, Signature as Wn, NumberSchema as Wr, MessageTransportDirection as Wt, registerIdentityOnOverlay as X, principalForPublicKey as Xn, TupleSchema as Xr, traceMessageTransport as Xt, createManagedIdentity as Y, keyIdForPublicKey as Yn, StringSchema as Yr, connectTransports as Yt, registerLazyIdentityOnOverlay as Z, publicKeyForKeyId as Zn, UnionSchema as Zr, IDisposable as Zt, SignParamsOptions as _, SignedCapability as _n, InterfaceInfo as _r, SocketEndpoint as _t, ServiceHandle as a, RequestId as ai, requireObjectParams as an, StreamSendParams as ar, KeypairIdentity as at, signParams as b, matchParams as bn, StreamCallOptions as br, formatEndpointUri as bt, defaultsInterface as c, isResponse as ci, parseMethodName as cn, generateTsInterface as cr, PublicWrappingIdentity as ct, schemasInterface as d, CallBind as dn, normalizeJsonSchema as dr, WrappingIdentity as dt, JsonRpcMessage as ei, HubRpcJsonRpcNotification as en, jcsCanonicalizeBytes as er, capBagFreshAt as et, SeededPrincipalOptions as f, CallTarget as fn, computeInterfaceHash as fr, CmdEnvEndpoint as ft, JsonObject as g, Permission as gn, InterfaceHandlers as gr, ResolvedEndpoint as gt, createSeededSigningIdentity as h, Pattern as hn, InterfaceDefinitionOpts as hr, FormatEndpointOptions as ht, RegisterOptions as i, JsonRpcSuccess as ii, HubRpcWireParams as in, StreamDir as ir, Identity as it, crypto_d_exports as j, readSignature as jn, zodToSvcJsonSchema as jr, IRequestHandler as jt, identityInterface as k, Signatures as kn, notificationType as kr, Channel as kt, directoryInterface as l, JsonValue as li, Ability as ln, Components as lr, SerializedKeypairSigningIdentity as lt, createSeededMemoryPrincipal as m, ParamMatcher as mn, InterfaceDefinition as mr, EndpointCommand as mt, HubRpcConnection as n, JsonRpcRequest as ni, HubRpcUnsigned as nn, StreamControlReason as nr, permits as nt, RootPrincipalReq as o, isNotification as oi, ParsedMethodName as on, streamInterface as or, KeypairSigningIdentity as ot, createMemoryPrincipal as p, Capability as pn, InterfaceClient as pr, CmdStdioEndpoint as pt, ManagedIdentityStorage as q, bytesToBase64Url as qn, RefSchema as qr, MessageWithContext as qt, InterfaceRegistration as r, JsonRpcResponse as ri, HubRpcWireMeta as rn, StreamControlType as rr, signCapability as rt, RootPrincipalSet as s, isRequest as si, methodNameToTarget as sn, GenerateInterfaceOptions as sr, PublicSigningIdentity as st, GetOptions as t, JsonRpcNotification as ti, HubRpcJsonRpcRequest as tn, STREAM_METHOD as tr, capabilityFreshAt as tt, directoryWatchNever as u, Call as un, isAssignable as ur, SigningIdentity as ut, VerifyCallOptions as v, TargetPattern as vn, MemberMap as vr, WsEndpoint as vt, VerifyRpcCallOptions as w, HUBRPC_META_KEY as wn, MemberDocs as wr, ChannelConnector as wt, verifyCall as x, permissionMatchesTarget as xn, StreamingCall as xr, isHubEndpoint as xt, VerifyResult as y, capabilityPermits as yn, StreamApi as yr, WsNoInitEndpoint as yt, PrincipalWithStore as z, PRINCIPAL_PREFIX as zn, DiscriminatorSchema as zr, SendOpts as zt }; //# sourceMappingURL=hubRpcConnection-Ba6dm9d-.d.ts.map