API / @aihu/agent-server

@aihu/agent-server

Agents & governance

Server-side glue: mount an aihu component server-side and let an MCP client drive it through the agent-service live-dispatch gate, forwarding approved invocations to a browser bridge.

version
0.4.0
exports
22
values
8
types
14
01

createAgentServer

functionagent
function createAgentServer(options: AgentServerOptions): AgentServer
02

createBridgeClient

functionagent
function createBridgeClient(options: BridgeClientOptions): BridgeClient

Start a browser bridge client.

03

createComponentMcpServer

functionagent
function createComponentMcpServer( agentServer: AgentServer, metas: AgentMetadata[] = getAllAgentMetadata(), ): Server
04

opaqueActionId

functionagent
function opaqueActionId(tag: string, member: string): string

Deterministic, stable opaque ID for an agent-exposed member — identical to the compiler's `opaque_member_id(tag, member)`.

05

opaqueActionIdForTool

functionagent
function opaqueActionIdForTool(toolName: string): string | null

Opaque ID directly from a `"<tag>/<member>"` tool name; null if malformed.

06

parseToolName

functionagent
function parseToolName(toolName: string): { tag: string; member: string } | null

Split a `"<tag>/<member>"` tool name.

07

serveComponentMcp

functionagent
async function serveComponentMcp( agentServer: AgentServer, metas?: AgentMetadata[], ): Promise<Server>

Convenience: create the component MCP server and connect it over stdio (the same transport `@aihu/mcp` uses).

08

BRIDGE_PROTOCOL_VERSION

constagent
const BRIDGE_PROTOCOL_VERSION
09

AgentDispatcher

interfaceagent
interface AgentDispatcher {
  readonly tag: string
  /** opaqueId → action invoker (called with the positional args array). */
  readonly actions: Record<string, (args: unknown[]) => unknown>
  /** opaqueId → read accessor (current signal value). */
  readonly reads: Record<string, () => unknown>
  /** opaqueId → write accessor. */
  readonly writes: Record<string, (value: unknown) => void>
}

The compiler-emitted `__agentDispatcher` shape (client artifact, T1).

10

AgentServer

interfaceagent
interface AgentServer {
  /** The underlying agent-service (exposes `handleToolCall`, `asMiddleware`). */
  readonly service: AgentService
  /** The `MountScope` of the server-mounted component. */
  readonly mount: MountScope

  /**
   * Run a tool call through the full agent-service gate. On authorization, the
   * dispatch is applied to the server-mounted binding AND, if a browser bridge
   * is attached, forwarded to it as a {@link BridgeInvokeMessage}.
   *
   * Returns the agent-service envelope: `{ result }` on success, or
   * `{ error, code, jsonrpc }` on a gate rejection (404/401/403/429). The HTTP
   * code is never mutated here — the gate is the sole policy authority.
   */
  callTool(toolName: string, params: unknown, ctx?: RequestContext): Promise<unknown>

  /** Current serialized state of the server-mounted component. */
  serialize(): Snapshot

  /**
   * Attach a browser bridge channel. Approved invocations are forwarded to it;
   * `result`/`error`/`snapshot` frames from it resolve pending `callTool`
   * promises. Returns a detach function.
   *
   * The channel MUST complete the `hello` handshake (see
   * {@link BRIDGE_PROTOCOL_VERSION}) before anything is delegated to it. Until
   * it does, `hello` is the only frame it may send, and `callTool` refuses to
   * forward — an unverified channel never becomes the execution authority.
   * Attaching a new channel always resets this state; verified status is never
   * inherited from a previous peer.
   */
  attachBridge(channel: BridgeChannel): () => void

  /**
   * Build the MCP `Server` (stdio-ready) exposing each component action as an
   * MCP tool backed by `callTool`. Lazily constructed; the SDK is imported only
   * when this is called. See {@link createComponentMcpServer}.
   */
  // (the MCP server factory is a separate export to keep the SDK import lazy)

  /** Tear down: dispose the mount + detach any bridge. */
  dispose(): void
}
11

AgentServerOptions

interfaceagent
interface AgentServerOptions {
  /** The component to mount + drive. */
  target: AgentServerTarget
  /**
   * Optional host factory for the server-side mount. When omitted,
   * `createAgentServer` stands up an internal jsdom-backed global DOM (if the
   * runtime has none) and mounts into a fresh detached `<div>` — so a plain
   * Bun/Node consumer needs no DOM glue at all. Provide this only to mount into
   * a specific host element (an explicit factory always wins).
   */
  createHost?: () => Element
  /** Auth plugin — forwarded to `createAgentService` for `$scope` checks. */
  authPlugin?: AuthPlugin
  /** Rate-limit plugin — forwarded to `createAgentService`. */
  rateLimitPlugin?: RateLimitPlugin
  /**
   * Per-request auth resolver — forwarded to `createAgentService.asMiddleware`.
   * Lets scoped/rate-limited tools be reachable over the bundled HTTP path.
   */
  resolveAuth?: AgentServiceOptions['resolveAuth']
  /**
   * Auth-discovery URL forwarded to `createAgentService` — included in every
   * 401 envelope so a refused agent knows where to obtain a credential (e.g.
   * the deployment's `/.well-known/oauth-protected-resource`). Informational
   * only; never a policy input.
   */
  authDiscoveryUrl?: AgentServiceOptions['authDiscoveryUrl']
  /**
   * How long (ms) `callTool` waits for an attached bridge channel to complete
   * its `hello` handshake before refusing to delegate to it (503
   * `BRIDGE_UNVERIFIED`). Defaults to 1000ms.
   *
   * The wait exists only to absorb the attach/connect race over a real socket;
   * it is not a grace period. A channel that sends a MISMATCHED or non-numeric
   * protocol version is rejected immediately, without waiting out this timeout.
   */
  bridgeHandshakeTimeoutMs?: number
}
12

AgentServerTarget

interfaceagent
interface AgentServerTarget {
  /** arbor render tree (root `Node`) to mount server-side. */
  node?: Node
  /** Compiler-emitted `__agentBinding` server export for the component. */
  agentBinding?: AgentBindingSpec
  /** A pre-built `MountScope` (its binding is already registered). */
  mount?: MountScope
}

The thing being driven.

13

BridgeChannel

interfaceagent
interface BridgeChannel {
  /** Send one (already-serialized) message frame to the peer. */
  send(data: string): void
  /** Register a frame handler. Returns an unsubscribe function. */
  onMessage(handler: (data: string) => void): () => void
  /** Register a close handler. Returns an unsubscribe function. */
  onClose(handler: () => void): () => void
  /** True while the channel can still deliver frames. */
  readonly connected: boolean
}

The minimal duplex transport the bridge needs.

14

BridgeClient

interfaceagent
interface BridgeClient {
  /** Stop handling frames. Does not close the underlying channel. */
  dispose(): void
}

A running bridge client.

15

BridgeClientOptions

interfaceagent
interface BridgeClientOptions {
  /** The compiler-emitted `__agentDispatcher` for the mounted component. */
  dispatcher: AgentDispatcher
  /** Duplex channel to the server (a `ws` WebSocket in production). */
  channel: BridgeChannel
  /**
   * Optional snapshot source (e.g. the mount's `serialize`). When provided, a
   * `snapshot` frame is pushed after each successful invocation so the server
   * and any read-only viewer reflect the visible instance's new state.
   */
  serialize?: () => unknown
}
16

BridgeErrorMessage

interfaceagent
interface BridgeErrorMessage {
  type: 'error'
  callId: string
  message: string
}

Client → server.

17

BridgeHelloMessage

interfaceagent
interface BridgeHelloMessage {
  type: 'hello'
  protocol: number
}

Handshake, client → server, sent once on connect.

18

BridgeInvokeMessage

interfaceagent
interface BridgeInvokeMessage {
  type: 'invoke'
  /** Correlates the eventual `result`/`error`/`snapshot` reply. */
  callId: string
  /** Opaque action identifier the client maps to a concrete action. */
  opaqueActionId: string
  /** Positional arguments for the action. */
  args: unknown[]
}

Server → client.

19

BridgeResultMessage

interfaceagent
interface BridgeResultMessage {
  type: 'result'
  callId: string
  result: unknown
}

Client → server.

20

BridgeSnapshotMessage

interfaceagent
interface BridgeSnapshotMessage {
  type: 'snapshot'
  callId?: string
  snapshot: Snapshot
}

Client → server (or server → a read-only viewer).

21

BridgeClientMessage

typeagent
type BridgeClientMessage =
  | BridgeHelloMessage
  | BridgeResultMessage
  | BridgeErrorMessage
  | BridgeSnapshotMessage

Any message a client may send to the server over the bridge.

22

BridgeServerMessage

typeagent
type BridgeServerMessage = BridgeInvokeMessage

Any message the server may send to a client over the bridge.