agentwire

@kovenlabs/agentwire

The zero-dependency core — bus, channels, interactive primitives, logging seams.

pnpm add @kovenlabs/agentwire

Zero runtime dependencies. Safe to import on the server or the client.

Exports

ExportKindSummary
publishfnbroadcast a payload on a channel (by name)
subscribefnlisten on a channel; returns unsubscribe
requestfnrequest/reply with timeout
_resetForTestingfnclear all channels (tests)
RequestOptionstype{ timeoutMs?, filter? }
RequestResulttype{ ok: true; value } | { ok: false }
chanfndeclare a typed channel token
defineChannelsfngroup channel tokens, preserving types
toolResultChannelfnthe tool:<name>:result channel
publishTofntyped publish over a token
subscribeTofntyped subscribe over a token
requestViafntyped request over tokens
Channeltypea typed channel token
PayloadOftypeextract a channel's payload type
CHAT_ABOUT_THIS_REASONconstdefault interrupt reason
isCompletedOutputfnguard for status: "completed"
isInterruptedOutputfnguard for status: "interrupted"
CompletedOutputBasetypecompleted output discriminant
InterruptedOutputBasetypeinterrupted output discriminant
InteractiveResolvePayloadtype{ tool, toolCallId, output }
noopLogger / consoleLoggerconstlogger defaults
noopTelemetryconsttelemetry default
serializeErrorfnerror → log-safe string
AgentLogger / AgentLogContexttypelogging interface
AgentTelemetry / AgentTelemetryEventtypetelemetry interface

The bus

publish

publish<P>(channel: string, payload: P): void — broadcast payload to every subscriber currently on channel. No-op if there are none. Handlers run synchronously, in registration order, over a snapshot of the subscriber set (so a handler may (un)subscribe during dispatch).

subscribe

subscribe<P>(channel: string, handler: (p: P) => void): () => void — listen on channel. Returns an unsubscribe function.

request

request<R, P>(requestChannel, replyChannel, payload, opts?) — publish on requestChannel, resolve with the first reply on replyChannel that passes the optional filter. Resolves { ok: false } after opts.timeoutMs (default 2000).

type RequestResult<R> = { ok: true; value: R } | { ok: false };

interface RequestOptions<R> {
  timeoutMs?: number;
  filter?: (reply: R) => boolean; // non-matching replies are ignored, not consumed
}

_resetForTesting

_resetForTesting(): void — clears every channel and subscriber. Test-only.

Typed channels

Prefer these over the raw string bus — the Channel<P> token carries the payload type so publish/subscribe/request are type-checked.

chan

chan<P>(name): Channel<P> — declare a typed channel token. P is carried at the type level only.

defineChannels

defineChannels(map): map — identity helper that preserves literal types; group your channel tokens (the typed replacement for a hand-maintained constants object).

import { chan, defineChannels } from "@kovenlabs/agentwire";

export const events = defineChannels({
  request: chan<{ version: string }>("page:request"),
  reply: chan<{ values: number }>("page:reply"),
});

toolResultChannel

toolResultChannel(toolName): Channel<unknown> — the conventional channel a server tool's result is published on: tool:<toolName>:result.

Typed wrappers

publishTo / subscribeTo / requestVia mirror publish / subscribe / request but take channel tokens and enforce the payload type.

import { chan, publishTo, subscribeTo, requestVia } from "@kovenlabs/agentwire";

const ping = chan<{ n: number }>("ping");
const pong = chan<{ n: number }>("pong");

subscribeTo(ping, (p) => p.n); // p: { n: number }
publishTo(ping, { n: 1 });
const reply = await requestVia(ping, pong, { n: 1 }); // RequestResult<{ n: number }>

PayloadOf

PayloadOf<C> — type helper: extract the payload type from a Channel.

Interactive primitives

Generic building blocks for deferred tools (an app supplies its own tool-name union and per-tool output shapes).

  • CHAT_ABOUT_THIS_REASON — default reason for a "user bailed to chat" interrupt.
  • InteractiveResolvePayload<TName, TOutput> — { tool, toolCallId, output }, the payload published to settle a call.
  • CompletedOutputBase / InterruptedOutputBase — discriminated bases for status: "completed" | "interrupted" outputs.
  • isCompletedOutput(output) / isInterruptedOutput(output, reason?) — guards.
import { isInterruptedOutput, CHAT_ABOUT_THIS_REASON } from "@kovenlabs/agentwire";

if (isInterruptedOutput(output, CHAT_ABOUT_THIS_REASON)) {
  // the user chose to chat instead of answering
}

Logging

The library ships no concrete logger. Implement the interface, or use a default.

interface AgentLogger {
  info(message: string, context?: AgentLogContext): void; // AgentLogContext = Record<string, unknown>
  error(message: string, context?: AgentLogContext): void;
}

interface AgentTelemetry {
  capture(event: AgentTelemetryEvent): void; // { tool, toolCallId, outcome, durationMs, … }
}

Provided implementations: noopLogger, consoleLogger, noopTelemetry. serializeError(error) turns any thrown value into a log-safe string.