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

@synadia-ai/agent-service

v0.5.2

Published

Server-side TypeScript SDK for the Synadia Agent Protocol for NATS — host an agent (AgentService, ReferenceAgent, server-side helpers).

Readme

@synadia-ai/agent-service

Server-side TypeScript SDK for the Synadia Agent Protocol for NATS. Host an agent — register the agents micro service, serve the prompt and status endpoints, publish heartbeats, and stream typed chunks back to callers.

Pairs with @synadia-ai/agents (the caller-side SDK). Agent harness authors install both — caller imports stay on @synadia-ai/agents (subjects, envelope types, errors), host imports come from @synadia-ai/agent-service (AgentService, ReferenceAgent, server-side wire helpers). The two packages release in lockstep.

  • AgentService handles the §12 agent-checklist boilerplate: registration, prompt + status endpoints, heartbeat loop, per-request keep-alive, terminator emission, 400/500 error handling.
  • extraEndpoints + .service getter — register custom endpoints (e.g. spawn / stop / list on a controller agent) alongside the protocol-required ones, with collision validation. The getter is an escape hatch for runtime-dynamic registration.
  • ReferenceAgent — spec-compliant test counterparty exposed via the /testing subpath.
  • Runs on Node ≥ 20 and Bun ≥ 1.2.

Install

bun add @synadia-ai/agents @synadia-ai/agent-service
# or: npm install @synadia-ai/agents @synadia-ai/agent-service
# or: pnpm add @synadia-ai/agents @synadia-ai/agent-service

30-second quickstart — host an agent

You bring a NatsConnection; the SDK uses it. Use @nats-io/transport-node for TCP (nats://, tls://) or wsconnect from @nats-io/nats-core for WebSocket (ws://, wss://).

import { connect } from "@nats-io/transport-node";
import { AgentService } from "@synadia-ai/agent-service";

const nc = await connect({ servers: "nats://localhost:4222" });

const service = new AgentService({
  nc,
  agent: "echo", // metadata.agent — canonical harness identifier
  owner: "demo", // metadata.owner — operator / account namespace
  name: "main", // 5th subject token — instance name
  description: "Echo agent demo",
  heartbeatIntervalS: 30,
});

service.onPrompt(async (envelope, response) => {
  // The handler can stream as many chunks as it wants — terminator is automatic.
  await response.send(`echo: ${envelope.prompt}`);
});

await service.start();
console.log(`listening on ${service.subject.prompt}`);

service.start() is everything: it adds the prompt and status endpoints with the right queue groups, advertises the broker-derived max_payload, kicks off the heartbeat publisher (with an immediate first beat so discovery is prompt), and stays running until you call service.stop().

The matching caller-side code lives next to @synadia-ai/agents — see its README for discover() / prompt().

Custom endpoints

A controller-style agent often needs more than the protocol-required prompt + status. Declare them upfront with extraEndpoints:

import { AgentService, type AgentServiceExtraEndpoint } from "@synadia-ai/agent-service";

const spawn: AgentServiceExtraEndpoint = {
  name: "spawn",
  subject: "agents.spawn.echo.demo.main",
  queue: "echo-controllers",
  handler: (err, msg) => {
    if (err) return;
    msg.respond(new TextEncoder().encode(`spawned`));
  },
  metadata: { role: "controller" },
};

const service = new AgentService({
  nc,
  agent: "echo",
  owner: "demo",
  name: "main",
  extraEndpoints: [spawn /*, stop, list, … */],
});

start() validates names against prompt, status, and other extraEndpoints entries, so a collision fails fast before any registration happens. Subjects are advertised verbatim — the SDK does not prefix them.

For runtime-dynamic registration, use the .service getter as an escape hatch:

await service.start();
service.service.addEndpoint("late-bound", {
  /* … */
});

The getter throws if accessed before start(), and direct calls bypass extraEndpoints's duplicate-name guard — prefer the declarative form.

Wire helpers

The SDK exports the chunk and heartbeat encoders for harnesses that need them outside the AgentService flow (e.g. an event-driven streamer that doesn't fit the closed-handler shape):

| Export | Purpose | | -------------------------------------------------------------- | -------------------------------------------------------------------------- | | encodeChunk(chunk) | Encode a typed chunk (response / status / query) to wire JSON bytes. | | splitResponseText(text, maxBytes, opts?) | UTF-8-safe chunker for long response payloads. | | buildHeartbeatPayload(subject, intervalS, instanceId, opts?) | Build a §8.3 heartbeat / status payload. | | encodeHeartbeatPayload(payload) | Encode that payload to wire JSON bytes. | | DEFAULT_MAX_PAYLOAD / DEFAULT_* constants | Fallback values when no broker INFO.max_payload is reported, etc. |

The agents/openclaw, agents/pi, and agents/claude-code harnesses in this monorepo use these primitives directly today; the controller agents in examples/pi-headless and examples/claude-code-headless are the obvious migration candidates for AgentService.

Reference agent (@synadia-ai/agent-service/testing)

import { connect } from "@nats-io/transport-node";
import { ReferenceAgent } from "@synadia-ai/agent-service/testing";

const nc = await connect({ servers: "nats://localhost:4222" });
const ref = new ReferenceAgent({
  nc,
  agent: "echo",
  owner: "demo",
  name: "ref",
  heartbeatIntervalS: 1,
});
await ref.start();

ReferenceAgent implements the full §12 agent checklist with no-frills defaults — useful as a counterparty in caller-side integration tests and for cross-SDK interop checks. It accepts a custom promptHandler callback (a raw ServiceMsg) so tests can assert on malformed inputs, drop chunks, and emit unknown shapes that production agents would never produce. For real harnesses use AgentService instead.

The caller SDK's integration tests use ReferenceAgent as their agent counterparty — see client-sdk/typescript/test/integration/.

What's in the box

| API | Purpose | | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | | new AgentService({ nc, agent, owner, name, ... }) | Register and run a protocol-compliant agent. | | service.onPrompt(handler) | Wire up the prompt handler. (envelope, response) => …. | | service.start() / service.stop() | Lifecycle. | | service.subject / service.instanceId / .service | Inspection: subject builder, service id, underlying micro service. | | extraEndpoints option | Declarative custom endpoints. | | PromptResponse.send / .ask | Stream chunks back; .ask round-trips a §7 mid-stream query. | | ReferenceAgent (/testing) | Spec-compliant counterparty for tests. | | encodeChunk, splitResponseText, buildHeartbeatPayload, encodeHeartbeatPayload | Wire primitives. | | DEFAULT_ATTACHMENTS_OK, DEFAULT_HEARTBEAT_INTERVAL_S, DEFAULT_KEEPALIVE_INTERVAL_S, DEFAULT_MAX_PAYLOAD | Server-side defaults. |

Subpath exports:

  • @synadia-ai/agent-service/testing — the ReferenceAgent helper.

The error class hierarchy lives on the caller side at @synadia-ai/agents/errors — both packages share the same types so an instanceof check on either side reaches the same class.

Local development

The package depends on @synadia-ai/agents via a file: link to the sibling caller package; both packages need a current dist/ for consumers to load. Build caller first, then host:

(cd ../../client-sdk/typescript && bun run build)
(cd ../../agent-sdk/typescript  && bun install && bun run build)

The repo's README-DEV.md covers the build/install dance for every common workflow (running examples, installing the agent plugins locally, running test suites).

Contributing

bun install          # or: npm install
bun run typecheck
bun run lint
bun run test:unit         # no NATS required
bun run test:integration  # spawns nats-server - install via brew / apt / https://github.com/nats-io/nats-server/releases

Integration tests skip cleanly with a friendly message if nats-server isn't on PATH.

License

Apache-2.0