@fishaudio/agent-client 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/transport/livekit.ts","../src/index.ts","../src/audio/output.ts","../src/audio/analysis.ts","../src/session/agentSession.ts","../src/sessionToken.ts","../src/session/emitter.ts","../src/session/toolDispatcher.ts","../src/session/wakeLock.ts"],"sourcesContent":["export type FishAgentErrorCode =\n | \"session_request_failed\"\n | \"agent_not_public\"\n | \"origin_forbidden\"\n | \"unsupported_transport\"\n | \"mic_permission_denied\"\n /** A requested audio device could not be activated, or the browser does not support selecting it. */\n | \"device_change_failed\"\n | \"connection_failed\"\n | \"session_expired\"\n | \"tool_failed\"\n | \"tool_timeout\"\n /** An upstream model/speech provider failed during the session. */\n | \"provider_error\"\n /** The agent runtime hit an internal error. */\n | \"internal_error\";\n\nexport class FishAgentError extends Error {\n readonly code: FishAgentErrorCode;\n /** HTTP status of the failing session request, when the error originated there. */\n readonly statusCode?: number;\n\n constructor(\n code: FishAgentErrorCode,\n message: string,\n options?: { statusCode?: number; cause?: unknown },\n ) {\n super(message, options?.cause !== undefined ? { cause: options.cause } : undefined);\n this.name = \"FishAgentError\";\n this.code = code;\n this.statusCode = options?.statusCode;\n }\n}\n","import {\n ConnectionState,\n DisconnectReason,\n Room,\n RoomEvent,\n Track,\n TrackEvent,\n type RemoteTrack,\n} from \"livekit-client\";\nimport {\n AGENT_EVENT_TOPIC,\n CLIENT_EVENT_TOPIC,\n type AgentSessionMessage,\n type ClientSessionMessage,\n type SessionToken,\n} from \"@fishaudio/agent-protocol\";\nimport { FishAgentError } from \"../errors.js\";\nimport type { Transport, TransportCallbacks, TransportConnectOptions } from \"./types.js\";\n\nfunction hasMessageType(value: unknown): value is { type: string } {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { type?: unknown }).type === \"string\"\n );\n}\n\n/** Sole production Transport. LiveKit vocabulary must not escape this file,\n * except the `Room` handed out through the `getRoom()` escape hatch. */\nexport class LiveKitTransport implements Transport {\n private room?: Room;\n private callbacks?: TransportCallbacks;\n\n async connect(sessionToken: SessionToken, options: TransportConnectOptions): Promise<void> {\n this.callbacks = options.callbacks;\n const room = new Room({\n audioCaptureDefaults: {\n deviceId: options.inputDeviceId,\n echoCancellation: true,\n noiseSuppression: true,\n autoGainControl: true,\n },\n });\n this.room = room;\n\n room.on(RoomEvent.DataReceived, (payload, _participant, _kind, topic) => {\n if (topic !== AGENT_EVENT_TOPIC) {\n return;\n }\n let message: unknown;\n try {\n message = JSON.parse(new TextDecoder().decode(payload));\n } catch {\n return;\n }\n if (hasMessageType(message)) {\n this.callbacks?.onAgentEvent(message as AgentSessionMessage);\n }\n });\n\n const AGENT_STATE_ATTRIBUTE = \"lk.agent.state\";\n room.on(RoomEvent.ParticipantAttributesChanged, (changed, participant) => {\n const state = changed[AGENT_STATE_ATTRIBUTE];\n if (!participant.isLocal && state) {\n this.callbacks?.onAgentState(state);\n }\n });\n\n room.on(RoomEvent.TrackSubscribed, (track: RemoteTrack) => {\n if (track.kind === Track.Kind.Audio && track.mediaStreamTrack) {\n this.callbacks?.onOutputStream(new MediaStream([track.mediaStreamTrack]));\n }\n });\n\n // Both sides' transcripts ride LiveKit's built-in transcription streams:\n // agent segments are playout-synced delta streams (final on stream close —\n // an interrupted segment closes containing only the spoken text); user STT\n // segments are re-published per update with an explicit final attribute,\n // sent on behalf of the user, so sender identity distinguishes the roles.\n room.registerTextStreamHandler(\"lk.transcription\", async (reader, participantInfo) => {\n const attributes = reader.info.attributes ?? {};\n const segmentId = attributes[\"lk.segment_id\"];\n if (!segmentId) {\n return;\n }\n const role =\n participantInfo.identity === room.localParticipant.identity ? \"user\" : \"agent\";\n const finalAttribute = attributes[\"lk.transcription_final\"] === \"true\";\n let text = \"\";\n let aborted = false;\n try {\n for await (const delta of reader) {\n text += delta;\n this.callbacks?.onTranscription({ segmentId, role, text, final: false });\n }\n } catch {\n aborted = true; // interrupted mid-stream; what arrived is what was spoken\n }\n if (text) {\n const final = role === \"agent\" ? true : finalAttribute && !aborted;\n this.callbacks?.onTranscription({ segmentId, role, text, final });\n }\n });\n\n room.on(RoomEvent.Reconnecting, () => {\n this.callbacks?.onConnectionState(\"reconnecting\");\n });\n room.on(RoomEvent.Reconnected, () => {\n // A full reconnect replays the roster from the join response before this\n // event; an agent still missing here hung up while we were away.\n if (room.remoteParticipants.size === 0) {\n this.handleAgentLeft(room);\n return;\n }\n this.callbacks?.onConnectionState(\"connected\");\n });\n room.on(RoomEvent.Disconnected, (reason?: DisconnectReason) => {\n this.callbacks?.onConnectionState(\n \"disconnected\",\n reason === undefined ? undefined : DisconnectReason[reason],\n );\n // Remote-initiated ends never go through disconnect(); drop the refs here\n // so getRoom() honors its ended-means-undefined contract and the room\n // can be collected.\n if (this.room === room) {\n this.room = undefined;\n this.callbacks = undefined;\n }\n });\n // An agent-side hangup ends the worker without closing the room; the\n // agent participant leaving IS the end-of-call signal for the client.\n // A full reconnect also clears the roster this way, and can do so while\n // room.state still reads Connected (Reconnecting is only set right after),\n // so defer one microtask and require a room that is genuinely connected.\n room.on(RoomEvent.ParticipantDisconnected, () => {\n if (room.remoteParticipants.size === 0) {\n queueMicrotask(() => {\n if (room.state === ConnectionState.Connected && room.remoteParticipants.size === 0) {\n this.handleAgentLeft(room);\n }\n });\n }\n });\n\n try {\n await room.connect(sessionToken.livekit_url, sessionToken.token);\n } catch (cause) {\n throw new FishAgentError(\"connection_failed\", \"Could not establish the realtime session\", {\n cause,\n });\n }\n // The attribute is sticky: if the agent joined and set its state before us,\n // no change event will fire — replay the current value once.\n for (const participant of room.remoteParticipants.values()) {\n const state = participant.attributes?.[AGENT_STATE_ATTRIBUTE];\n if (state) {\n this.callbacks?.onAgentState(state);\n }\n }\n if (options.microphone !== false) {\n await room.localParticipant.setMicrophoneEnabled(true);\n this.publishInputStream();\n }\n }\n\n private handleAgentLeft(room: Room): void {\n this.callbacks?.onConnectionState(\"disconnected\", \"AGENT_LEFT\");\n void room.disconnect();\n }\n\n async disconnect(): Promise<void> {\n const room = this.room;\n this.room = undefined;\n this.callbacks = undefined;\n await room?.disconnect();\n }\n\n getRoom(): Room | undefined {\n return this.room;\n }\n\n async setMicEnabled(enabled: boolean): Promise<void> {\n await this.room?.localParticipant.setMicrophoneEnabled(enabled);\n if (enabled) {\n // After a mic-less connect the first enable captures the track late;\n // re-wiring an already-published track is a harmless replace.\n this.publishInputStream();\n }\n }\n\n async setInputDevice(deviceId: string): Promise<void> {\n const room = this.room;\n if (!room) {\n return;\n }\n const previous = room.getActiveDevice(\"audioinput\");\n let switched: boolean;\n try {\n // exact: a wrong id must fail loudly, not silently fall back to another mic.\n switched = await room.switchActiveDevice(\"audioinput\", deviceId, true);\n } catch (error) {\n // livekit stops the current track before acquiring the new device, so a\n // failed pick would otherwise leave the call silently mic-dead — switch\n // back before surfacing the failure. livekit reports \"default\" until the\n // real captured id is known; that pseudo-id only resolves on Chrome, so\n // restore it non-exactly and let other browsers fall back to any mic.\n if (previous !== undefined) {\n await room\n .switchActiveDevice(\"audioinput\", previous, previous !== \"default\")\n .catch(() => undefined);\n this.publishInputStream();\n }\n throw error;\n }\n if (!switched) {\n throw new FishAgentError(\n \"device_change_failed\",\n \"Could not activate the requested microphone\",\n );\n }\n // A live switch replaces the publication's MediaStreamTrack — re-wire\n // analysis. While muted, livekit defers the swap to the next unmute and\n // setMicEnabled re-wires then.\n this.publishInputStream();\n }\n\n /** Local tracks already wired to re-emit their stream when livekit restarts them. */\n private readonly restartWiredTracks = new WeakSet<object>();\n\n private publishInputStream(): void {\n const room = this.room;\n if (!room) {\n return;\n }\n for (const publication of room.localParticipant.audioTrackPublications.values()) {\n const track = publication.track;\n if (track && !this.restartWiredTracks.has(track)) {\n this.restartWiredTracks.add(track);\n // Restarts replace the MediaStreamTrack without any Room-level event —\n // livekit's devicechange auto-recovery and getRoom() escape-hatch\n // switches would otherwise leave input analysis on a dead track.\n track.on(TrackEvent.Restarted, () => this.publishInputStream());\n }\n const mediaStreamTrack = track?.mediaStreamTrack;\n if (mediaStreamTrack) {\n this.callbacks?.onInputStream(new MediaStream([mediaStreamTrack]));\n }\n }\n }\n\n async sendClientEvent(message: ClientSessionMessage): Promise<void> {\n if (!this.room) {\n throw new FishAgentError(\"connection_failed\", \"Session is not connected\");\n }\n const payload = new TextEncoder().encode(JSON.stringify(message));\n await this.room.localParticipant.publishData(payload, {\n reliable: true,\n topic: CLIENT_EVENT_TOPIC,\n });\n }\n}\n","// Public surface. Transport-neutral except the deliberate escape hatch\n// `AgentSession.getRoom()`, whose return type is livekit-client's `Room`.\nexport { AgentSession, type AgentSessionOptions } from \"./session/agentSession.js\";\nexport { DEFAULT_SERVER_URL } from \"./sessionToken.js\";\nexport { FishAgentError, type FishAgentErrorCode } from \"./errors.js\";\nexport type {\n AgentMode,\n AgentResponseDeltaEvent,\n AgentResponseEvent,\n AgentSessionCallbacks,\n AgentSessionEvents,\n ConversationMessage,\n EndReason,\n SessionStatus,\n ToolCallCompletedEvent,\n ToolCallFailedEvent,\n ToolCallStartedEvent,\n TranscriptSegment,\n UserTranscriptEvent,\n} from \"./events.js\";\nexport type { ClientToolHandler } from \"./session/toolDispatcher.js\";\nexport type {\n AgentSessionCreateRequest,\n SessionOverrides,\n SessionToken,\n ToolCallSource,\n} from \"@fishaudio/agent-protocol\";\n","import { FishAgentError } from \"../errors.js\";\n\n/** Throws `device_change_failed` where the browser cannot route audio output (`setSinkId`). */\nexport function assertOutputSelectionSupported(): void {\n if (typeof HTMLMediaElement === \"undefined\" || !(\"setSinkId\" in HTMLMediaElement.prototype)) {\n throw new FishAgentError(\n \"device_change_failed\",\n \"This browser does not support selecting an audio output device\",\n );\n }\n}\n\n/** Agent audio playback via a detached <audio> element (browser only; inert elsewhere). */\nexport class AudioOutput {\n private element?: HTMLAudioElement;\n private volume = 1;\n\n private ensureElement(): HTMLAudioElement | undefined {\n if (typeof document === \"undefined\") {\n return undefined;\n }\n if (!this.element) {\n this.element = document.createElement(\"audio\");\n this.element.autoplay = true;\n // Same-effect attribute as playsInline for iOS Safari without DOM typings noise.\n this.element.setAttribute(\"playsinline\", \"\");\n this.element.volume = this.volume;\n }\n return this.element;\n }\n\n setStream(stream: MediaStream): void {\n const element = this.ensureElement();\n if (!element) {\n return;\n }\n element.srcObject = stream;\n void element.play().catch(() => {\n // Autoplay policy: playback stays pending until startAudio() runs in a user gesture.\n });\n }\n\n /** Call from a user gesture to satisfy browser autoplay policies. */\n async startAudio(): Promise<void> {\n await this.element?.play();\n }\n\n /**\n * Route playback to an output device. The element is created on demand so\n * the browser validates the device id right here, even before the agent's\n * audio arrives. Rejects with `device_change_failed` where output selection\n * is unsupported or the device cannot be used.\n */\n async setSinkId(sinkId: string): Promise<void> {\n assertOutputSelectionSupported();\n const element = this.ensureElement();\n try {\n await element?.setSinkId(sinkId);\n } catch (error) {\n throw new FishAgentError(\n \"device_change_failed\",\n \"Could not switch to the requested audio output device\",\n { cause: error },\n );\n }\n }\n\n setVolume(volume: number): void {\n this.volume = Math.min(1, Math.max(0, volume));\n if (this.element) {\n this.element.volume = this.volume;\n }\n }\n\n getVolume(): number {\n return this.volume;\n }\n\n dispose(): void {\n if (this.element) {\n this.element.srcObject = null;\n this.element.remove();\n this.element = undefined;\n }\n }\n}\n","/**\n * WebAudio analyser over a MediaStream, feeding visualizers (levels + FFT)\n * without exposing any transport object. Browser only; every accessor\n * degrades to silence when WebAudio is unavailable.\n */\nexport class AudioStreamAnalyser {\n private context?: AudioContext;\n private analyser?: AnalyserNode;\n private source?: MediaStreamAudioSourceNode;\n\n setStream(stream: MediaStream): void {\n if (typeof AudioContext === \"undefined\") {\n return;\n }\n this.context ??= new AudioContext();\n this.analyser ??= this.context.createAnalyser();\n this.source?.disconnect();\n this.source = this.context.createMediaStreamSource(stream);\n this.source.connect(this.analyser);\n this.resume();\n }\n\n /** AudioContexts start suspended under autoplay policies; safe to call any time. */\n resume(): void {\n if (this.context?.state === \"suspended\") {\n void this.context.resume().catch(() => {});\n }\n }\n\n /** RMS level in [0, 1]. */\n getVolume(): number {\n if (!this.analyser) {\n return 0;\n }\n const samples = new Uint8Array(this.analyser.fftSize);\n this.analyser.getByteTimeDomainData(samples);\n let sumOfSquares = 0;\n for (const sample of samples) {\n const centered = (sample - 128) / 128;\n sumOfSquares += centered * centered;\n }\n return Math.sqrt(sumOfSquares / samples.length);\n }\n\n getFrequencyData(): Uint8Array {\n if (!this.analyser) {\n return new Uint8Array(0);\n }\n const data = new Uint8Array(this.analyser.frequencyBinCount);\n this.analyser.getByteFrequencyData(data);\n return data;\n }\n\n dispose(): void {\n this.source?.disconnect();\n this.analyser?.disconnect();\n void this.context?.close().catch(() => {});\n this.source = undefined;\n this.analyser = undefined;\n this.context = undefined;\n }\n}\n","// Type-only: the transport implementation still loads lazily (see start()).\nimport type { Room } from \"livekit-client\";\nimport type {\n AgentSessionMessage,\n ClientSessionMessage,\n SessionToken,\n} from \"@fishaudio/agent-protocol\";\nimport { assertOutputSelectionSupported, AudioOutput } from \"../audio/output.js\";\nimport { AudioStreamAnalyser } from \"../audio/analysis.js\";\nimport { FishAgentError } from \"../errors.js\";\nimport type {\n AgentMode,\n AgentSessionCallbacks,\n AgentSessionEvents,\n EndReason,\n SessionStatus,\n TranscriptSegment,\n} from \"../events.js\";\nimport { resolveSessionToken, type SessionRequestOptions } from \"../sessionToken.js\";\nimport type {\n TranscriptionSegmentUpdate,\n Transport,\n TransportConnectionState,\n TransportFactory,\n} from \"../transport/types.js\";\nimport { TypedEmitter } from \"./emitter.js\";\nimport {\n ClientToolDispatcher,\n DEFAULT_CLIENT_TOOL_TIMEOUT_MS,\n type ClientToolHandler,\n} from \"./toolDispatcher.js\";\nimport { WakeLockHolder } from \"./wakeLock.js\";\n\nexport interface AgentSessionOptions extends SessionRequestOptions {\n clientTools?: Record<string, ClientToolHandler>;\n clientToolTimeoutMs?: number;\n /**\n * Capture the microphone on start. Default true. Set false to join without\n * requesting it (text-first UIs); the first `setMicMuted(false)` then\n * captures — the permission prompt happens there, so call it from a user\n * gesture.\n */\n microphone?: boolean;\n audio?: {\n inputDeviceId?: string;\n /** Playback device (`setSinkId`); start rejects with `device_change_failed`\n * where output selection is unsupported (before creating a server session)\n * or the device cannot be used (before connecting). */\n outputDeviceId?: string;\n };\n /**\n * Hold a screen wake lock while the session is live, so long calls survive\n * the phone trying to sleep. Default true; denial is silent. Set false to\n * leave screen policy to the page.\n */\n wakeLock?: boolean;\n callbacks?: Partial<AgentSessionCallbacks>;\n}\n\n/** How long after connect the agent participant gets to show up before the\n * session gives up — normal joins land within a couple of seconds. */\nconst AGENT_JOIN_TIMEOUT_MS = 15_000;\n\n/** Assigned in AgentSession's static block; bridges startAgentSession to the\n * class-private #startWith so no transport types sit on the public class. */\nlet startWith: (\n options: AgentSessionOptions,\n createTransport: TransportFactory,\n) => Promise<AgentSession>;\n\nexport class AgentSession extends TypedEmitter<AgentSessionEvents> {\n #status: SessionStatus = \"connecting\";\n #mode: AgentMode = \"listening\";\n #endReason?: EndReason;\n #sessionId: string;\n #micMuted = false;\n #endedByClient = false;\n #ending?: Promise<void>;\n\n #transport!: Transport;\n #tools!: ClientToolDispatcher;\n readonly #output = new AudioOutput();\n readonly #outputAnalyser = new AudioStreamAnalyser();\n readonly #inputAnalyser = new AudioStreamAnalyser();\n readonly #wakeLock = new WakeLockHolder();\n\n /** Text already delivered per agent segment, to derive deltas from full-text updates. */\n readonly #agentSegments = new Map<string, string>();\n /** Transcript so far, for late subscribers (getTranscript); upserted per segment. */\n readonly #transcript: TranscriptSegment[] = [];\n readonly #transcriptIndex = new Map<string, number>();\n #typedMessageCount = 0;\n\n // Data frames reach only participants already in the room — and the worker\n // registers its handlers while starting up — so typed messages wait here\n // until the agent publishes its first pipeline state.\n #agentPresent = false;\n #pendingMessages: ClientSessionMessage[] = [];\n #joinTimer?: ReturnType<typeof setTimeout>;\n\n private constructor(sessionToken: SessionToken) {\n super();\n this.#sessionId = sessionToken.session_id;\n }\n\n /**\n * Obtain a session token (if needed), connect, publish the microphone and\n * hand back a live session. Rejects with FishAgentError on session-request,\n * permission or connect failures.\n */\n static async start(options: AgentSessionOptions): Promise<AgentSession> {\n // Lazy so the transport implementation (and livekit-client) never loads\n // in SSR bundles or in tests that inject their own transport.\n const { LiveKitTransport } = await import(\"../transport/livekit.js\");\n return AgentSession.#startWith(options, () => new LiveKitTransport());\n }\n\n // ---- getters ----\n\n get sessionId(): string {\n return this.#sessionId;\n }\n\n get status(): SessionStatus {\n return this.#status;\n }\n\n get mode(): AgentMode {\n return this.#mode;\n }\n\n get isSpeaking(): boolean {\n return this.#mode === \"speaking\";\n }\n\n get micMuted(): boolean {\n return this.#micMuted;\n }\n\n get endReason(): EndReason | undefined {\n return this.#endReason;\n }\n\n /**\n * Snapshot of every transcript segment so far, in conversation order. Seed a\n * consumer that subscribes after events already fired (e.g. a component\n * mounted right after start), then apply live events by segmentId.\n */\n getTranscript(): TranscriptSegment[] {\n return this.#transcript.map((segment) => ({ ...segment }));\n }\n\n // ---- text & control channel ----\n\n /** `audio: false` asks the agent to answer this turn in text only (no TTS). */\n sendUserMessage(text: string, options?: { audio?: boolean }): void {\n const trimmed = text.trim();\n if (!trimmed) {\n return;\n }\n const message: ClientSessionMessage =\n options?.audio === false\n ? { type: \"user.message\", text: trimmed, audio: false }\n : { type: \"user.message\", text: trimmed };\n if (this.#agentPresent) {\n this.#sendClientEvent(message);\n } else {\n this.#pendingMessages.push(message);\n }\n this.#recordTypedUserMessage(trimmed);\n }\n\n sendUserActivity(): void {\n this.#sendClientEvent({ type: \"user.activity\" });\n }\n\n interrupt(): void {\n this.#sendClientEvent({ type: \"user.interrupt\" });\n }\n\n registerClientTool(name: string, handler: ClientToolHandler): void {\n this.#tools.register(name, handler);\n }\n\n // ---- audio ----\n\n /**\n * After a `microphone: false` start, the first unmute captures and publishes\n * the microphone — the permission prompt happens here. A denial rejects with\n * `mic_permission_denied` and the session stays muted.\n */\n async setMicMuted(muted: boolean): Promise<void> {\n const previous = this.#micMuted;\n this.#micMuted = muted;\n try {\n await this.#transport.setMicEnabled(!muted);\n } catch (error) {\n this.#micMuted = previous;\n throw AgentSession.#asMicError(error);\n }\n }\n\n /** Call from a user gesture to satisfy browser autoplay policies. */\n async startAudio(): Promise<void> {\n this.#outputAnalyser.resume();\n this.#inputAnalyser.resume();\n await this.#output.startAudio();\n }\n\n /**\n * Switch the microphone mid-call. While the mic is not captured yet\n * (`microphone: false` start, still muted) this records the preference for\n * the first unmute. Rejects with `device_change_failed` when the device\n * cannot be activated, switching back to the previous microphone (best\n * effort — the transport stops the old capture before acquiring the new).\n */\n async setInputDevice(deviceId: string): Promise<void> {\n try {\n await this.#transport.setInputDevice(deviceId);\n } catch (error) {\n throw AgentSession.#asDeviceError(error, \"Could not switch the microphone\");\n }\n }\n\n /**\n * Route the agent's audio to an output device (`setSinkId`); pass `\"\"` to\n * return to the default device. Rejects with `device_change_failed` where\n * the browser does not support output selection (common on mobile browsers)\n * or the device cannot be used; playback stays on the previous device.\n */\n async setOutputDevice(deviceId: string): Promise<void> {\n await this.#output.setSinkId(deviceId);\n }\n\n setOutputVolume(volume: number): void {\n this.#output.setVolume(volume);\n }\n\n getOutputVolume(): number {\n return this.#outputAnalyser.getVolume();\n }\n\n getInputVolume(): number {\n return this.#inputAnalyser.getVolume();\n }\n\n getOutputFrequencyData(): Uint8Array {\n return this.#outputAnalyser.getFrequencyData();\n }\n\n getInputFrequencyData(): Uint8Array {\n return this.#inputAnalyser.getFrequencyData();\n }\n\n /**\n * Escape hatch: the underlying LiveKit `Room`, for needs the session API\n * doesn't cover (connection-quality telemetry, publishing extra tracks).\n * Code using it couples to this SDK's transport choice and livekit-client\n * version — prefer the session API where one exists. `undefined` after the\n * session ends, or when a custom transport doesn't expose a room.\n */\n getRoom(): Room | undefined {\n return this.#transport.getRoom?.() as Room | undefined;\n }\n\n // ---- lifecycle ----\n\n end(): Promise<void> {\n if (this.#ending) {\n return this.#ending;\n }\n if (this.#status === \"ended\") {\n return Promise.resolve();\n }\n const ending = this.#endOnce();\n this.#ending = ending;\n void ending.catch(() => {\n if (this.#ending === ending) {\n this.#ending = undefined;\n }\n });\n return ending;\n }\n\n async #endOnce(): Promise<void> {\n this.#endedByClient = true;\n try {\n // Graceful hangup over the reverse channel; participant departure is the\n // worker-side fallback if this frame never arrives.\n await this.#transport.sendClientEvent({ type: \"user.hangup\" });\n } catch {\n // best effort\n }\n await this.#transport.disconnect();\n this.#finalize(\"user_hangup\");\n }\n\n // ---- internals ----\n\n static {\n startWith = (options, createTransport) => AgentSession.#startWith(options, createTransport);\n }\n\n static async #startWith(\n options: AgentSessionOptions,\n createTransport: TransportFactory,\n ): Promise<AgentSession> {\n if (options.audio?.outputDeviceId !== undefined) {\n // Cheap sync check first, so an unsupported browser fails before a\n // server session is even created.\n assertOutputSelectionSupported();\n }\n const sessionToken = await resolveSessionToken(options);\n const session = new AgentSession(sessionToken);\n if (options.audio?.outputDeviceId !== undefined) {\n // The browser validates the device id here, before anything connects;\n // rejects like a token failure — no events, no teardown needed yet.\n await session.#output.setSinkId(options.audio.outputDeviceId);\n }\n\n for (const [key, callback] of Object.entries(options.callbacks ?? {})) {\n const event = (key.charAt(2).toLowerCase() +\n key.slice(3)) as keyof AgentSessionEvents;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n session.on(event, callback as any);\n }\n\n const transport = createTransport(sessionToken);\n session.#transport = transport;\n if (options.microphone === false) {\n session.#micMuted = true;\n }\n session.#tools = new ClientToolDispatcher(\n options.clientTools,\n options.clientToolTimeoutMs ?? DEFAULT_CLIENT_TOOL_TIMEOUT_MS,\n (message) => transport.sendClientEvent(message),\n (error) => session.emit(\"error\", error),\n );\n\n try {\n await transport.connect(sessionToken, {\n inputDeviceId: options.audio?.inputDeviceId,\n microphone: options.microphone,\n callbacks: {\n onAgentEvent: (message) => session.#handleAgentEvent(message),\n // Mode model: \"listening\" is the default state; the framework's agent\n // state supplies the thinking edge; speaking follows the playout-synced\n // transcription segments (open = audible, closed = back to listening).\n onAgentState: (state) => {\n // Any published agent state proves the worker's pipeline is up and\n // its event handlers are registered — the gate for queued messages.\n session.#handleAgentPresent();\n if (state === \"thinking\") {\n session.#setMode(\"thinking\");\n }\n },\n onTranscription: (update) => session.#handleTranscription(update),\n onOutputStream: (stream) => {\n session.#output.setStream(stream);\n session.#outputAnalyser.setStream(stream);\n },\n onInputStream: (stream) => session.#inputAnalyser.setStream(stream),\n onConnectionState: (state, reason) => session.#handleConnectionState(state, reason),\n },\n });\n } catch (error) {\n // connect() can fail after the room is already joined (e.g. mic capture);\n // tear the transport down or the room stays connected with no owner.\n await session.#transport.disconnect().catch(() => undefined);\n session.#finalize(\"connection_lost\");\n throw AgentSession.#asStartError(error);\n }\n\n if (session.#status === \"connecting\") {\n session.#setStatus(\"connected\");\n session.emit(\"connect\", { sessionId: session.#sessionId });\n }\n if (session.#status !== \"ended\" && options.wakeLock !== false) {\n session.#wakeLock.acquire();\n }\n if (!session.#agentPresent && session.#status === \"connected\") {\n session.#joinTimer = setTimeout(() => session.#abandonAgentWait(), AGENT_JOIN_TIMEOUT_MS);\n }\n return session;\n }\n\n static #asStartError(error: unknown): FishAgentError {\n if (error instanceof FishAgentError) {\n return error;\n }\n if (error instanceof Error && error.name === \"NotAllowedError\") {\n return new FishAgentError(\"mic_permission_denied\", \"Microphone permission was denied\", {\n cause: error,\n });\n }\n return new FishAgentError(\"connection_failed\", \"Could not establish the realtime session\", {\n cause: error,\n });\n }\n\n static #asMicError(error: unknown): FishAgentError {\n if (error instanceof FishAgentError) {\n return error;\n }\n if (error instanceof Error && error.name === \"NotAllowedError\") {\n return new FishAgentError(\"mic_permission_denied\", \"Microphone permission was denied\", {\n cause: error,\n });\n }\n if (\n error instanceof Error &&\n (error.name === \"OverconstrainedError\" || error.name === \"NotFoundError\")\n ) {\n // Device/constraint failures surface here both for a preference recorded\n // while muted and for a first capture with no usable microphone — keep\n // the device error code, with a message that fits either.\n return new FishAgentError(\n \"device_change_failed\",\n \"No usable microphone could be activated\",\n { cause: error },\n );\n }\n return new FishAgentError(\"connection_failed\", \"Could not toggle the microphone\", {\n cause: error,\n });\n }\n\n static #asDeviceError(error: unknown, message: string): FishAgentError {\n if (error instanceof FishAgentError) {\n return error;\n }\n return new FishAgentError(\"device_change_failed\", message, { cause: error });\n }\n\n #sendClientEvent(message: Parameters<Transport[\"sendClientEvent\"]>[0]): void {\n void this.#transport.sendClientEvent(message).catch((error: unknown) => {\n this.emit(\n \"error\",\n new FishAgentError(\"connection_failed\", \"Failed to send to the agent\", { cause: error }),\n );\n });\n }\n\n #setStatus(status: SessionStatus): void {\n if (this.#status !== status) {\n this.#status = status;\n this.emit(\"statusChange\", status);\n }\n }\n\n #setMode(mode: AgentMode): void {\n if (this.#mode !== mode) {\n this.#mode = mode;\n this.emit(\"modeChange\", mode);\n }\n }\n\n #handleAgentPresent(): void {\n if (this.#agentPresent) {\n return;\n }\n this.#agentPresent = true;\n if (this.#joinTimer) {\n clearTimeout(this.#joinTimer);\n this.#joinTimer = undefined;\n }\n const pending = this.#pendingMessages;\n this.#pendingMessages = [];\n for (const message of pending) {\n this.#sendClientEvent(message);\n }\n }\n\n #abandonAgentWait(): void {\n if (this.#status === \"ended\" || this.#agentPresent) {\n return;\n }\n this.emit(\n \"error\",\n new FishAgentError(\"connection_failed\", \"The agent did not join the session\"),\n );\n void this.#transport.disconnect().catch(() => {});\n this.#finalize(\"connection_lost\");\n }\n\n #handleConnectionState(state: TransportConnectionState, reason?: string): void {\n if (this.#status === \"ended\") {\n return;\n }\n if (state === \"reconnecting\") {\n this.#setStatus(\"reconnecting\");\n return;\n }\n if (state === \"connected\") {\n if (this.#status === \"reconnecting\") {\n this.#setStatus(\"connected\");\n }\n return;\n }\n // AGENT_LEFT = the agent participant hung up (worker ends without closing\n // the room). Room deletion covers server-forced ends and the duration\n // cap — the protocol has no session.ended message to tell these apart.\n const ended = this.#endedByClient\n ? \"user_hangup\"\n : reason === \"AGENT_LEFT\" || reason === \"ROOM_DELETED\" || reason === \"ROOM_CLOSED\"\n ? \"agent_hangup\"\n : \"connection_lost\";\n this.#finalize(ended);\n }\n\n #finalize(reason: EndReason): void {\n if (this.#status === \"ended\") {\n return;\n }\n if (this.#joinTimer) {\n clearTimeout(this.#joinTimer);\n this.#joinTimer = undefined;\n }\n this.#endReason = reason;\n this.#setStatus(\"ended\");\n this.#wakeLock.release();\n this.#output.dispose();\n this.#outputAnalyser.dispose();\n this.#inputAnalyser.dispose();\n this.emit(\"disconnect\", { reason });\n }\n\n #handleAgentEvent(message: AgentSessionMessage): void {\n switch (message.type) {\n case \"client_tool.call\":\n void this.#tools.dispatch(message);\n return;\n case \"tool.started\":\n this.emit(\"toolCallStarted\", {\n callId: message.callId,\n toolName: message.toolName,\n source: message.toolSource,\n input: message.input,\n inputTruncated: message.inputTruncated === true,\n });\n return;\n case \"tool.completed\":\n this.emit(\"toolCallCompleted\", {\n callId: message.callId,\n toolName: message.toolName,\n source: message.toolSource,\n output: message.output,\n outputTruncated: message.outputTruncated === true,\n });\n return;\n case \"tool.failed\":\n this.emit(\"toolCallFailed\", {\n callId: message.callId,\n toolName: message.toolName,\n source: message.toolSource,\n error: message.error,\n });\n return;\n case \"error\":\n this.emit(\n \"error\",\n message.code === \"provider_error\"\n ? new FishAgentError(\"provider_error\", \"An upstream provider failed during the session\")\n : new FishAgentError(\"internal_error\", \"The agent runtime hit an internal error\"),\n );\n return;\n default:\n // Forward compatibility: unknown message types are ignored by contract.\n return;\n }\n }\n\n #recordSegment(role: \"user\" | \"agent\", segmentId: string, text: string, final: boolean): void {\n const key = `${role}:${segmentId}`;\n const index = this.#transcriptIndex.get(key);\n if (index === undefined) {\n this.#transcriptIndex.set(key, this.#transcript.length);\n this.#transcript.push({ segmentId, role, text, final });\n } else {\n this.#transcript[index] = { segmentId, role, text, final };\n }\n }\n\n #handleTranscription(update: TranscriptionSegmentUpdate): void {\n if (update.role === \"user\") {\n this.#recordSegment(\"user\", update.segmentId, update.text, update.final);\n this.emit(\"userTranscript\", {\n segmentId: update.segmentId,\n text: update.text,\n final: update.final,\n });\n if (update.final) {\n this.emit(\"message\", { role: \"user\", text: update.text });\n }\n return;\n }\n // Agent speech: playout-synced, so streaming text means the agent is audibly\n // speaking; the segment closing means playout finished — back to listening.\n this.#setMode(update.final ? \"listening\" : \"speaking\");\n this.#recordSegment(\"agent\", update.segmentId, update.text, update.final);\n const previous = this.#agentSegments.get(update.segmentId) ?? \"\";\n const delta = update.text.startsWith(previous) ? update.text.slice(previous.length) : update.text;\n this.#agentSegments.set(update.segmentId, update.text);\n if (delta) {\n this.emit(\"agentResponseDelta\", { segmentId: update.segmentId, delta, text: update.text });\n }\n if (update.final) {\n this.#agentSegments.delete(update.segmentId);\n this.emit(\"agentResponse\", { segmentId: update.segmentId, text: update.text });\n this.emit(\"message\", { role: \"agent\", text: update.text });\n }\n }\n\n // Typed messages are not echoed by the server; the sender finalizes its own bubble.\n #recordTypedUserMessage(text: string): void {\n const segmentId = `typed_${++this.#typedMessageCount}`;\n this.#recordSegment(\"user\", segmentId, text, true);\n this.emit(\"userTranscript\", { segmentId, text, final: true });\n this.emit(\"message\", { role: \"user\", text });\n }\n}\n\n/** Internal seam: identical to AgentSession.start but with an injectable transport. */\nexport function startAgentSession(\n options: AgentSessionOptions,\n createTransport: TransportFactory,\n): Promise<AgentSession> {\n return startWith(options, createTransport);\n}\n","import {\n SESSION_TRANSPORT_LIVEKIT,\n type AgentSessionCreateRequest,\n type SessionLanguage,\n type SessionOverrides,\n type SessionToken,\n} from \"@fishaudio/agent-protocol\";\nimport { FishAgentError } from \"./errors.js\";\n\nexport const SDK_VERSION = \"0.0.1\";\nexport const DEFAULT_SERVER_URL = \"https://api.fish.audio\";\n\nconst SUPPORTED_TRANSPORTS: string[] = [SESSION_TRANSPORT_LIVEKIT];\n\nexport interface SessionRequestOptions {\n /** Public agent: the SDK creates the session directly against the Fish API. */\n agentId?: string;\n /** Session token created by the host backend, passed through verbatim. */\n sessionToken?: SessionToken;\n serverUrl?: string;\n /** Sugar for `overrides.language`; pair with a voice in that language. */\n language?: SessionLanguage;\n /**\n * IANA timezone for the agent's sense of local time in this session. Omit to\n * use the browser's timezone (sent automatically as a hint). With\n * `sessionToken` auth the host backend chooses instead, via `timezone` on its\n * session-creation call.\n */\n timezone?: string;\n /**\n * The agent knows the current date and time by default. Set false to withhold\n * both from this session's prompt. With `sessionToken` auth the host backend\n * chooses instead, via `world_context` on its session-creation call.\n */\n worldContext?: boolean;\n /** Per-session config overrides; each field must be allow-listed on the agent. */\n overrides?: SessionOverrides;\n /** `{{name}}` template values for the agent's prompt and first message. */\n dynamicVariables?: Record<string, string | number | boolean>;\n endUserId?: string;\n /** Caller-owned tag, stored and returned verbatim; the platform never interprets it. */\n metadata?: Record<string, unknown>;\n /**\n * Stream the agent's tool-call lifecycle (toolCallStarted/Completed/Failed\n * events, with payloads) to this session. Default true; set false when tool\n * data must stay hidden from this client. With `sessionToken` auth the host\n * backend chooses instead, via `tool_events` on its session-creation call.\n */\n toolEvents?: boolean;\n}\n\nexport function validateSessionToken(sessionToken: SessionToken): SessionToken {\n const transport: string = sessionToken.transport;\n if (!SUPPORTED_TRANSPORTS.includes(transport)) {\n throw new FishAgentError(\n \"unsupported_transport\",\n `Session token uses transport \"${transport}\" which this SDK version does not support; ` +\n \"please upgrade @fishaudio/agent-client\",\n );\n }\n const expiresAt = Date.parse(sessionToken.expires_at);\n if (!Number.isNaN(expiresAt) && expiresAt <= Date.now()) {\n throw new FishAgentError(\n \"session_expired\",\n \"The session token's join deadline has passed; request a new session\",\n );\n }\n return sessionToken;\n}\n\nexport async function resolveSessionToken(options: SessionRequestOptions): Promise<SessionToken> {\n const provided = [options.agentId, options.sessionToken].filter((value) => value !== undefined);\n if (provided.length !== 1) {\n throw new TypeError(\n \"Exactly one of `agentId` or `sessionToken` must be provided to start a session\",\n );\n }\n\n if (options.sessionToken) {\n return validateSessionToken(options.sessionToken);\n }\n return validateSessionToken(await createPublicSession(options));\n}\n\n// The browser knows the end user's real timezone; without the hint the\n// platform would have to guess UTC.\nfunction detectClientTimezone(): string | undefined {\n try {\n return Intl.DateTimeFormat().resolvedOptions().timeZone || undefined;\n } catch {\n return undefined;\n }\n}\n\nasync function createPublicSession(options: SessionRequestOptions): Promise<SessionToken> {\n const serverUrl = (options.serverUrl ?? DEFAULT_SERVER_URL).replace(/\\/$/, \"\");\n const clientTimezone = detectClientTimezone();\n const request: AgentSessionCreateRequest = {\n agent_id: options.agentId as string,\n ...(options.language ? { language: options.language } : {}),\n ...(options.timezone ? { timezone: options.timezone } : {}),\n ...(clientTimezone ? { client_timezone: clientTimezone } : {}),\n ...(options.worldContext !== undefined ? { world_context: options.worldContext } : {}),\n ...(options.overrides ? { overrides: options.overrides } : {}),\n ...(options.dynamicVariables ? { dynamic_variables: options.dynamicVariables } : {}),\n ...(options.endUserId ? { end_user_id: options.endUserId } : {}),\n ...(options.metadata ? { metadata: options.metadata } : {}),\n ...(options.toolEvents !== undefined ? { tool_events: options.toolEvents } : {}),\n };\n\n let response: Response;\n try {\n response = await fetch(`${serverUrl}/v1/agent/sessions`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Fish-SDK\": `agent-client/${SDK_VERSION}`,\n },\n body: JSON.stringify(request),\n });\n } catch (cause) {\n throw new FishAgentError(\"session_request_failed\", \"Could not reach the session endpoint\", {\n cause,\n });\n }\n\n if (!response.ok) {\n throw await createSessionError(response);\n }\n return (await response.json()) as SessionToken;\n}\n\nasync function createSessionError(response: Response): Promise<FishAgentError> {\n let message = `Session request failed with HTTP ${response.status}`;\n let code: string | undefined;\n try {\n const body = (await response.json()) as { message?: string; error?: string; code?: string };\n message = body.message ?? body.error ?? message;\n code = body.code;\n } catch {\n // keep the status-based message\n }\n const options = { statusCode: response.status };\n // 409 alone is ambiguous (the server also uses it for e.g. an unpublished\n // agent); only the coded arm means \"this SDK is too old\".\n if (response.status === 409 && code === \"unsupported_transport\") {\n return new FishAgentError(\n \"unsupported_transport\",\n `${message}; please upgrade @fishaudio/agent-client`,\n options,\n );\n }\n if (response.status === 403) {\n return /origin/i.test(message)\n ? new FishAgentError(\"origin_forbidden\", message, options)\n : new FishAgentError(\"agent_not_public\", message, options);\n }\n return new FishAgentError(\"session_request_failed\", message, options);\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\ntype Listener = (...args: any[]) => void;\n\n/** Minimal typed emitter; listener errors are isolated so one bad consumer can't break the session. */\nexport class TypedEmitter<Events extends Record<keyof Events, Listener>> {\n private readonly listeners = new Map<keyof Events, Set<Listener>>();\n\n on<K extends keyof Events>(event: K, listener: Events[K]): this {\n let set = this.listeners.get(event);\n if (!set) {\n set = new Set();\n this.listeners.set(event, set);\n }\n set.add(listener);\n return this;\n }\n\n off<K extends keyof Events>(event: K, listener: Events[K]): this {\n this.listeners.get(event)?.delete(listener);\n return this;\n }\n\n once<K extends keyof Events>(event: K, listener: Events[K]): this {\n const wrapper = ((...args: Parameters<Events[K]>) => {\n this.off(event, wrapper);\n listener(...args);\n }) as Events[K];\n return this.on(event, wrapper);\n }\n\n protected emit<K extends keyof Events>(event: K, ...args: Parameters<Events[K]>): void {\n const set = this.listeners.get(event);\n if (!set) {\n return;\n }\n for (const listener of [...set]) {\n try {\n listener(...args);\n } catch (error) {\n // eslint-disable-next-line no-console\n console.error(\"[fish-agent] listener error\", error);\n }\n }\n }\n}\n","import type { AgentSessionMessage, ClientSessionMessage } from \"@fishaudio/agent-protocol\";\nimport { FishAgentError } from \"../errors.js\";\n\nexport type ClientToolHandler = (\n params: Record<string, unknown>,\n context: { callId: string; toolName: string },\n) => unknown | Promise<unknown>;\n\nexport const DEFAULT_CLIENT_TOOL_TIMEOUT_MS = 15_000;\n\ntype ClientToolCall = Extract<AgentSessionMessage, { type: \"client_tool.call\" }>;\n\nexport class ClientToolDispatcher {\n private readonly tools = new Map<string, ClientToolHandler>();\n\n constructor(\n tools: Record<string, ClientToolHandler> | undefined,\n private readonly timeoutMs: number,\n private readonly send: (message: ClientSessionMessage) => Promise<void>,\n private readonly onError: (error: FishAgentError) => void,\n ) {\n for (const [name, handler] of Object.entries(tools ?? {})) {\n this.tools.set(name, handler);\n }\n }\n\n register(name: string, handler: ClientToolHandler): void {\n this.tools.set(name, handler);\n }\n\n async dispatch(call: ClientToolCall): Promise<void> {\n const handler = this.tools.get(call.toolName);\n if (!handler) {\n this.onError(\n new FishAgentError(\"tool_failed\", `Client tool \"${call.toolName}\" is not registered`),\n );\n if (call.expectsResponse) {\n await this.reply(call.callId, { isError: true, result: \"Tool is not registered\" });\n }\n return;\n }\n\n try {\n const result = await this.withTimeout(\n Promise.resolve(handler(call.params, { callId: call.callId, toolName: call.toolName })),\n call.toolName,\n );\n if (call.expectsResponse) {\n await this.reply(call.callId, result === undefined ? {} : { result });\n }\n } catch (error) {\n this.onError(\n error instanceof FishAgentError\n ? error\n : new FishAgentError(\"tool_failed\", `Client tool \"${call.toolName}\" threw`, {\n cause: error,\n }),\n );\n if (call.expectsResponse) {\n await this.reply(call.callId, { isError: true, result: String(error) });\n }\n }\n }\n\n private async reply(\n callId: string,\n body: { result?: unknown; isError?: boolean },\n ): Promise<void> {\n try {\n await this.send({ type: \"client_tool.result\", callId, ...body });\n } catch (error) {\n this.onError(\n new FishAgentError(\"tool_failed\", \"Failed to deliver a client tool result\", {\n cause: error,\n }),\n );\n }\n }\n\n private withTimeout<T>(promise: Promise<T>, toolName: string): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(\n new FishAgentError(\n \"tool_timeout\",\n `Client tool \"${toolName}\" timed out after ${this.timeoutMs}ms`,\n ),\n );\n }, this.timeoutMs);\n promise.then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (error: unknown) => {\n clearTimeout(timer);\n reject(error instanceof Error ? error : new Error(String(error)));\n },\n );\n });\n }\n}\n","/**\n * Holds a screen wake lock while a session is live (browser only; inert\n * elsewhere). The platform releases the lock whenever the page is hidden, so\n * visibility returns re-request it. Failures are silent by design: a session\n * works fine without the lock, the screen just may sleep mid-call.\n */\nexport class WakeLockHolder {\n #sentinel?: WakeLockSentinel;\n #active = false;\n\n readonly #onVisibilityChange = (): void => {\n if (document.visibilityState === \"visible\") {\n void this.#request();\n }\n };\n\n acquire(): void {\n if (\n this.#active ||\n typeof document === \"undefined\" ||\n typeof navigator === \"undefined\" ||\n !navigator.wakeLock\n ) {\n return;\n }\n this.#active = true;\n document.addEventListener(\"visibilitychange\", this.#onVisibilityChange);\n void this.#request();\n }\n\n async #request(): Promise<void> {\n if (!this.#active) {\n return;\n }\n try {\n const sentinel = await navigator.wakeLock.request(\"screen\");\n if (!this.#active) {\n void sentinel.release().catch(() => undefined);\n return;\n }\n // Concurrent requests (acquire + visibility return) may both resolve;\n // drop the earlier sentinel so exactly one is held.\n const previous = this.#sentinel;\n this.#sentinel = sentinel;\n if (previous && previous !== sentinel) {\n void previous.release().catch(() => undefined);\n }\n } catch {\n // Denied (battery saver, hidden tab, permissions policy) — run without it.\n }\n }\n\n release(): void {\n if (!this.#active) {\n return;\n }\n this.#active = false;\n document.removeEventListener(\"visibilitychange\", this.#onVisibilityChange);\n void this.#sentinel?.release().catch(() => undefined);\n this.#sentinel = undefined;\n }\n}\n"],"mappings":"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","names":["import_agent_protocol","LiveKitTransport","_a","_b"]}
@@ -0,0 +1,251 @@
1
+ import { Room } from 'livekit-client';
2
+ import { ToolCallSource, SessionToken, SessionLanguage, SessionOverrides } from '@fishaudio/agent-protocol';
3
+ export { AgentSessionCreateRequest, SessionOverrides, SessionToken, ToolCallSource } from '@fishaudio/agent-protocol';
4
+
5
+ type FishAgentErrorCode = "session_request_failed" | "agent_not_public" | "origin_forbidden" | "unsupported_transport" | "mic_permission_denied"
6
+ /** A requested audio device could not be activated, or the browser does not support selecting it. */
7
+ | "device_change_failed" | "connection_failed" | "session_expired" | "tool_failed" | "tool_timeout"
8
+ /** An upstream model/speech provider failed during the session. */
9
+ | "provider_error"
10
+ /** The agent runtime hit an internal error. */
11
+ | "internal_error";
12
+ declare class FishAgentError extends Error {
13
+ readonly code: FishAgentErrorCode;
14
+ /** HTTP status of the failing session request, when the error originated there. */
15
+ readonly statusCode?: number;
16
+ constructor(code: FishAgentErrorCode, message: string, options?: {
17
+ statusCode?: number;
18
+ cause?: unknown;
19
+ });
20
+ }
21
+
22
+ type SessionStatus = "connecting" | "connected" | "reconnecting" | "ended";
23
+ type AgentMode = "listening" | "thinking" | "speaking";
24
+ type EndReason = "user_hangup" | "agent_hangup" | "connection_lost";
25
+ interface UserTranscriptEvent {
26
+ /** Groups updates of one utterance; interim updates replace, they never append. */
27
+ segmentId: string;
28
+ /** Full text of the utterance so far. */
29
+ text: string;
30
+ final: boolean;
31
+ }
32
+ interface AgentResponseDeltaEvent {
33
+ /** Groups streaming updates of one agent speech segment. */
34
+ segmentId: string;
35
+ delta: string;
36
+ /** Accumulated segment text including this delta. */
37
+ text: string;
38
+ }
39
+ interface AgentResponseEvent {
40
+ segmentId: string;
41
+ text: string;
42
+ }
43
+ /** One transcript segment; `text` is the whole segment so far, refined in place. */
44
+ interface TranscriptSegment {
45
+ segmentId: string;
46
+ role: "user" | "agent";
47
+ text: string;
48
+ final: boolean;
49
+ }
50
+ interface ConversationMessage {
51
+ role: "user" | "agent";
52
+ text: string;
53
+ }
54
+ /**
55
+ * Tool-call lifecycle, forwarded for every tool the agent runs (including client
56
+ * tools, which complete once this client returns their result). `callId` pairs one
57
+ * started event with exactly one completed/failed event; terminal events repeat
58
+ * name/source so a listener attached mid-call can still render a full item.
59
+ * Emission is a session-creation option (`toolEvents`/`tool_events`, default true).
60
+ */
61
+ interface ToolCallStartedEvent {
62
+ callId: string;
63
+ toolName: string;
64
+ source: ToolCallSource;
65
+ /** Tool arguments as a JSON string; parse only when `inputTruncated` is false. */
66
+ input: string;
67
+ inputTruncated: boolean;
68
+ }
69
+ interface ToolCallCompletedEvent {
70
+ callId: string;
71
+ toolName: string;
72
+ source: ToolCallSource;
73
+ /** Tool result as a JSON string; parse only when `outputTruncated` is false. */
74
+ output: string;
75
+ outputTruncated: boolean;
76
+ }
77
+ interface ToolCallFailedEvent {
78
+ callId: string;
79
+ toolName: string;
80
+ source: ToolCallSource;
81
+ error: string;
82
+ }
83
+ interface AgentSessionEvents {
84
+ connect: (event: {
85
+ sessionId: string;
86
+ }) => void;
87
+ disconnect: (event: {
88
+ reason: EndReason;
89
+ }) => void;
90
+ statusChange: (status: SessionStatus) => void;
91
+ modeChange: (mode: AgentMode) => void;
92
+ userTranscript: (event: UserTranscriptEvent) => void;
93
+ agentResponseDelta: (event: AgentResponseDeltaEvent) => void;
94
+ agentResponse: (event: AgentResponseEvent) => void;
95
+ /** Finalized messages only, in conversation order — the "just give me a chat log" event. */
96
+ message: (message: ConversationMessage) => void;
97
+ toolCallStarted: (event: ToolCallStartedEvent) => void;
98
+ toolCallCompleted: (event: ToolCallCompletedEvent) => void;
99
+ toolCallFailed: (event: ToolCallFailedEvent) => void;
100
+ error: (error: FishAgentError) => void;
101
+ }
102
+ type AgentSessionCallbacks = {
103
+ [K in keyof AgentSessionEvents as `on${Capitalize<K>}`]: AgentSessionEvents[K];
104
+ };
105
+
106
+ declare const DEFAULT_SERVER_URL = "https://api.fish.audio";
107
+ interface SessionRequestOptions {
108
+ /** Public agent: the SDK creates the session directly against the Fish API. */
109
+ agentId?: string;
110
+ /** Session token created by the host backend, passed through verbatim. */
111
+ sessionToken?: SessionToken;
112
+ serverUrl?: string;
113
+ /** Sugar for `overrides.language`; pair with a voice in that language. */
114
+ language?: SessionLanguage;
115
+ /**
116
+ * IANA timezone for the agent's sense of local time in this session. Omit to
117
+ * use the browser's timezone (sent automatically as a hint). With
118
+ * `sessionToken` auth the host backend chooses instead, via `timezone` on its
119
+ * session-creation call.
120
+ */
121
+ timezone?: string;
122
+ /**
123
+ * The agent knows the current date and time by default. Set false to withhold
124
+ * both from this session's prompt. With `sessionToken` auth the host backend
125
+ * chooses instead, via `world_context` on its session-creation call.
126
+ */
127
+ worldContext?: boolean;
128
+ /** Per-session config overrides; each field must be allow-listed on the agent. */
129
+ overrides?: SessionOverrides;
130
+ /** `{{name}}` template values for the agent's prompt and first message. */
131
+ dynamicVariables?: Record<string, string | number | boolean>;
132
+ endUserId?: string;
133
+ /** Caller-owned tag, stored and returned verbatim; the platform never interprets it. */
134
+ metadata?: Record<string, unknown>;
135
+ /**
136
+ * Stream the agent's tool-call lifecycle (toolCallStarted/Completed/Failed
137
+ * events, with payloads) to this session. Default true; set false when tool
138
+ * data must stay hidden from this client. With `sessionToken` auth the host
139
+ * backend chooses instead, via `tool_events` on its session-creation call.
140
+ */
141
+ toolEvents?: boolean;
142
+ }
143
+
144
+ type Listener = (...args: any[]) => void;
145
+ /** Minimal typed emitter; listener errors are isolated so one bad consumer can't break the session. */
146
+ declare class TypedEmitter<Events extends Record<keyof Events, Listener>> {
147
+ private readonly listeners;
148
+ on<K extends keyof Events>(event: K, listener: Events[K]): this;
149
+ off<K extends keyof Events>(event: K, listener: Events[K]): this;
150
+ once<K extends keyof Events>(event: K, listener: Events[K]): this;
151
+ protected emit<K extends keyof Events>(event: K, ...args: Parameters<Events[K]>): void;
152
+ }
153
+
154
+ type ClientToolHandler = (params: Record<string, unknown>, context: {
155
+ callId: string;
156
+ toolName: string;
157
+ }) => unknown | Promise<unknown>;
158
+
159
+ interface AgentSessionOptions extends SessionRequestOptions {
160
+ clientTools?: Record<string, ClientToolHandler>;
161
+ clientToolTimeoutMs?: number;
162
+ /**
163
+ * Capture the microphone on start. Default true. Set false to join without
164
+ * requesting it (text-first UIs); the first `setMicMuted(false)` then
165
+ * captures — the permission prompt happens there, so call it from a user
166
+ * gesture.
167
+ */
168
+ microphone?: boolean;
169
+ audio?: {
170
+ inputDeviceId?: string;
171
+ /** Playback device (`setSinkId`); start rejects with `device_change_failed`
172
+ * where output selection is unsupported (before creating a server session)
173
+ * or the device cannot be used (before connecting). */
174
+ outputDeviceId?: string;
175
+ };
176
+ /**
177
+ * Hold a screen wake lock while the session is live, so long calls survive
178
+ * the phone trying to sleep. Default true; denial is silent. Set false to
179
+ * leave screen policy to the page.
180
+ */
181
+ wakeLock?: boolean;
182
+ callbacks?: Partial<AgentSessionCallbacks>;
183
+ }
184
+ declare class AgentSession extends TypedEmitter<AgentSessionEvents> {
185
+ #private;
186
+ private constructor();
187
+ /**
188
+ * Obtain a session token (if needed), connect, publish the microphone and
189
+ * hand back a live session. Rejects with FishAgentError on session-request,
190
+ * permission or connect failures.
191
+ */
192
+ static start(options: AgentSessionOptions): Promise<AgentSession>;
193
+ get sessionId(): string;
194
+ get status(): SessionStatus;
195
+ get mode(): AgentMode;
196
+ get isSpeaking(): boolean;
197
+ get micMuted(): boolean;
198
+ get endReason(): EndReason | undefined;
199
+ /**
200
+ * Snapshot of every transcript segment so far, in conversation order. Seed a
201
+ * consumer that subscribes after events already fired (e.g. a component
202
+ * mounted right after start), then apply live events by segmentId.
203
+ */
204
+ getTranscript(): TranscriptSegment[];
205
+ /** `audio: false` asks the agent to answer this turn in text only (no TTS). */
206
+ sendUserMessage(text: string, options?: {
207
+ audio?: boolean;
208
+ }): void;
209
+ sendUserActivity(): void;
210
+ interrupt(): void;
211
+ registerClientTool(name: string, handler: ClientToolHandler): void;
212
+ /**
213
+ * After a `microphone: false` start, the first unmute captures and publishes
214
+ * the microphone — the permission prompt happens here. A denial rejects with
215
+ * `mic_permission_denied` and the session stays muted.
216
+ */
217
+ setMicMuted(muted: boolean): Promise<void>;
218
+ /** Call from a user gesture to satisfy browser autoplay policies. */
219
+ startAudio(): Promise<void>;
220
+ /**
221
+ * Switch the microphone mid-call. While the mic is not captured yet
222
+ * (`microphone: false` start, still muted) this records the preference for
223
+ * the first unmute. Rejects with `device_change_failed` when the device
224
+ * cannot be activated, switching back to the previous microphone (best
225
+ * effort — the transport stops the old capture before acquiring the new).
226
+ */
227
+ setInputDevice(deviceId: string): Promise<void>;
228
+ /**
229
+ * Route the agent's audio to an output device (`setSinkId`); pass `""` to
230
+ * return to the default device. Rejects with `device_change_failed` where
231
+ * the browser does not support output selection (common on mobile browsers)
232
+ * or the device cannot be used; playback stays on the previous device.
233
+ */
234
+ setOutputDevice(deviceId: string): Promise<void>;
235
+ setOutputVolume(volume: number): void;
236
+ getOutputVolume(): number;
237
+ getInputVolume(): number;
238
+ getOutputFrequencyData(): Uint8Array;
239
+ getInputFrequencyData(): Uint8Array;
240
+ /**
241
+ * Escape hatch: the underlying LiveKit `Room`, for needs the session API
242
+ * doesn't cover (connection-quality telemetry, publishing extra tracks).
243
+ * Code using it couples to this SDK's transport choice and livekit-client
244
+ * version — prefer the session API where one exists. `undefined` after the
245
+ * session ends, or when a custom transport doesn't expose a room.
246
+ */
247
+ getRoom(): Room | undefined;
248
+ end(): Promise<void>;
249
+ }
250
+
251
+ export { type AgentMode, type AgentResponseDeltaEvent, type AgentResponseEvent, AgentSession, type AgentSessionCallbacks, type AgentSessionEvents, type AgentSessionOptions, type ClientToolHandler, type ConversationMessage, DEFAULT_SERVER_URL, type EndReason, FishAgentError, type FishAgentErrorCode, type SessionStatus, type ToolCallCompletedEvent, type ToolCallFailedEvent, type ToolCallStartedEvent, type TranscriptSegment, type UserTranscriptEvent };