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
const client = new AStackCSRClient({workerUrl: 'wss://worker.astack.dev', // RequiredsessionToken: 'st_...', // Auth token from server SDKsessionId: 'sess_...', // Alternative: direct session IDsampleRate: 24000, // Audio sample rate (default: 24000)providers: { // AI provider selectionasr: 'self', // 'self' | 'deepinfra' (default: 'self')llm: 'self', // 'self' | 'deepinfra' (default: 'self')tts: 'self', // 'self' | 'deepinfra' (default: 'self')},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 SDKprovidersASR/LLM/TTS provider: 'self' or 'deepinfra'enableImageCaptureSend camera frames for vision processingMETHODS
connect(): Promise<void>Establish WebSocket connection and authenticatestartCall(): Promise<void>Initialize audio context, request mic/camera, start streamingstopCall(): 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 resourcesEVENTS
The client extends EventEmitter. Listen for events with client.on(event, callback).
connected() => voiddisconnected() => voiderror(error: Error) => voidcallStarted() => voidcallStopped() => voidtranscript(text: string) => voidresponse(text: string) => voidblendshapeUpdate(blendshapes: number[]) => voidplaybackStarted() => voidplaybackEnded() => voidmodelStatus(status: { model_loaded?: boolean }) => 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 or TalkingHeadAvatar 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 | nullconnect, // () => Promise<void>disconnect, // () => voidstartCall, // () => Promise<void>stopCall, // () => voidsendText, // (message: string) => void} = useAStackCSR({workerUrl: 'wss://worker.astack.dev',sessionToken: 'st_...',autoConnect: false, // auto-connect on mount (default: false)});
AVATAR COMPONENTS
import { VRMAvatar, TalkingHeadAvatar } 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="/models/avatar.vrm" // VRM/GLB model path/>// Ready Player Me avatar with TalkingHead<TalkingHeadAvatarblendshapes={blendshapes}width={400}height={400}avatarUrl="https://models.readyplayer.me/..."/>
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