Skip to main content

TypeScript Channel Client

@swarmd/channel-client is the recommended way to connect a website, bot, or backend service to an agent through a Swarmd channel. It wraps the Conversation REST API and handles:
  • OAuth2 client-credentials exchange, token caching, and refresh
  • deriving channelId from the standard channel-{channelId} client ID
  • conversation creation and follow-up message continuity
  • the send fast path and background polling
  • typed lifecycle events for working, hitl-held, and terminal outcomes
  • cancellation with AbortSignal
  • text and artifact reply extraction
  • structured authentication, API, and polling errors
clientSecret is a server credential. Use this library in a Node.js server, API route, server action, worker, or other trusted backend. The client refuses to initialize when it detects a browser. Never put the secret in a NEXT_PUBLIC_*, VITE_*, or other client-exposed environment variable.

Install

Node.js 18 or later is supported. The package has no runtime dependencies and uses the built-in Fetch API.

Before You Start

In the Swarmd dashboard:
  1. Create a channel for the website or service.
  2. Save the returned clientId and clientSecret. The secret is shown only when the channel is created.
  3. Subscribe the channel to the agent it should invoke.
  4. Copy the subscribed agent’s agentId.
See Your First Agent for the API-based setup. Configure server-only environment variables:
The client derives channelId from clientId. If your OAuth client does not use the standard channel-{channelId} form, also pass the channel UUID as channelId.

Quick Start

Create one reusable client on the server:
Start a conversation and wait for the first reply:
startAndSend creates a conversation, sends the message, and polls if the relay has not completed during its early-return window.

Complete Next.js Example

This example starts with a standard TypeScript Next.js application and ends with a browser chat connected to a Swarmd channel.

1. Create the project

The example uses this structure:

2. Connect the channel

Create a channel in Swarmd, subscribe it to the agent your website should invoke, and copy:
  • the channel clientId
  • the channel clientSecret
  • the subscribed agent’s agentId
Add them to .env.local. These variables deliberately have no NEXT_PUBLIC_ prefix, so Next.js keeps them on the server:

3. Create the server-side client

Exporting one client lets every request share the OAuth token cache.

4. Add the server route

The browser sends only the user’s message and its current contextId. The route creates the conversation on the first turn and reuses it on later turns.

5. Call the route from the browser

Run the project:
The first message creates a durable conversation. The response returns its contextId, and the component includes that ID in subsequent messages so the agent retains the conversation history.
This compact example keeps the /api/chat request open while the SDK polls. That is suitable for ordinary flows. If an approval may take minutes or hours, return the initial sendMessage state to the browser and expose a separate state endpoint, as shown in the split send-and-poll flow below.

Handle Lifecycle Events

For most integrations, onEvent is the idiomatic way to react to progress. Events are a discriminated union, so TypeScript narrows the available fields inside each case.
The callback fires for the initial send response and whenever the lifecycle event changes. Repeated polls in the same state are de-duplicated. Applications do not need to interpret HTTP 200 versus 202, inspect task status, or decide when polling should stop. Use the lower-level onState callback if you need every state snapshot instead. Every event includes the original ConversationState as event.state, so uncommon metadata and per-task progress remain available without weakening the common API.

Async event stream

Use sendEvents when events fit more naturally into an async pipeline or an SSE/WebSocket handler:
The iterable ends automatically after a terminal event. watchConversation(contextId) provides the same event stream without sending a new message, which is useful when resuming a persisted conversation.
The package deliberately does not expose a React hook that talks directly to Swarmd: doing so would place the channel secret in the browser bundle. A React application should consume its own API route, SSE stream, or WebSocket and map these server-side lifecycle events into component state.

Split Send and Poll Flow

Use the low-level methods when your frontend needs to show WORKING or HITL_HELD immediately.
sendMessage uses aggregateState, not only the HTTP status, to decide whether work is pending. waitForTerminal stops on COMPLETED, REJECTED, FAILED, or CANCELED.

Handling UI States

For HITL_HELD, inspect state.latestTask?.metadata?.relay_reason:
  • HITL_HELD means a policy or action needs reviewer approval.
  • HITL_HELD_AGENT_INPUT_REQUIRED means the agent requested human input.
Approval happens in Swarmd or through the approval API. The channel client continues polling and observes the result. Approval normally leads to COMPLETED; rejection leads to REJECTED.

Conversation Continuity

Create a conversation once per chat session and reuse its contextId:
The conversation remains durable on the relay. Store the contextId if polling must resume after a tab closes or a process restarts:
For long histories:

Custom Messages and Cancellation

Send structured A2A parts instead of plain text:
Cancel local waiting with an AbortSignal:
This stops the local request or poll loop; it does not cancel server-side agent work.

Errors

The client caches OAuth tokens until 30 seconds before expiry. If an API request returns 401, it clears the cached token, obtains a fresh token, and retries that request once.

Configuration

Only override URLs or scope for a Swarmd environment whose operator supplied different values.

Method Reference

Legacy JSON-RPC Integrations

The Hotels demo originally implemented message/send and tasks/get directly over the channel JSON-RPC endpoint. It now consumes this SDK through the repository-local dependency file:../../sdks/typescript/packages/channel-client, so its deployed container continuously validates the same Conversation REST implementation customers receive. The Prismforce integration established the newer model: one stable contextId, aggregate state, and a state poll endpoint. @swarmd/channel-client follows that model. Existing JSON-RPC integrations can remain in place, but new website integrations should use this library. See Frontend Integration (JSON-RPC — legacy) when maintaining an older integration.