@fishaudio/agent-client 0.0.1 → 0.1.0

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.
package/dist/index.cjs CHANGED
@@ -444,13 +444,13 @@ function detectClientTimezone() {
444
444
  async function createPublicSession(options) {
445
445
  const serverUrl = (options.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/$/, "");
446
446
  const clientTimezone = detectClientTimezone();
447
+ const overrides = options.language || options.overrides ? { ...options.language ? { language: options.language } : {}, ...options.overrides } : void 0;
447
448
  const request = {
448
449
  agent_id: options.agentId,
449
- ...options.language ? { language: options.language } : {},
450
450
  ...options.timezone ? { timezone: options.timezone } : {},
451
451
  ...clientTimezone ? { client_timezone: clientTimezone } : {},
452
452
  ...options.worldContext !== void 0 ? { world_context: options.worldContext } : {},
453
- ...options.overrides ? { overrides: options.overrides } : {},
453
+ ...overrides ? { overrides } : {},
454
454
  ...options.dynamicVariables ? { dynamic_variables: options.dynamicVariables } : {},
455
455
  ...options.endUserId ? { end_user_id: options.endUserId } : {},
456
456
  ...options.metadata ? { metadata: options.metadata } : {},
@@ -1 +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"]}
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`; an explicit override wins. 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 overrides: SessionOverrides | undefined =\n options.language || options.overrides\n ? { ...(options.language ? { language: options.language } : {}), ...options.overrides }\n : undefined;\n const request: AgentSessionCreateRequest = {\n agent_id: options.agentId as string,\n ...(options.timezone ? { timezone: options.timezone } : {}),\n ...(clientTimezone ? { client_timezone: clientTimezone } : {}),\n ...(options.worldContext !== undefined ? { world_context: options.worldContext } : {}),\n ...(overrides ? { 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"]}
package/dist/index.d.cts CHANGED
@@ -110,7 +110,7 @@ interface SessionRequestOptions {
110
110
  /** Session token created by the host backend, passed through verbatim. */
111
111
  sessionToken?: SessionToken;
112
112
  serverUrl?: string;
113
- /** Sugar for `overrides.language`; pair with a voice in that language. */
113
+ /** Sugar for `overrides.language`; an explicit override wins. Pair with a voice in that language. */
114
114
  language?: SessionLanguage;
115
115
  /**
116
116
  * IANA timezone for the agent's sense of local time in this session. Omit to
package/dist/index.d.ts CHANGED
@@ -110,7 +110,7 @@ interface SessionRequestOptions {
110
110
  /** Session token created by the host backend, passed through verbatim. */
111
111
  sessionToken?: SessionToken;
112
112
  serverUrl?: string;
113
- /** Sugar for `overrides.language`; pair with a voice in that language. */
113
+ /** Sugar for `overrides.language`; an explicit override wins. Pair with a voice in that language. */
114
114
  language?: SessionLanguage;
115
115
  /**
116
116
  * IANA timezone for the agent's sense of local time in this session. Omit to
package/dist/index.js CHANGED
@@ -181,13 +181,13 @@ function detectClientTimezone() {
181
181
  async function createPublicSession(options) {
182
182
  const serverUrl = (options.serverUrl ?? DEFAULT_SERVER_URL).replace(/\/$/, "");
183
183
  const clientTimezone = detectClientTimezone();
184
+ const overrides = options.language || options.overrides ? { ...options.language ? { language: options.language } : {}, ...options.overrides } : void 0;
184
185
  const request = {
185
186
  agent_id: options.agentId,
186
- ...options.language ? { language: options.language } : {},
187
187
  ...options.timezone ? { timezone: options.timezone } : {},
188
188
  ...clientTimezone ? { client_timezone: clientTimezone } : {},
189
189
  ...options.worldContext !== void 0 ? { world_context: options.worldContext } : {},
190
- ...options.overrides ? { overrides: options.overrides } : {},
190
+ ...overrides ? { overrides } : {},
191
191
  ...options.dynamicVariables ? { dynamic_variables: options.dynamicVariables } : {},
192
192
  ...options.endUserId ? { end_user_id: options.endUserId } : {},
193
193
  ...options.metadata ? { metadata: options.metadata } : {},
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/audio/output.ts","../src/audio/analysis.ts","../src/sessionToken.ts","../src/session/emitter.ts","../src/session/toolDispatcher.ts","../src/session/wakeLock.ts","../src/session/agentSession.ts"],"sourcesContent":["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","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","// 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"],"mappings":"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","names":["_a","_b"]}
1
+ {"version":3,"sources":["../src/audio/output.ts","../src/audio/analysis.ts","../src/sessionToken.ts","../src/session/emitter.ts","../src/session/toolDispatcher.ts","../src/session/wakeLock.ts","../src/session/agentSession.ts"],"sourcesContent":["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","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`; an explicit override wins. 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 overrides: SessionOverrides | undefined =\n options.language || options.overrides\n ? { ...(options.language ? { language: options.language } : {}), ...options.overrides }\n : undefined;\n const request: AgentSessionCreateRequest = {\n agent_id: options.agentId as string,\n ...(options.timezone ? { timezone: options.timezone } : {}),\n ...(clientTimezone ? { client_timezone: clientTimezone } : {}),\n ...(options.worldContext !== undefined ? { world_context: options.worldContext } : {}),\n ...(overrides ? { 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","// 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"],"mappings":";;;;;;;;;;AAGO,SAAS,iCAAuC;AACrD,MAAI,OAAO,qBAAqB,eAAe,EAAE,eAAe,iBAAiB,YAAY;AAC3F,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAGO,IAAM,cAAN,MAAkB;AAAA,EAAlB;AAEL,SAAQ,SAAS;AAAA;AAAA,EAET,gBAA8C;AACpD,QAAI,OAAO,aAAa,aAAa;AACnC,aAAO;AAAA,IACT;AACA,QAAI,CAAC,KAAK,SAAS;AACjB,WAAK,UAAU,SAAS,cAAc,OAAO;AAC7C,WAAK,QAAQ,WAAW;AAExB,WAAK,QAAQ,aAAa,eAAe,EAAE;AAC3C,WAAK,QAAQ,SAAS,KAAK;AAAA,IAC7B;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,UAAU,QAA2B;AACnC,UAAM,UAAU,KAAK,cAAc;AACnC,QAAI,CAAC,SAAS;AACZ;AAAA,IACF;AACA,YAAQ,YAAY;AACpB,SAAK,QAAQ,KAAK,EAAE,MAAM,MAAM;AAAA,IAEhC,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,aAA4B;AAChC,UAAM,KAAK,SAAS,KAAK;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,UAAU,QAA+B;AAC7C,mCAA+B;AAC/B,UAAM,UAAU,KAAK,cAAc;AACnC,QAAI;AACF,YAAM,SAAS,UAAU,MAAM;AAAA,IACjC,SAAS,OAAO;AACd,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA,EAAE,OAAO,MAAM;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,UAAU,QAAsB;AAC9B,SAAK,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,MAAM,CAAC;AAC7C,QAAI,KAAK,SAAS;AAChB,WAAK,QAAQ,SAAS,KAAK;AAAA,IAC7B;AAAA,EACF;AAAA,EAEA,YAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,UAAgB;AACd,QAAI,KAAK,SAAS;AAChB,WAAK,QAAQ,YAAY;AACzB,WAAK,QAAQ,OAAO;AACpB,WAAK,UAAU;AAAA,IACjB;AAAA,EACF;AACF;;;AChFO,IAAM,sBAAN,MAA0B;AAAA,EAK/B,UAAU,QAA2B;AACnC,QAAI,OAAO,iBAAiB,aAAa;AACvC;AAAA,IACF;AACA,SAAK,YAAL,KAAK,UAAY,IAAI,aAAa;AAClC,SAAK,aAAL,KAAK,WAAa,KAAK,QAAQ,eAAe;AAC9C,SAAK,QAAQ,WAAW;AACxB,SAAK,SAAS,KAAK,QAAQ,wBAAwB,MAAM;AACzD,SAAK,OAAO,QAAQ,KAAK,QAAQ;AACjC,SAAK,OAAO;AAAA,EACd;AAAA;AAAA,EAGA,SAAe;AACb,QAAI,KAAK,SAAS,UAAU,aAAa;AACvC,WAAK,KAAK,QAAQ,OAAO,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAC3C;AAAA,EACF;AAAA;AAAA,EAGA,YAAoB;AAClB,QAAI,CAAC,KAAK,UAAU;AAClB,aAAO;AAAA,IACT;AACA,UAAM,UAAU,IAAI,WAAW,KAAK,SAAS,OAAO;AACpD,SAAK,SAAS,sBAAsB,OAAO;AAC3C,QAAI,eAAe;AACnB,eAAW,UAAU,SAAS;AAC5B,YAAM,YAAY,SAAS,OAAO;AAClC,sBAAgB,WAAW;AAAA,IAC7B;AACA,WAAO,KAAK,KAAK,eAAe,QAAQ,MAAM;AAAA,EAChD;AAAA,EAEA,mBAA+B;AAC7B,QAAI,CAAC,KAAK,UAAU;AAClB,aAAO,IAAI,WAAW,CAAC;AAAA,IACzB;AACA,UAAM,OAAO,IAAI,WAAW,KAAK,SAAS,iBAAiB;AAC3D,SAAK,SAAS,qBAAqB,IAAI;AACvC,WAAO;AAAA,EACT;AAAA,EAEA,UAAgB;AACd,SAAK,QAAQ,WAAW;AACxB,SAAK,UAAU,WAAW;AAC1B,SAAK,KAAK,SAAS,MAAM,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AACzC,SAAK,SAAS;AACd,SAAK,WAAW;AAChB,SAAK,UAAU;AAAA,EACjB;AACF;;;AC7DA;AAAA,EACE;AAAA,OAKK;AAGA,IAAM,cAAc;AACpB,IAAM,qBAAqB;AAElC,IAAM,uBAAiC,CAAC,yBAAyB;AAuC1D,SAAS,qBAAqB,cAA0C;AAC7E,QAAM,YAAoB,aAAa;AACvC,MAAI,CAAC,qBAAqB,SAAS,SAAS,GAAG;AAC7C,UAAM,IAAI;AAAA,MACR;AAAA,MACA,iCAAiC,SAAS;AAAA,IAE5C;AAAA,EACF;AACA,QAAM,YAAY,KAAK,MAAM,aAAa,UAAU;AACpD,MAAI,CAAC,OAAO,MAAM,SAAS,KAAK,aAAa,KAAK,IAAI,GAAG;AACvD,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,oBAAoB,SAAuD;AAC/F,QAAM,WAAW,CAAC,QAAQ,SAAS,QAAQ,YAAY,EAAE,OAAO,CAAC,UAAU,UAAU,MAAS;AAC9F,MAAI,SAAS,WAAW,GAAG;AACzB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,QAAQ,cAAc;AACxB,WAAO,qBAAqB,QAAQ,YAAY;AAAA,EAClD;AACA,SAAO,qBAAqB,MAAM,oBAAoB,OAAO,CAAC;AAChE;AAIA,SAAS,uBAA2C;AAClD,MAAI;AACF,WAAO,KAAK,eAAe,EAAE,gBAAgB,EAAE,YAAY;AAAA,EAC7D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,oBAAoB,SAAuD;AACxF,QAAM,aAAa,QAAQ,aAAa,oBAAoB,QAAQ,OAAO,EAAE;AAC7E,QAAM,iBAAiB,qBAAqB;AAC5C,QAAM,YACJ,QAAQ,YAAY,QAAQ,YACxB,EAAE,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC,GAAI,GAAG,QAAQ,UAAU,IACpF;AACN,QAAM,UAAqC;AAAA,IACzC,UAAU,QAAQ;AAAA,IAClB,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;AAAA,IACzD,GAAI,iBAAiB,EAAE,iBAAiB,eAAe,IAAI,CAAC;AAAA,IAC5D,GAAI,QAAQ,iBAAiB,SAAY,EAAE,eAAe,QAAQ,aAAa,IAAI,CAAC;AAAA,IACpF,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;AAAA,IACjC,GAAI,QAAQ,mBAAmB,EAAE,mBAAmB,QAAQ,iBAAiB,IAAI,CAAC;AAAA,IAClF,GAAI,QAAQ,YAAY,EAAE,aAAa,QAAQ,UAAU,IAAI,CAAC;AAAA,IAC9D,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;AAAA,IACzD,GAAI,QAAQ,eAAe,SAAY,EAAE,aAAa,QAAQ,WAAW,IAAI,CAAC;AAAA,EAChF;AAEA,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,MAAM,GAAG,SAAS,sBAAsB;AAAA,MACvD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,cAAc,gBAAgB,WAAW;AAAA,MAC3C;AAAA,MACA,MAAM,KAAK,UAAU,OAAO;AAAA,IAC9B,CAAC;AAAA,EACH,SAAS,OAAO;AACd,UAAM,IAAI,eAAe,0BAA0B,wCAAwC;AAAA,MACzF;AAAA,IACF,CAAC;AAAA,EACH;AAEA,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,MAAM,mBAAmB,QAAQ;AAAA,EACzC;AACA,SAAQ,MAAM,SAAS,KAAK;AAC9B;AAEA,eAAe,mBAAmB,UAA6C;AAC7E,MAAI,UAAU,oCAAoC,SAAS,MAAM;AACjE,MAAI;AACJ,MAAI;AACF,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,cAAU,KAAK,WAAW,KAAK,SAAS;AACxC,WAAO,KAAK;AAAA,EACd,QAAQ;AAAA,EAER;AACA,QAAM,UAAU,EAAE,YAAY,SAAS,OAAO;AAG9C,MAAI,SAAS,WAAW,OAAO,SAAS,yBAAyB;AAC/D,WAAO,IAAI;AAAA,MACT;AAAA,MACA,GAAG,OAAO;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACA,MAAI,SAAS,WAAW,KAAK;AAC3B,WAAO,UAAU,KAAK,OAAO,IACzB,IAAI,eAAe,oBAAoB,SAAS,OAAO,IACvD,IAAI,eAAe,oBAAoB,SAAS,OAAO;AAAA,EAC7D;AACA,SAAO,IAAI,eAAe,0BAA0B,SAAS,OAAO;AACtE;;;AC7JO,IAAM,eAAN,MAAkE;AAAA,EAAlE;AACL,SAAiB,YAAY,oBAAI,IAAiC;AAAA;AAAA,EAElE,GAA2B,OAAU,UAA2B;AAC9D,QAAI,MAAM,KAAK,UAAU,IAAI,KAAK;AAClC,QAAI,CAAC,KAAK;AACR,YAAM,oBAAI,IAAI;AACd,WAAK,UAAU,IAAI,OAAO,GAAG;AAAA,IAC/B;AACA,QAAI,IAAI,QAAQ;AAChB,WAAO;AAAA,EACT;AAAA,EAEA,IAA4B,OAAU,UAA2B;AAC/D,SAAK,UAAU,IAAI,KAAK,GAAG,OAAO,QAAQ;AAC1C,WAAO;AAAA,EACT;AAAA,EAEA,KAA6B,OAAU,UAA2B;AAChE,UAAM,WAAW,IAAI,SAAgC;AACnD,WAAK,IAAI,OAAO,OAAO;AACvB,eAAS,GAAG,IAAI;AAAA,IAClB;AACA,WAAO,KAAK,GAAG,OAAO,OAAO;AAAA,EAC/B;AAAA,EAEU,KAA6B,UAAa,MAAmC;AACrF,UAAM,MAAM,KAAK,UAAU,IAAI,KAAK;AACpC,QAAI,CAAC,KAAK;AACR;AAAA,IACF;AACA,eAAW,YAAY,CAAC,GAAG,GAAG,GAAG;AAC/B,UAAI;AACF,iBAAS,GAAG,IAAI;AAAA,MAClB,SAAS,OAAO;AAEd,gBAAQ,MAAM,+BAA+B,KAAK;AAAA,MACpD;AAAA,IACF;AAAA,EACF;AACF;;;ACpCO,IAAM,iCAAiC;AAIvC,IAAM,uBAAN,MAA2B;AAAA,EAGhC,YACE,OACiB,WACA,MACA,SACjB;AAHiB;AACA;AACA;AANnB,SAAiB,QAAQ,oBAAI,IAA+B;AAQ1D,eAAW,CAAC,MAAM,OAAO,KAAK,OAAO,QAAQ,SAAS,CAAC,CAAC,GAAG;AACzD,WAAK,MAAM,IAAI,MAAM,OAAO;AAAA,IAC9B;AAAA,EACF;AAAA,EAEA,SAAS,MAAc,SAAkC;AACvD,SAAK,MAAM,IAAI,MAAM,OAAO;AAAA,EAC9B;AAAA,EAEA,MAAM,SAAS,MAAqC;AAClD,UAAM,UAAU,KAAK,MAAM,IAAI,KAAK,QAAQ;AAC5C,QAAI,CAAC,SAAS;AACZ,WAAK;AAAA,QACH,IAAI,eAAe,eAAe,gBAAgB,KAAK,QAAQ,qBAAqB;AAAA,MACtF;AACA,UAAI,KAAK,iBAAiB;AACxB,cAAM,KAAK,MAAM,KAAK,QAAQ,EAAE,SAAS,MAAM,QAAQ,yBAAyB,CAAC;AAAA,MACnF;AACA;AAAA,IACF;AAEA,QAAI;AACF,YAAM,SAAS,MAAM,KAAK;AAAA,QACxB,QAAQ,QAAQ,QAAQ,KAAK,QAAQ,EAAE,QAAQ,KAAK,QAAQ,UAAU,KAAK,SAAS,CAAC,CAAC;AAAA,QACtF,KAAK;AAAA,MACP;AACA,UAAI,KAAK,iBAAiB;AACxB,cAAM,KAAK,MAAM,KAAK,QAAQ,WAAW,SAAY,CAAC,IAAI,EAAE,OAAO,CAAC;AAAA,MACtE;AAAA,IACF,SAAS,OAAO;AACd,WAAK;AAAA,QACH,iBAAiB,iBACb,QACA,IAAI,eAAe,eAAe,gBAAgB,KAAK,QAAQ,WAAW;AAAA,UACxE,OAAO;AAAA,QACT,CAAC;AAAA,MACP;AACA,UAAI,KAAK,iBAAiB;AACxB,cAAM,KAAK,MAAM,KAAK,QAAQ,EAAE,SAAS,MAAM,QAAQ,OAAO,KAAK,EAAE,CAAC;AAAA,MACxE;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,MACZ,QACA,MACe;AACf,QAAI;AACF,YAAM,KAAK,KAAK,EAAE,MAAM,sBAAsB,QAAQ,GAAG,KAAK,CAAC;AAAA,IACjE,SAAS,OAAO;AACd,WAAK;AAAA,QACH,IAAI,eAAe,eAAe,0CAA0C;AAAA,UAC1E,OAAO;AAAA,QACT,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,YAAe,SAAqB,UAA8B;AACxE,WAAO,IAAI,QAAW,CAAC,SAAS,WAAW;AACzC,YAAM,QAAQ,WAAW,MAAM;AAC7B;AAAA,UACE,IAAI;AAAA,YACF;AAAA,YACA,gBAAgB,QAAQ,qBAAqB,KAAK,SAAS;AAAA,UAC7D;AAAA,QACF;AAAA,MACF,GAAG,KAAK,SAAS;AACjB,cAAQ;AAAA,QACN,CAAC,UAAU;AACT,uBAAa,KAAK;AAClB,kBAAQ,KAAK;AAAA,QACf;AAAA,QACA,CAAC,UAAmB;AAClB,uBAAa,KAAK;AAClB,iBAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAAA,QAClE;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;ACrGA;AAMO,IAAM,iBAAN,MAAqB;AAAA,EAArB;AAAA;AACL;AACA,gCAAU;AAEV,uBAAS,qBAAsB,MAAY;AACzC,UAAI,SAAS,oBAAoB,WAAW;AAC1C,aAAK,sBAAK,uCAAL;AAAA,MACP;AAAA,IACF;AAAA;AAAA,EAEA,UAAgB;AACd,QACE,mBAAK,YACL,OAAO,aAAa,eACpB,OAAO,cAAc,eACrB,CAAC,UAAU,UACX;AACA;AAAA,IACF;AACA,uBAAK,SAAU;AACf,aAAS,iBAAiB,oBAAoB,mBAAK,oBAAmB;AACtE,SAAK,sBAAK,uCAAL;AAAA,EACP;AAAA,EAwBA,UAAgB;AACd,QAAI,CAAC,mBAAK,UAAS;AACjB;AAAA,IACF;AACA,uBAAK,SAAU;AACf,aAAS,oBAAoB,oBAAoB,mBAAK,oBAAmB;AACzE,SAAK,mBAAK,YAAW,QAAQ,EAAE,MAAM,MAAM,MAAS;AACpD,uBAAK,WAAY;AAAA,EACnB;AACF;AAtDE;AACA;AAES;AAJJ;AAwBC,aAAQ,iBAAkB;AAC9B,MAAI,CAAC,mBAAK,UAAS;AACjB;AAAA,EACF;AACA,MAAI;AACF,UAAM,WAAW,MAAM,UAAU,SAAS,QAAQ,QAAQ;AAC1D,QAAI,CAAC,mBAAK,UAAS;AACjB,WAAK,SAAS,QAAQ,EAAE,MAAM,MAAM,MAAS;AAC7C;AAAA,IACF;AAGA,UAAM,WAAW,mBAAK;AACtB,uBAAK,WAAY;AACjB,QAAI,YAAY,aAAa,UAAU;AACrC,WAAK,SAAS,QAAQ,EAAE,MAAM,MAAM,MAAS;AAAA,IAC/C;AAAA,EACF,QAAQ;AAAA,EAER;AACF;;;ACWF,IAAM,wBAAwB;AAI9B,IAAI;AAjEJ;AAsEO,IAAM,gBAAN,MAAM,sBAAqB,aAAiC;AAAA,EA8BzD,YAAY,cAA4B;AAC9C,UAAM;AA/BH;AACL,gCAAyB;AACzB,8BAAmB;AACnB;AACA;AACA,kCAAY;AACZ,uCAAiB;AACjB;AAEA;AACA;AACA,uBAAS,SAAU,IAAI,YAAY;AACnC,uBAAS,iBAAkB,IAAI,oBAAoB;AACnD,uBAAS,gBAAiB,IAAI,oBAAoB;AAClD,uBAAS,WAAY,IAAI,eAAe;AAGxC;AAAA,uBAAS,gBAAiB,oBAAI,IAAoB;AAElD;AAAA,uBAAS,aAAmC,CAAC;AAC7C,uBAAS,kBAAmB,oBAAI,IAAoB;AACpD,2CAAqB;AAKrB;AAAA;AAAA;AAAA,sCAAgB;AAChB,yCAA2C,CAAC;AAC5C;AAIE,uBAAK,YAAa,aAAa;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAa,MAAM,SAAqD;AA9G1E;AAiHI,UAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,uBAAyB;AACnE,WAAO,oCAAa,oCAAb,SAAwB,SAAS,MAAM,IAAI,iBAAiB;AAAA,EACrE;AAAA;AAAA,EAIA,IAAI,YAAoB;AACtB,WAAO,mBAAK;AAAA,EACd;AAAA,EAEA,IAAI,SAAwB;AAC1B,WAAO,mBAAK;AAAA,EACd;AAAA,EAEA,IAAI,OAAkB;AACpB,WAAO,mBAAK;AAAA,EACd;AAAA,EAEA,IAAI,aAAsB;AACxB,WAAO,mBAAK,WAAU;AAAA,EACxB;AAAA,EAEA,IAAI,WAAoB;AACtB,WAAO,mBAAK;AAAA,EACd;AAAA,EAEA,IAAI,YAAmC;AACrC,WAAO,mBAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAqC;AACnC,WAAO,mBAAK,aAAY,IAAI,CAAC,aAAa,EAAE,GAAG,QAAQ,EAAE;AAAA,EAC3D;AAAA;AAAA;AAAA,EAKA,gBAAgB,MAAc,SAAqC;AACjE,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,SAAS;AACZ;AAAA,IACF;AACA,UAAM,UACJ,SAAS,UAAU,QACf,EAAE,MAAM,gBAAgB,MAAM,SAAS,OAAO,MAAM,IACpD,EAAE,MAAM,gBAAgB,MAAM,QAAQ;AAC5C,QAAI,mBAAK,gBAAe;AACtB,4BAAK,6CAAL,WAAsB;AAAA,IACxB,OAAO;AACL,yBAAK,kBAAiB,KAAK,OAAO;AAAA,IACpC;AACA,0BAAK,oDAAL,WAA6B;AAAA,EAC/B;AAAA,EAEA,mBAAyB;AACvB,0BAAK,6CAAL,WAAsB,EAAE,MAAM,gBAAgB;AAAA,EAChD;AAAA,EAEA,YAAkB;AAChB,0BAAK,6CAAL,WAAsB,EAAE,MAAM,iBAAiB;AAAA,EACjD;AAAA,EAEA,mBAAmB,MAAc,SAAkC;AACjE,uBAAK,QAAO,SAAS,MAAM,OAAO;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,YAAY,OAA+B;AA/LnD;AAgMI,UAAM,WAAW,mBAAK;AACtB,uBAAK,WAAY;AACjB,QAAI;AACF,YAAM,mBAAK,YAAW,cAAc,CAAC,KAAK;AAAA,IAC5C,SAAS,OAAO;AACd,yBAAK,WAAY;AACjB,YAAM,oCAAa,qCAAb,SAAyB;AAAA,IACjC;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,aAA4B;AAChC,uBAAK,iBAAgB,OAAO;AAC5B,uBAAK,gBAAe,OAAO;AAC3B,UAAM,mBAAK,SAAQ,WAAW;AAAA,EAChC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,eAAe,UAAiC;AAxNxD;AAyNI,QAAI;AACF,YAAM,mBAAK,YAAW,eAAe,QAAQ;AAAA,IAC/C,SAAS,OAAO;AACd,YAAM,oCAAa,wCAAb,SAA4B,OAAO;AAAA,IAC3C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,gBAAgB,UAAiC;AACrD,UAAM,mBAAK,SAAQ,UAAU,QAAQ;AAAA,EACvC;AAAA,EAEA,gBAAgB,QAAsB;AACpC,uBAAK,SAAQ,UAAU,MAAM;AAAA,EAC/B;AAAA,EAEA,kBAA0B;AACxB,WAAO,mBAAK,iBAAgB,UAAU;AAAA,EACxC;AAAA,EAEA,iBAAyB;AACvB,WAAO,mBAAK,gBAAe,UAAU;AAAA,EACvC;AAAA,EAEA,yBAAqC;AACnC,WAAO,mBAAK,iBAAgB,iBAAiB;AAAA,EAC/C;AAAA,EAEA,wBAAoC;AAClC,WAAO,mBAAK,gBAAe,iBAAiB;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,UAA4B;AAC1B,WAAO,mBAAK,YAAW,UAAU;AAAA,EACnC;AAAA;AAAA,EAIA,MAAqB;AACnB,QAAI,mBAAK,UAAS;AAChB,aAAO,mBAAK;AAAA,IACd;AACA,QAAI,mBAAK,aAAY,SAAS;AAC5B,aAAO,QAAQ,QAAQ;AAAA,IACzB;AACA,UAAM,SAAS,sBAAK,qCAAL;AACf,uBAAK,SAAU;AACf,SAAK,OAAO,MAAM,MAAM;AACtB,UAAI,mBAAK,aAAY,QAAQ;AAC3B,2BAAK,SAAU;AAAA,MACjB;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT;AAkVF;AAriBE;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACS;AACA;AACA;AACA;AAGA;AAEA;AACA;AACT;AAKA;AACA;AACA;AA5BK;AAsNC,aAAQ,iBAAkB;AAC9B,qBAAK,gBAAiB;AACtB,MAAI;AAGF,UAAM,mBAAK,YAAW,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAAA,EAC/D,QAAQ;AAAA,EAER;AACA,QAAM,mBAAK,YAAW,WAAW;AACjC,wBAAK,sCAAL,WAAe;AACjB;AAjOK;AAyOQ,eAAU,eACrB,SACA,iBACuB;AAlT3B;AAmTI,MAAI,QAAQ,OAAO,mBAAmB,QAAW;AAG/C,mCAA+B;AAAA,EACjC;AACA,QAAM,eAAe,MAAM,oBAAoB,OAAO;AACtD,QAAM,UAAU,IAAI,cAAa,YAAY;AAC7C,MAAI,QAAQ,OAAO,mBAAmB,QAAW;AAG/C,UAAM,sBAAQ,SAAQ,UAAU,QAAQ,MAAM,cAAc;AAAA,EAC9D;AAEA,aAAW,CAAC,KAAK,QAAQ,KAAK,OAAO,QAAQ,QAAQ,aAAa,CAAC,CAAC,GAAG;AACrE,UAAM,QAAS,IAAI,OAAO,CAAC,EAAE,YAAY,IACvC,IAAI,MAAM,CAAC;AAEb,YAAQ,GAAG,OAAO,QAAe;AAAA,EACnC;AAEA,QAAM,YAAY,gBAAgB,YAAY;AAC9C,wBAAQ,YAAa;AACrB,MAAI,QAAQ,eAAe,OAAO;AAChC,0BAAQ,WAAY;AAAA,EACtB;AACA,wBAAQ,QAAS,IAAI;AAAA,IACnB,QAAQ;AAAA,IACR,QAAQ,uBAAuB;AAAA,IAC/B,CAAC,YAAY,UAAU,gBAAgB,OAAO;AAAA,IAC9C,CAAC,UAAU,QAAQ,KAAK,SAAS,KAAK;AAAA,EACxC;AAEA,MAAI;AACF,UAAM,UAAU,QAAQ,cAAc;AAAA,MACpC,eAAe,QAAQ,OAAO;AAAA,MAC9B,YAAY,QAAQ;AAAA,MACpB,WAAW;AAAA,QACT,cAAc,CAAC,YAAS;AAxVlC,cAAAA;AAwVqC,iCAAAA,MAAA,SAAQ,8CAAR,KAAAA,KAA0B;AAAA;AAAA;AAAA;AAAA;AAAA,QAIrD,cAAc,CAAC,UAAU;AA5VnC,cAAAA,KAAAC;AA+VY,0BAAAD,MAAA,SAAQ,gDAAR,KAAAA;AACA,cAAI,UAAU,YAAY;AACxB,4BAAAC,MAAA,SAAQ,qCAAR,KAAAA,KAAiB;AAAA,UACnB;AAAA,QACF;AAAA,QACA,iBAAiB,CAAC,WAAQ;AApWpC,cAAAD;AAoWuC,iCAAAA,MAAA,SAAQ,iDAAR,KAAAA,KAA6B;AAAA;AAAA,QAC1D,gBAAgB,CAAC,WAAW;AAC1B,gCAAQ,SAAQ,UAAU,MAAM;AAChC,gCAAQ,iBAAgB,UAAU,MAAM;AAAA,QAC1C;AAAA,QACA,eAAe,CAAC,WAAW,sBAAQ,gBAAe,UAAU,MAAM;AAAA,QAClE,mBAAmB,CAAC,OAAO,WAAQ;AA1W7C,cAAAA;AA0WgD,iCAAAA,MAAA,SAAQ,mDAAR,KAAAA,KAA+B,OAAO;AAAA;AAAA,MAC9E;AAAA,IACF,CAAC;AAAA,EACH,SAAS,OAAO;AAGd,UAAM,sBAAQ,YAAW,WAAW,EAAE,MAAM,MAAM,MAAS;AAC3D,kCAAQ,sCAAR,SAAkB;AAClB,UAAM,oCAAa,uCAAb,SAA2B;AAAA,EACnC;AAEA,MAAI,sBAAQ,aAAY,cAAc;AACpC,kCAAQ,uCAAR,SAAmB;AACnB,YAAQ,KAAK,WAAW,EAAE,WAAW,sBAAQ,YAAW,CAAC;AAAA,EAC3D;AACA,MAAI,sBAAQ,aAAY,WAAW,QAAQ,aAAa,OAAO;AAC7D,0BAAQ,WAAU,QAAQ;AAAA,EAC5B;AACA,MAAI,CAAC,sBAAQ,kBAAiB,sBAAQ,aAAY,aAAa;AAC7D,0BAAQ,YAAa,WAAW,MAAG;AA7XzC,UAAAA;AA6X4C,6BAAAA,MAAA,SAAQ,8CAAR,KAAAA;AAAA,OAA6B,qBAAqB;AAAA,EAC1F;AACA,SAAO;AACT;AAEO,kBAAa,SAAC,OAAgC;AACnD,MAAI,iBAAiB,gBAAgB;AACnC,WAAO;AAAA,EACT;AACA,MAAI,iBAAiB,SAAS,MAAM,SAAS,mBAAmB;AAC9D,WAAO,IAAI,eAAe,yBAAyB,oCAAoC;AAAA,MACrF,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,SAAO,IAAI,eAAe,qBAAqB,4CAA4C;AAAA,IACzF,OAAO;AAAA,EACT,CAAC;AACH;AAEO,gBAAW,SAAC,OAAgC;AACjD,MAAI,iBAAiB,gBAAgB;AACnC,WAAO;AAAA,EACT;AACA,MAAI,iBAAiB,SAAS,MAAM,SAAS,mBAAmB;AAC9D,WAAO,IAAI,eAAe,yBAAyB,oCAAoC;AAAA,MACrF,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,MACE,iBAAiB,UAChB,MAAM,SAAS,0BAA0B,MAAM,SAAS,kBACzD;AAIA,WAAO,IAAI;AAAA,MACT;AAAA,MACA;AAAA,MACA,EAAE,OAAO,MAAM;AAAA,IACjB;AAAA,EACF;AACA,SAAO,IAAI,eAAe,qBAAqB,mCAAmC;AAAA,IAChF,OAAO;AAAA,EACT,CAAC;AACH;AAEO,mBAAc,SAAC,OAAgB,SAAiC;AACrE,MAAI,iBAAiB,gBAAgB;AACnC,WAAO;AAAA,EACT;AACA,SAAO,IAAI,eAAe,wBAAwB,SAAS,EAAE,OAAO,MAAM,CAAC;AAC7E;AAEA,qBAAgB,SAAC,SAA4D;AAC3E,OAAK,mBAAK,YAAW,gBAAgB,OAAO,EAAE,MAAM,CAAC,UAAmB;AACtE,SAAK;AAAA,MACH;AAAA,MACA,IAAI,eAAe,qBAAqB,+BAA+B,EAAE,OAAO,MAAM,CAAC;AAAA,IACzF;AAAA,EACF,CAAC;AACH;AAEA,eAAU,SAAC,QAA6B;AACtC,MAAI,mBAAK,aAAY,QAAQ;AAC3B,uBAAK,SAAU;AACf,SAAK,KAAK,gBAAgB,MAAM;AAAA,EAClC;AACF;AAEA,aAAQ,SAAC,MAAuB;AAC9B,MAAI,mBAAK,WAAU,MAAM;AACvB,uBAAK,OAAQ;AACb,SAAK,KAAK,cAAc,IAAI;AAAA,EAC9B;AACF;AAEA,wBAAmB,WAAS;AAC1B,MAAI,mBAAK,gBAAe;AACtB;AAAA,EACF;AACA,qBAAK,eAAgB;AACrB,MAAI,mBAAK,aAAY;AACnB,iBAAa,mBAAK,WAAU;AAC5B,uBAAK,YAAa;AAAA,EACpB;AACA,QAAM,UAAU,mBAAK;AACrB,qBAAK,kBAAmB,CAAC;AACzB,aAAW,WAAW,SAAS;AAC7B,0BAAK,6CAAL,WAAsB;AAAA,EACxB;AACF;AAEA,sBAAiB,WAAS;AACxB,MAAI,mBAAK,aAAY,WAAW,mBAAK,gBAAe;AAClD;AAAA,EACF;AACA,OAAK;AAAA,IACH;AAAA,IACA,IAAI,eAAe,qBAAqB,oCAAoC;AAAA,EAC9E;AACA,OAAK,mBAAK,YAAW,WAAW,EAAE,MAAM,MAAM;AAAA,EAAC,CAAC;AAChD,wBAAK,sCAAL,WAAe;AACjB;AAEA,2BAAsB,SAAC,OAAiC,QAAuB;AAC7E,MAAI,mBAAK,aAAY,SAAS;AAC5B;AAAA,EACF;AACA,MAAI,UAAU,gBAAgB;AAC5B,0BAAK,uCAAL,WAAgB;AAChB;AAAA,EACF;AACA,MAAI,UAAU,aAAa;AACzB,QAAI,mBAAK,aAAY,gBAAgB;AACnC,4BAAK,uCAAL,WAAgB;AAAA,IAClB;AACA;AAAA,EACF;AAIA,QAAM,QAAQ,mBAAK,kBACf,gBACA,WAAW,gBAAgB,WAAW,kBAAkB,WAAW,gBACjE,iBACA;AACN,wBAAK,sCAAL,WAAe;AACjB;AAEA,cAAS,SAAC,QAAyB;AACjC,MAAI,mBAAK,aAAY,SAAS;AAC5B;AAAA,EACF;AACA,MAAI,mBAAK,aAAY;AACnB,iBAAa,mBAAK,WAAU;AAC5B,uBAAK,YAAa;AAAA,EACpB;AACA,qBAAK,YAAa;AAClB,wBAAK,uCAAL,WAAgB;AAChB,qBAAK,WAAU,QAAQ;AACvB,qBAAK,SAAQ,QAAQ;AACrB,qBAAK,iBAAgB,QAAQ;AAC7B,qBAAK,gBAAe,QAAQ;AAC5B,OAAK,KAAK,cAAc,EAAE,OAAO,CAAC;AACpC;AAEA,sBAAiB,SAAC,SAAoC;AACpD,UAAQ,QAAQ,MAAM;AAAA,IACpB,KAAK;AACH,WAAK,mBAAK,QAAO,SAAS,OAAO;AACjC;AAAA,IACF,KAAK;AACH,WAAK,KAAK,mBAAmB;AAAA,QAC3B,QAAQ,QAAQ;AAAA,QAChB,UAAU,QAAQ;AAAA,QAClB,QAAQ,QAAQ;AAAA,QAChB,OAAO,QAAQ;AAAA,QACf,gBAAgB,QAAQ,mBAAmB;AAAA,MAC7C,CAAC;AACD;AAAA,IACF,KAAK;AACH,WAAK,KAAK,qBAAqB;AAAA,QAC7B,QAAQ,QAAQ;AAAA,QAChB,UAAU,QAAQ;AAAA,QAClB,QAAQ,QAAQ;AAAA,QAChB,QAAQ,QAAQ;AAAA,QAChB,iBAAiB,QAAQ,oBAAoB;AAAA,MAC/C,CAAC;AACD;AAAA,IACF,KAAK;AACH,WAAK,KAAK,kBAAkB;AAAA,QAC1B,QAAQ,QAAQ;AAAA,QAChB,UAAU,QAAQ;AAAA,QAClB,QAAQ,QAAQ;AAAA,QAChB,OAAO,QAAQ;AAAA,MACjB,CAAC;AACD;AAAA,IACF,KAAK;AACH,WAAK;AAAA,QACH;AAAA,QACA,QAAQ,SAAS,mBACb,IAAI,eAAe,kBAAkB,gDAAgD,IACrF,IAAI,eAAe,kBAAkB,yCAAyC;AAAA,MACpF;AACA;AAAA,IACF;AAEE;AAAA,EACJ;AACF;AAEA,mBAAc,SAAC,MAAwB,WAAmB,MAAc,OAAsB;AAC5F,QAAM,MAAM,GAAG,IAAI,IAAI,SAAS;AAChC,QAAM,QAAQ,mBAAK,kBAAiB,IAAI,GAAG;AAC3C,MAAI,UAAU,QAAW;AACvB,uBAAK,kBAAiB,IAAI,KAAK,mBAAK,aAAY,MAAM;AACtD,uBAAK,aAAY,KAAK,EAAE,WAAW,MAAM,MAAM,MAAM,CAAC;AAAA,EACxD,OAAO;AACL,uBAAK,aAAY,KAAK,IAAI,EAAE,WAAW,MAAM,MAAM,MAAM;AAAA,EAC3D;AACF;AAEA,yBAAoB,SAAC,QAA0C;AAC7D,MAAI,OAAO,SAAS,QAAQ;AAC1B,0BAAK,2CAAL,WAAoB,QAAQ,OAAO,WAAW,OAAO,MAAM,OAAO;AAClE,SAAK,KAAK,kBAAkB;AAAA,MAC1B,WAAW,OAAO;AAAA,MAClB,MAAM,OAAO;AAAA,MACb,OAAO,OAAO;AAAA,IAChB,CAAC;AACD,QAAI,OAAO,OAAO;AAChB,WAAK,KAAK,WAAW,EAAE,MAAM,QAAQ,MAAM,OAAO,KAAK,CAAC;AAAA,IAC1D;AACA;AAAA,EACF;AAGA,wBAAK,qCAAL,WAAc,OAAO,QAAQ,cAAc;AAC3C,wBAAK,2CAAL,WAAoB,SAAS,OAAO,WAAW,OAAO,MAAM,OAAO;AACnE,QAAM,WAAW,mBAAK,gBAAe,IAAI,OAAO,SAAS,KAAK;AAC9D,QAAM,QAAQ,OAAO,KAAK,WAAW,QAAQ,IAAI,OAAO,KAAK,MAAM,SAAS,MAAM,IAAI,OAAO;AAC7F,qBAAK,gBAAe,IAAI,OAAO,WAAW,OAAO,IAAI;AACrD,MAAI,OAAO;AACT,SAAK,KAAK,sBAAsB,EAAE,WAAW,OAAO,WAAW,OAAO,MAAM,OAAO,KAAK,CAAC;AAAA,EAC3F;AACA,MAAI,OAAO,OAAO;AAChB,uBAAK,gBAAe,OAAO,OAAO,SAAS;AAC3C,SAAK,KAAK,iBAAiB,EAAE,WAAW,OAAO,WAAW,MAAM,OAAO,KAAK,CAAC;AAC7E,SAAK,KAAK,WAAW,EAAE,MAAM,SAAS,MAAM,OAAO,KAAK,CAAC;AAAA,EAC3D;AACF;AAAA;AAGA,4BAAuB,SAAC,MAAoB;AAC1C,QAAM,YAAY,SAAgB,EAAL,uBAAK,oBAAL,CAAuB;AACpD,wBAAK,2CAAL,WAAoB,QAAQ,WAAW,MAAM;AAC7C,OAAK,KAAK,kBAAkB,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAC5D,OAAK,KAAK,WAAW,EAAE,MAAM,QAAQ,KAAK,CAAC;AAC7C;AAriBK,aAAM,eAAN;AAsOH,YAAY,CAAC,SAAS,oBAAiB;AA5S3C;AA4S8C,6CAAa,oCAAb,SAAwB,SAAS;AAAA;AAtOxE,IAAM,eAAN;","names":["_a","_b"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fishaudio/agent-client",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "Fish Audio voice-agent client SDK for browsers: sessions, realtime audio, transcripts, client tools.",
5
5
  "keywords": [
6
6
  "fish-audio",
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "livekit-client": "^2.20.0",
52
- "@fishaudio/agent-protocol": "^0.0.1"
52
+ "@fishaudio/agent-protocol": "^0.1.0"
53
53
  },
54
54
  "devDependencies": {
55
55
  "tsup": "^8.5.0",