CLIENT SDK
The Client SDK (@aether-stack-dev/client-sdk) runs in the browser and connects to an AStack worker via WebSocket for real-time voice, text, and video AI.
INSTALLATION
$ npm install @aether-stack-dev/client-sdk
CONFIGURATION
// workerUrl is dynamic — returned by the server SDK when you create a sessionconst { sessionToken, workerUrl } = await fetch('/api/start-session').then(r => r.json());const client = new AStackCSRClient({workerUrl: workerUrl, // Dynamic per-session URL from serversessionToken: sessionToken, // Auth token from server SDKsessionId: 'sess_...', // Alternative: direct session IDsampleRate: 24000, // Audio sample rate (default: 24000)fps: 30, // Animation frame rate (default: 30)enableImageCapture: true, // Enable camera capture (default: true)imageCaptureInterval: 5000, // Capture interval ms (default: 5000)});
workerUrlRequired. WebSocket URL of the AStack workersessionTokenAuth token generated by server SDKenableImageCaptureSend camera frames for vision processingautoReconnectAuto-reconnect on disconnect (default: true)maxRetriesMax reconnection attempts (default: 5)reconnectDelayBase delay between reconnects in ms, with exponential backoff (default: 1000)audioProcessorUrlPath to AudioWorklet processor file (default: '/audio-processor.js')debugEnable debug logging (default: false)METHODS
connect(): Promise<void>Establish WebSocket connection and authenticatestartCall(options?: CallOptions): Promise<void>Initialize audio context, request mic/camera, start streaming. Options: providers, systemPrompt, priorContext, configOverrides, disableA2FstopCall(): voidStop audio/video capture, reset blendshapesdisconnect(): voidStop call and close WebSocket connectionsendText(message: string): voidSend text input to the AI (instead of voice)getCallStatus(): CallStatusReturns 'idle' | 'starting' | 'active' | 'stopping' | 'error'getCurrentBlendshapes(): number[]Returns current 52-value blendshape arrayisConnected(): booleanReturns true if WebSocket is opendestroy(): Promise<void>Disconnect and clean up all resourcesgetReconnectAttempts(): numberReturns current reconnection attempt countSTARTCALL OPTIONS
await client.startCall({providers: {asr: 'self', // 'self' | 'deepinfra' (default: 'self')llm: 'self', // 'self' | 'deepinfra' (default: 'self')tts: 'self', // 'self' | 'deepinfra' (default: 'self')},systemPrompt: 'You are a helpful assistant.',disableA2F: false,});
EVENTS
The client extends EventEmitter. Listen for events with client.on(event, callback).
connected() => voiddisconnected() => voidreconnecting(attempt: number) => voidreconnected() => voiderror(error: Error) => voidcallStarted() => voidcallStopped() => voidspeechStarted() => voidinterim(text: string) => voidtranscript(text: string) => voidutteranceEnd() => voidresponse(text: string) => voidresponseComplete() => voidasrError(message: string) => voidblendshapeUpdate(blendshapes: number[]) => voidplaybackStarted() => voidplaybackEnded() => voidmodelStatus(status: { model_loaded?: boolean }) => voidcreditsExhausted() => voidsessionExpired(reason: string) => voidEXAMPLE
client.on('transcript', (text) => {console.log('User said:', text);});client.on('response', (text) => {console.log('AI:', text);});client.on('blendshapeUpdate', (blendshapes) => {// blendshapes is number[52] — ARKit blendshape weights (0-1)// Use with VRMAvatar for lip sync});client.on('error', (error) => {console.error('AStack error:', error.message);});
AUDIO PLAYER
The AudioPlayer handles audio playback and blendshape synchronization. It is used internally by the client but can also be used standalone.
import { AudioPlayer } from '@aether-stack-dev/client-sdk';const player = new AudioPlayer(24000); // sample rateplayer.enqueue({ audio: arrayBuffer, blendshapes: frames });player.clearQueue();await player.destroy();// Events: blendshapeUpdate, playbackStarted, playbackEnded, queueEmpty, error
REACT HOOKS
Import from @aether-stack-dev/client-sdk/react.
useAStackCSR
const {client, // AStackCSRClient | nullisConnected, // booleancallStatus, // 'idle' | 'starting' | 'active' | 'stopping' | 'error'blendshapes, // number[] (52 values)transcript, // string — latest user speechresponse, // string — latest AI responseerror, // Error | nullcharacterConfig, // CharacterConfig — spread on VRMAvatarconnect, // () => Promise<void>disconnect, // () => voidstartCall, // () => Promise<void>stopCall, // () => voidsendText, // (message: string) => void} = useAStackCSR({workerUrl: workerUrl, // Dynamic per-session URL from serversessionToken: sessionToken,autoConnect: false, // auto-connect on mount (default: false)});
AVATAR COMPONENTS
import { VRMAvatar } from '@aether-stack-dev/client-sdk/react';// 3D VRM avatar with Three.js<VRMAvatarblendshapes={blendshapes} // number[52] from useAStackCSRwidth={400} // pixels (default: 400)height={400} // pixels (default: 400)modelUrl="https://xwqtyabmavpacejuypyp.supabase.co/storage/v1/object/public/vrm-templates/5705015963733407866.vrm"/>
ERROR HANDLING
import { AStackError, ErrorCodes } from '@aether-stack-dev/client-sdk';client.on('error', (error) => {if (error instanceof AStackError) {switch (error.code) {case ErrorCodes.CONNECTION_LOST:// Reconnect logicbreak;case ErrorCodes.MEDIA_ACCESS_DENIED:// Prompt user for mic/camera permissionsbreak;}}});
ERROR CODES
INVALID_API_KEYAPI key is invalid or missingINVALID_TOKENSession token is invalidAUTHENTICATION_FAILEDAuthentication rejected by serverSESSION_EXPIREDSession has expiredCONNECTION_LOSTWebSocket connection droppedNETWORK_ERRORNetwork connectivity issueMEDIA_ACCESS_DENIEDMic/camera permission deniedAUDIO_ERRORAudio processing failureRATE_LIMIT_EXCEEDEDToo many requestsINSUFFICIENT_CREDITSAccount has no remaining credits