Skip to main content
@fishaudio/agent-client runs a live voice conversation with your agent from any web page: open the microphone, stream audio both ways, and react to typed events for transcripts, agent state, and tool calls. The SDK handles the realtime transport (WebRTC) internally. Using React? @fishaudio/agent-react wraps this SDK in hooks and a provider.

Install

Start a session

AgentSession.start() creates the session, connects, and opens the microphone in one call. Authenticate one of two ways:
  • agentId: for public agents. The SDK creates the session directly from the browser; no backend needed.
  • sessionToken: for private agents. Your backend calls POST /v1/agent/sessions with your API key and hands the JSON response to the browser; pass it through unchanged. See Authenticated sessions.
start() resolves once the realtime connection is up and rejects with a FishAgentError if session creation, the microphone permission, or the connection fails. Call it from a user gesture (a click handler): browsers only grant the microphone and audio playback inside one.

Options

Callbacks shorthand

Session lifecycle

A session moves through a fixed state machine, surfaced by the statusChange event and the session.status property:
States
start() resolves once the realtime connection is up; the agent itself joins moments later. If it has not joined within 15 seconds, the session emits a connection_failed error and ends with reason connection_lost instead of idling on a dead call. When the session ends, disconnect fires with a reason (also available as session.endReason): Brief network drops don’t end the session: the SDK moves to reconnecting and back to connected automatically, reusing the same session. It never creates a new session, so you never need a new token mid-call.
Events are not replayed after a reconnect. Anything emitted while you were in reconnecting (transcript updates, tool events, errors) is dropped, not resent. Design your UI to tolerate gaps: tool toolCallCompleted / toolCallFailed events repeat the tool name and source, so a terminal event still renders even if you missed toolCallStarted.
The session also exposes sessionId (use it to look up the conversation record later), status, mode, isSpeaking, micMuted, and endReason as properties.

Events

The session is a typed event emitter: subscribe with session.on(event, handler), remove with off, or use once.
Subscribe to events

Transcript semantics

Transcripts on both sides arrive as segments, one segment per utterance or response, identified by segmentId:
  • User segments: interim results replace the entire segment text (they never append). Render by upserting on segmentId; final: true marks the segment as finalized.
  • Agent segments: text streams in sync with audio playback: what you display matches what the user has actually heard. If the agent is interrupted, the segment finalizes containing only the words that were spoken. A reply that spans several speech segments (for example around a tool call) arrives as several segments.
  • The message event delivers only finalized messages from both sides, in order. Use it when you want a simple transcript list without handling interim updates.
A listener attached late misses the events fired before it subscribed. session.getTranscript() returns every segment so far, as { segmentId, role, text, final } in conversation order: seed your UI from it, then keep applying live events by segmentId.

Tool call lifecycle

Every tool the agent runs (webhook, client, or system tool) emits one toolCallStarted, resolved by exactly one toolCallCompleted or toolCallFailed with the same callId. input and output are JSON strings truncated at 4 KB; parse them only when the matching *Truncated flag is false. These events carry tool arguments and results into the end user’s browser: to keep them off the client, start with toolEvents: false, or create the session with tool_events: false on your backend.

Agent modes

session.mode (and the modeChange event) tracks what the agent is doing, for driving an orb or status indicator: session.isSpeaking is a convenience boolean for the speaking mode. Mode does not react to the user’s own speech. For instant “the mic hears you” feedback, poll getInputVolume() locally.

Send text

Users can type instead of talking, in the same session:
Text input
Text-only replies are not paced to audio playback: the transcript streams as fast as it generates. Messages sent right after start() resolves, before the agent has finished joining, are held and delivered in order once it is ready.

Audio controls

Device ids come from navigator.mediaDevices.enumerateDevices(). To pick devices before the call starts, pass the audio option (inputDeviceId / outputDeviceId) to start() instead.
Audio visualizer

Client tools

Register handlers for tools of type client declared on the agent. The agent calls them mid-conversation and your return value goes back to the model:
Register a client tool
Handlers can be sync or async; a thrown error or a timeout (default 15 s, set with clientToolTimeoutMs) is returned to the agent as a tool error. See Client tools for declaration, naming rules, result size, and dispatch semantics.

Errors

Failures surface as FishAgentError: thrown from AgentSession.start() when the session can’t be created, emitted on the error event otherwise. Each carries a code, an optional statusCode (set on session-creation HTTP errors), and cause.
provider_error and internal_error carry only the category code. Raw provider or infrastructure details are never sent to the browser.
Handle errors

End the session

Hang up
Calling end() more than once, or while a previous call is still finishing, is safe. endReason stays readable on the ended session.

Underlying connection

The session API is transport-neutral: the server picks the transport when it creates the session, livekit (WebRTC) today. For needs the session API does not cover yet, such as connection-quality telemetry, session.getRoom() returns the live livekit-client Room, or undefined once the session has ended. Prefer the session API where one exists: code built on the Room couples to this SDK’s transport choice and its pinned livekit-client major, with no compatibility promise across SDK versions. See Wire protocol for the messages underneath.

Going further

React SDK

Hooks, provider, and visualizer components on top of this SDK.

Authenticated sessions

Create session tokens on your backend for private agents.

Public agents

Backend-free sessions with origin allow-lists and rate limits.

Wire protocol

The wire-level events underneath the SDK.