@sayna-ai/node-sdk
Version:
Node.js SDK for Sayna.ai server-side WebSocket connections
391 lines • 15.2 kB
TypeScript
import type { STTConfig, TTSConfig, LiveKitConfig, STTResultMessage, ErrorMessage, SaynaMessage, Participant, VoicesResponse, HealthResponse, LiveKitTokenResponse, SipHook, SipHooksResponse } from "./types";
/**
* Event handler for speech-to-text results.
*/
export type STTResultHandler = (result: STTResultMessage) => void | Promise<void>;
/**
* Event handler for text-to-speech audio data.
*/
export type TTSAudioHandler = (audio: ArrayBuffer) => void | Promise<void>;
/**
* Event handler for error messages.
*/
export type ErrorHandler = (error: ErrorMessage) => void | Promise<void>;
/**
* Event handler for participant messages.
*/
export type MessageHandler = (message: SaynaMessage) => void | Promise<void>;
/**
* Event handler for participant disconnections.
*/
export type ParticipantDisconnectedHandler = (participant: Participant) => void | Promise<void>;
/**
* Event handler for TTS playback completion.
*/
export type TTSPlaybackCompleteHandler = (timestamp: number) => void | Promise<void>;
/**
* Client for connecting to Sayna WebSocket server for real-time voice interactions.
*
* @example
* ```typescript
* const client = await saynaConnect(
* "https://api.sayna.ai",
* sttConfig,
* ttsConfig
* );
*
* client.registerOnSttResult((result) => {
* console.log("Transcription:", result.transcript);
* });
*
* await client.speak("Hello, world!");
* ```
*/
export declare class SaynaClient {
private url;
private sttConfig?;
private ttsConfig?;
private livekitConfig?;
private withoutAudio;
private apiKey?;
private websocket?;
private isConnected;
private isReady;
private _livekitRoomName?;
private _livekitUrl?;
private _saynaParticipantIdentity?;
private _saynaParticipantName?;
private _streamId?;
private inputStreamId?;
private sttCallback?;
private ttsCallback?;
private errorCallback?;
private messageCallback?;
private participantDisconnectedCallback?;
private ttsPlaybackCompleteCallback?;
private readyPromiseResolve?;
private readyPromiseReject?;
/**
* Creates a new SaynaClient instance.
*
* @param url - The Sayna server URL (e.g., "https://api.sayna.ai")
* @param sttConfig - Speech-to-text configuration (required when withoutAudio=false)
* @param ttsConfig - Text-to-speech configuration (required when withoutAudio=false)
* @param livekitConfig - Optional LiveKit room configuration
* @param withoutAudio - If true, disables audio streaming (default: false)
* @param apiKey - Optional API key used to authorize HTTP and WebSocket calls (defaults to SAYNA_API_KEY env)
* @param streamId - Optional session identifier for recording paths; server generates a UUID when omitted
*
* @throws {SaynaValidationError} If URL is invalid or if audio configs are missing when audio is enabled
*/
constructor(url: string, sttConfig?: STTConfig, ttsConfig?: TTSConfig, livekitConfig?: LiveKitConfig, withoutAudio?: boolean, apiKey?: string, streamId?: string);
/**
* Establishes connection to the Sayna WebSocket server.
*
* @throws {SaynaConnectionError} If connection fails
* @returns Promise that resolves when the connection is ready
*/
connect(): Promise<void>;
/**
* Handles incoming JSON messages from the WebSocket.
* @internal
*/
private handleJsonMessage;
/**
* Cleans up internal state.
* @internal
*/
private cleanup;
/**
* Converts WebSocket URL to HTTP URL for REST API calls.
* @internal
*/
private getHttpUrl;
/**
* Generic fetch helper for making REST API calls to Sayna server.
* Handles URL construction, headers, error responses, and type conversion.
* @internal
*
* @param endpoint - API endpoint path (e.g., "/voices", "/speak")
* @param options - Fetch options including method, body, headers, etc.
* @param responseType - Expected response type: "json" or "arrayBuffer"
* @returns Promise resolving to the parsed response
* @throws {SaynaConnectionError} If the network request fails
* @throws {SaynaServerError} If the server returns an error response
*/
private fetchFromSayna;
/**
* Disconnects from the Sayna WebSocket server and cleans up resources.
*/
disconnect(): void;
/**
* Sends audio data to the server for speech recognition.
*
* @param audioData - Raw audio data as ArrayBuffer
* @throws {SaynaNotConnectedError} If not connected
* @throws {SaynaNotReadyError} If connection is not ready
*/
onAudioInput(audioData: ArrayBuffer): void;
/**
* Registers a callback for speech-to-text results.
*
* @param callback - Function to call when STT results are received
*/
registerOnSttResult(callback: STTResultHandler): void;
/**
* Registers a callback for text-to-speech audio data.
*
* @param callback - Function to call when TTS audio is received
*/
registerOnTtsAudio(callback: TTSAudioHandler): void;
/**
* Registers a callback for error messages.
*
* @param callback - Function to call when errors occur
*/
registerOnError(callback: ErrorHandler): void;
/**
* Registers a callback for participant messages.
*
* @param callback - Function to call when messages are received
*/
registerOnMessage(callback: MessageHandler): void;
/**
* Registers a callback for participant disconnection events.
*
* @param callback - Function to call when a participant disconnects
*/
registerOnParticipantDisconnected(callback: ParticipantDisconnectedHandler): void;
/**
* Registers a callback for TTS playback completion.
*
* @param callback - Function to call when TTS playback is complete
*/
registerOnTtsPlaybackComplete(callback: TTSPlaybackCompleteHandler): void;
/**
* Sends text to be synthesized as speech.
*
* @param text - Text to synthesize
* @param flush - Whether to flush the TTS queue before speaking (default: true)
* @param allowInterruption - Whether this speech can be interrupted (default: true)
* @throws {SaynaNotConnectedError} If not connected
* @throws {SaynaNotReadyError} If connection is not ready
* @throws {SaynaValidationError} If text is not a string
*/
speak(text: string, flush?: boolean, allowInterruption?: boolean): void;
/**
* Clears the text-to-speech queue.
*
* @throws {SaynaNotConnectedError} If not connected
* @throws {SaynaNotReadyError} If connection is not ready
*/
clear(): void;
/**
* Flushes the TTS queue by sending an empty speak command.
*
* @param allowInterruption - Whether the flush can be interrupted (default: true)
* @throws {SaynaNotConnectedError} If not connected
* @throws {SaynaNotReadyError} If connection is not ready
*/
ttsFlush(allowInterruption?: boolean): void;
/**
* Sends a message to the Sayna session.
*
* @param message - Message content
* @param role - Message role (e.g., "user", "assistant")
* @param topic - Optional topic identifier
* @param debug - Optional debug metadata
* @throws {SaynaNotConnectedError} If not connected
* @throws {SaynaNotReadyError} If connection is not ready
* @throws {SaynaValidationError} If parameters are invalid
*/
sendMessage(message: string, role: string, topic?: string, debug?: Record<string, unknown>): void;
/**
* Performs a health check on the Sayna server.
*
* @returns Promise that resolves with the health status
* @throws {SaynaConnectionError} If the request fails
* @throws {SaynaServerError} If server returns an error
*
* @example
* ```typescript
* const health = await client.health();
* console.log(health.status); // "OK"
* ```
*/
health(): Promise<HealthResponse>;
/**
* Retrieves the catalogue of text-to-speech voices grouped by provider.
*
* @returns Promise that resolves with voices organized by provider
* @throws {SaynaConnectionError} If the request fails
* @throws {SaynaServerError} If server returns an error
*
* @example
* ```typescript
* const voices = await client.getVoices();
* for (const [provider, voiceList] of Object.entries(voices)) {
* console.log(`${provider}:`, voiceList.map(v => v.name));
* }
* ```
*/
getVoices(): Promise<VoicesResponse>;
/**
* Synthesizes text into audio using the REST API endpoint.
* This is a standalone synthesis method that doesn't require an active WebSocket connection.
*
* @param text - Text to synthesize
* @param ttsConfig - Text-to-speech configuration
* @returns Promise that resolves with the audio data as ArrayBuffer
* @throws {SaynaValidationError} If text is empty
* @throws {SaynaConnectionError} If the request fails
* @throws {SaynaServerError} If server returns an error
*
* @example
* ```typescript
* const audioBuffer = await client.speakRest("Hello, world!", {
* provider: "elevenlabs",
* voice_id: "21m00Tcm4TlvDq8ikWAM",
* model: "eleven_turbo_v2",
* speaking_rate: 1.0,
* audio_format: "mp3",
* sample_rate: 24000,
* connection_timeout: 30,
* request_timeout: 60,
* pronunciations: []
* });
* ```
*/
speakRest(text: string, ttsConfig: TTSConfig): Promise<ArrayBuffer>;
/**
* Issues a LiveKit access token for a participant.
*
* @param roomName - LiveKit room to join or create
* @param participantName - Display name assigned to the participant
* @param participantIdentity - Unique identifier for the participant
* @returns Promise that resolves with the LiveKit token and connection details
* @throws {SaynaValidationError} If any parameter is empty
* @throws {SaynaConnectionError} If the request fails
* @throws {SaynaServerError} If server returns an error
*
* @example
* ```typescript
* const tokenInfo = await client.getLiveKitToken(
* "my-room",
* "John Doe",
* "user-123"
* );
* console.log("Token:", tokenInfo.token);
* console.log("LiveKit URL:", tokenInfo.livekit_url);
* ```
*/
getLiveKitToken(roomName: string, participantName: string, participantIdentity: string): Promise<LiveKitTokenResponse>;
/**
* Downloads the recorded audio file for a completed session.
*
* @param streamId - The session identifier (obtained from the `streamId` getter after connection)
* @returns Promise that resolves with the audio data as ArrayBuffer (OGG format)
* @throws {SaynaValidationError} If streamId is empty
* @throws {SaynaConnectionError} If the network request fails
* @throws {SaynaServerError} If the recording is not found or server returns an error
*
* @example
* ```typescript
* // After a session completes, download the recording
* const audioBuffer = await client.getRecording(client.streamId!);
*
* // Save to file (Node.js)
* import { writeFile } from "fs/promises";
* await writeFile("recording.ogg", Buffer.from(audioBuffer));
* ```
*/
getRecording(streamId: string): Promise<ArrayBuffer>;
/**
* Retrieves all configured SIP webhook hooks from the runtime cache.
*
* @returns Promise that resolves with the list of configured SIP hooks
* @throws {SaynaConnectionError} If the request fails
* @throws {SaynaServerError} If server returns an error (e.g., 500 if reading cache fails)
*
* @example
* ```typescript
* const response = await client.getSipHooks();
* for (const hook of response.hooks) {
* console.log(`Host: ${hook.host}, URL: ${hook.url}`);
* }
* ```
*/
getSipHooks(): Promise<SipHooksResponse>;
/**
* Sets or updates SIP webhook hooks in the runtime cache.
*
* Hooks with matching hosts will be replaced; new hosts will be added.
* The response contains the merged list of all hooks (existing + new).
*
* @param hooks - Array of SIP hook configurations to add or replace
* @returns Promise that resolves with the merged list of all configured hooks
* @throws {SaynaValidationError} If hooks array is empty or contains invalid entries
* @throws {SaynaConnectionError} If the request fails
* @throws {SaynaServerError} If server returns an error (e.g., 400 for duplicate hosts, 500 for cache errors)
*
* @example
* ```typescript
* const response = await client.setSipHooks([
* { host: "example.com", url: "https://webhook.example.com/events" },
* { host: "another.com", url: "https://webhook.another.com/events" }
* ]);
* console.log("Total hooks configured:", response.hooks.length);
* ```
*/
setSipHooks(hooks: SipHook[]): Promise<SipHooksResponse>;
/**
* Deletes SIP webhook hooks by host name from the runtime cache.
*
* If a deleted host exists in the original server configuration,
* it will revert to its config value after deletion.
*
* @param hosts - Array of host names to remove (case-insensitive)
* @returns Promise that resolves with the updated list of hooks after deletion
* @throws {SaynaValidationError} If hosts array is empty or contains invalid entries
* @throws {SaynaConnectionError} If the request fails
* @throws {SaynaServerError} If server returns an error (e.g., 400 for empty hosts, 500 for cache errors)
*
* @example
* ```typescript
* const response = await client.deleteSipHooks(["example.com", "another.com"]);
* console.log("Remaining hooks:", response.hooks.length);
* ```
*/
deleteSipHooks(hosts: string[]): Promise<SipHooksResponse>;
/**
* Whether the client is ready to send/receive data.
*/
get ready(): boolean;
/**
* Whether the client is connected to the WebSocket.
*/
get connected(): boolean;
/**
* LiveKit room name acknowledged by the server, if available.
*/
get livekitRoomName(): string | undefined;
/**
* LiveKit WebSocket URL configured on the server, if available.
*/
get livekitUrl(): string | undefined;
/**
* Identity assigned to the agent participant when LiveKit is enabled, if available.
*/
get saynaParticipantIdentity(): string | undefined;
/**
* Display name assigned to the agent participant when LiveKit is enabled, if available.
*/
get saynaParticipantName(): string | undefined;
/**
* Session identifier returned by the server.
* This can be used to download recordings or correlate session data.
* The value is available after the connection is ready.
*/
get streamId(): string | undefined;
}
//# sourceMappingURL=sayna-client.d.ts.map