npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@microsoft/agent-host-protocol

v0.3.0

Published

TypeScript client for the Agent Host Protocol (AHP).

Readme

@microsoft/agent-host-protocol

TypeScript client for the Agent Host Protocol (AHP).

npm

Browser-friendly client built on the global WebSocket API. Works in modern browsers and Node 21+ without additional runtime dependencies.

Entry points

The package exposes four subpath exports:

| Import path | What it gives you | |---|---| | @microsoft/agent-host-protocol | Wire types, actions, commands, reducers, version constants. No I/O. | | @microsoft/agent-host-protocol/client | AhpClient, Subscription, AhpStateMirror, the AhpTransport interface, InMemoryTransport, and the error taxonomy. | | @microsoft/agent-host-protocol/hosts | MultiHostClient, HostClientHandle, ReconnectPolicy, ClientIdStore (with InMemoryClientIdStore), MultiHostStateMirror, and the Host*Error family. Builds on /client to manage one or more host connections with reconnect, generation-checked handles, and fan-in events. | | @microsoft/agent-host-protocol/ws | WebSocketTransport — an AhpTransport implementation backed by the global WebSocket. |

The split mirrors the Rust SDK (ahp-types, ahp, ahp::hosts, ahp-ws) — wire types and reducers are decoupled from the client, which is in turn decoupled from a specific transport and from the multi-host orchestration layer.

Quickstart

import { ActionType, type ActionEnvelope } from '@microsoft/agent-host-protocol';
import { AhpClient, AhpStateMirror } from '@microsoft/agent-host-protocol/client';
import { WebSocketTransport } from '@microsoft/agent-host-protocol/ws';

const transport = await WebSocketTransport.connect('ws://localhost:12345');
const client = new AhpClient(transport);
const mirror = new AhpStateMirror();

client.connect();

const init = await client.initialize({
  clientId: 'my-client',
  protocolVersions: ['0.3.0'],
  initialSubscriptions: ['ahp-root://'],
});

for (const snapshot of init.snapshots) {
  mirror.applySnapshot(snapshot);
}

const root = client.attachSubscription('ahp-root://');
(async () => {
  for await (const event of root) {
    if (event.type === 'action') mirror.apply(event.params);
  }
})();

const sessionUri = `ahp-session:/${crypto.randomUUID()}`;
client.dispatch(sessionUri, {
  type: ActionType.SessionTurnStarted,
  // … remaining action fields
} as unknown as ActionEnvelope['action']);

Pluggable transports

AhpClient is transport-agnostic. Any framed message stream — a WebSocket, a Unix socket, stdio, or an in-memory pair for tests — can back an AhpTransport:

import type { AhpTransport, TransportFrame, JsonRpcMessage } from '@microsoft/agent-host-protocol/client';

class MyTransport implements AhpTransport {
  send(message: JsonRpcMessage | string): void { /* … */ }
  async recv(): Promise<TransportFrame | null> { /* … */ }
  close(): void { /* … */ }
}

InMemoryTransport.pair() returns two connected halves that exchange text frames — handy for unit tests that don't need a real socket.

Reducers and state mirror

The reducer functions (rootReducer, sessionReducer, terminalReducer, changesetReducer) are pure: replaying actions in serverSeq order on any prior snapshot yields identical state. This is the same property the Rust and Swift clients rely on for reconnection.

AhpStateMirror is a convenience that holds one RootState, a Map<URI, SessionState>, a Map<URI, TerminalState>, and a Map<URI, ChangesetState>. Apply Snapshots and ActionEnvelopes and it keeps those maps up to date. Larger apps usually keep their own state and call the reducers directly.

Errors

| Class | When it's thrown | |---|---| | RpcError | JSON-RPC error response from the server. Carries code, message, data. | | RpcTimeoutError | Client-side timeout fired before the server responded. Carries method, timeoutMs. Distinct from RpcError. | | TransportError | Failure of the underlying transport. kind: 'closed' \| 'io' \| 'protocol'. | | ClientClosedError | Request was in flight when the client was shut down. | | AhpClientError | Base class for every error this SDK throws — use instanceof to catch them all. |

Malformed inbound frames don't throw — they're logged via console.warn and the channel stays alive (matching the Rust client's tracing::warn! behavior). Pending requests still time out via RpcTimeoutError if the dropped frame would have been their reply.

Server-initiated requests

Some AHP methods (currently resourceRequest) can be initiated by the server. By default, the client responds with JSON-RPC MethodNotFound so the server does not leak a pending request. Install a typed handler to take over:

client.setServerRequestHandler(async (method, params) => {
  if (method === 'resourceRequest') {
    return { /* … */ };
  }
  throw new RpcError(JsonRpcErrorCodes.MethodNotFound, 'unhandled');
});

Reconnection

AhpClient.reconnect(...) sends the typed AHP reconnect request on an already-open transport. It does not decide when to reconnect, how often to retry, whether authentication errors are terminal, or how to update UI while reconnecting — those policies live in the app.

A typical app-level reconnect flow is:

  1. Open a fresh transport and AhpClient.
  2. Attach event streams before the handshake.
  3. Call connect() and reconnect({ clientId, lastSeenServerSeq, subscriptions }).
  4. Apply the returned replay actions or snapshots to your app store.
  5. Re-fetch listSessions or other ephemeral data — protocol notifications are not replayed.

If you'd rather not write that loop yourself, see @microsoft/agent-host-protocol/hosts — it ships a MultiHostClient that owns the reconnect supervisor, re-subscribes across reconnects, mirrors root state, and exposes generation-checked client handles. Single-host consumers use MultiHostClient.single(...).

Multi-host orchestration

For apps that talk to one or more AHP hosts at once (a local sessions server plus a tunnel-attached remote, multiple project hosts in a sidebar, …), the @microsoft/agent-host-protocol/hosts entry point ships MultiHostClient:

import { ActionType } from '@microsoft/agent-host-protocol';
import { WebSocketTransport } from '@microsoft/agent-host-protocol/ws';
import {
  MultiHostClient,
  type HostTransportFactory,
} from '@microsoft/agent-host-protocol/hosts';

const openLocal: HostTransportFactory = async (_hostId, _signal) =>
  WebSocketTransport.connect('ws://localhost:12345');

// Single-host: same API, never see "registry" concepts.
const { multi, host } = await MultiHostClient.single({
  id: 'local',
  label: 'Local sessions server',
  transportFactory: openLocal,
});
console.log(`connected to ${host.label}: ${host.state.status}`);

// Multi-host: add as many as you need.
await multi.addHost({
  id: 'tunnel',
  label: 'Tunnel',
  transportFactory: async (_id, _signal) =>
    WebSocketTransport.connect('wss://my-tunnel.example/sessions'),
});

// Fan-in of every inbound event, tagged with host of origin.
for await (const event of multi.events()) {
  console.log(`[${event.hostId}] ${event.channel}`, event.event.type);
}

Each host runs its own reconnect supervisor with the configured ReconnectPolicy (defaults to exponential backoff from 250 ms to 30 s with 25 % jitter), re-subscribes to known URIs after reconnects, and mirrors root state plus a session-summary cache so MultiHostClient .aggregatedSessions() and aggregatedAgents() are snapshot reads, not fan-out subscriptions. Every successful (re)connect bumps a per- host generation counter; HostClientHandles minted at an earlier generation throw HostReconnectedError instead of silently writing to the new connection.

Persistent clientIds are pluggable via the ClientIdStore interface. The default InMemoryClientIdStore is session-stable; production apps that need cross-launch identity wrap their platform's secure storage (localStorage, IndexedDB, Node fs, safeStorage, …) in a custom ClientIdStore.

For multi-host state, MultiHostStateMirror keys per-resource state by (hostId, uri) so URIs that legitimately collide across hosts (the normal case for session URIs) don't clobber each other.

Wire types

The wire types under src/types/ are generated from types/*.ts at the repository root and are not committed to the repo — avoiding a byte-for-byte duplication of the canonical TypeScript sources. Regenerate them whenever you pull or change the protocol:

npm run generate:typescript    # from the repo root

Generated files carry a banner; do not edit them by hand. The generate:typescript script is also part of npm run generate, which regenerates every language's client output.

Protocol version mapping

This package exports two protocol-version constants from its default entry point:

import { PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS } from '@microsoft/agent-host-protocol';
  • PROTOCOL_VERSION — SemVer string for the version this package's source tree implements.

  • SUPPORTED_PROTOCOL_VERSIONS — every version this package is willing to negotiate (most-preferred-first). Pass it as protocolVersions on InitializeParams:

    await client.initialize({
      clientId: 'my-client',
      protocolVersions: [...SUPPORTED_PROTOCOL_VERSIONS],
      initialSubscriptions: ['ahp-root://'],
    });

The same information is mirrored, in machine-readable form, in release-metadata.json and, in human-readable form, in CHANGELOG.md. CI verifies all three sources agree on every PR.

Development

From a fresh checkout:

# 1. Install the root tooling and generate the TS client's wire types.
npm install
npm run generate:typescript

# 2. Work in the client package.
cd clients/typescript
npm install
npm run typecheck
npm test
npm run build

CI runs the generate step automatically before the install/typecheck/test/build sequence, so contributors only need to remember step 1 locally after pulling protocol changes.

License

MIT