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
channelIdfrom the standardchannel-{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
Install
Before You Start
In the Swarmd dashboard:- Create a channel for the website or service.
- Save the returned
clientIdandclientSecret. The secret is shown only when the channel is created. - Subscribe the channel to the agent it should invoke.
- Copy the subscribed agent’s
agentId.
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: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
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
.env.local. These variables deliberately have no NEXT_PUBLIC_ prefix, so Next.js keeps them on the server:
3. Create the server-side client
4. Add the server route
The browser sends only the user’s message and its currentcontextId. The route creates the conversation on the first turn and reuses it on later turns.
5. Call the route from the browser
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.
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
UsesendEvents when events fit more naturally into an async pipeline or an SSE/WebSocket handler:
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 showWORKING 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_HELDmeans a policy or action needs reviewer approval.HITL_HELD_AGENT_INPUT_REQUIREDmeans the agent requested human input.
COMPLETED; rejection leads to REJECTED.
Conversation Continuity
Create a conversation once per chat session and reuse itscontextId:
contextId if polling must resume after a tab closes or a process restarts:
Custom Messages and Cancellation
Send structured A2A parts instead of plain text:AbortSignal:
Errors
401, it clears the cached token, obtains a fresh token, and retries that request once.
Configuration
Method Reference
Legacy JSON-RPC Integrations
The Hotels demo originally implementedmessage/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.