@anam-ai/js-sdk
Version:
Client side JavaScript SDK for Anam AI
290 lines • 13 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { ClientError, ErrorCode } from './lib/ClientError';
import { CoreApiRestClient, InternalEventEmitter, MessageHistoryClient, PublicEventEmitter, StreamingClient, } from './modules';
import { AnamEvent, } from './types';
export default class AnamClient {
constructor(sessionToken, personaConfig, options) {
this.inputAudioState = { isMuted: false };
this.sessionId = null;
this.streamingClient = null;
this._isStreaming = false;
const configError = this.validateClientConfig(sessionToken, personaConfig, options);
if (configError) {
throw new Error(configError);
}
this.personaConfig = personaConfig;
this.clientOptions = options;
this.publicEventEmitter = new PublicEventEmitter();
this.internalEventEmitter = new InternalEventEmitter();
this.apiClient = new CoreApiRestClient(sessionToken, options === null || options === void 0 ? void 0 : options.apiKey, options === null || options === void 0 ? void 0 : options.api);
this.messageHistoryClient = new MessageHistoryClient(this.publicEventEmitter, this.internalEventEmitter);
}
decodeJwt(token) {
try {
const base64Payload = token.split('.')[1];
const payload = JSON.parse(atob(base64Payload));
return payload;
}
catch (error) {
throw new Error('Invalid session token format');
}
}
validateClientConfig(sessionToken, personaConfig, options) {
var _a;
// Validate authentication configuration
if (!sessionToken && !(options === null || options === void 0 ? void 0 : options.apiKey)) {
return 'Either sessionToken or apiKey must be provided';
}
if ((options === null || options === void 0 ? void 0 : options.apiKey) && sessionToken) {
return 'Only one of sessionToken or apiKey should be used';
}
// Validate persona configuration based on session token
if (sessionToken) {
const decodedToken = this.decodeJwt(sessionToken);
const tokenType = (_a = decodedToken.type) === null || _a === void 0 ? void 0 : _a.toLowerCase();
if (tokenType === 'legacy') {
if (!personaConfig || !('personaId' in personaConfig)) {
return 'Both session token and client are missing a persona configuration. Please provide a persona ID of a saved persona in the personaConfig parameter.';
}
}
else if (tokenType === 'ephemeral' || tokenType === 'stateful') {
if (personaConfig) {
return 'This session token already contains a persona configuration. Please remove the personaConfig parameter.';
}
}
}
else {
// No session token (using apiKey)
if (!personaConfig) {
return 'Missing persona config. Persona configuration must be provided when using apiKey';
}
}
// Validate voice detection configuration
if (options === null || options === void 0 ? void 0 : options.voiceDetection) {
// End of speech sensitivity must be a number between 0 and 1
if (options.voiceDetection.endOfSpeechSensitivity !== undefined) {
if (typeof options.voiceDetection.endOfSpeechSensitivity !== 'number') {
return 'End of speech sensitivity must be a number';
}
if (options.voiceDetection.endOfSpeechSensitivity < 0 ||
options.voiceDetection.endOfSpeechSensitivity > 1) {
return 'End of speech sensitivity must be between 0 and 1';
}
}
}
return undefined;
}
buildStartSessionOptionsForClient() {
var _a;
const sessionOptions = {};
if ((_a = this.clientOptions) === null || _a === void 0 ? void 0 : _a.voiceDetection) {
sessionOptions.voiceDetection = this.clientOptions.voiceDetection;
}
// return undefined if no options are set
if (Object.keys(sessionOptions).length === 0) {
return undefined;
}
return sessionOptions;
}
startSession(userProvidedAudioStream) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
const config = this.personaConfig;
// build session options from client options
const sessionOptions = this.buildStartSessionOptionsForClient();
// start a new session
const response = yield this.apiClient.startSession(config, sessionOptions);
const { sessionId, clientConfig, engineHost, engineProtocol, signallingEndpoint, } = response;
const { heartbeatIntervalSeconds, maxWsReconnectionAttempts, iceServers } = clientConfig;
try {
this.streamingClient = new StreamingClient(sessionId, {
engine: {
baseUrl: `${engineProtocol}://${engineHost}`,
},
signalling: {
heartbeatIntervalSeconds,
maxWsReconnectionAttempts,
url: {
baseUrl: engineHost,
protocol: engineProtocol,
signallingPath: signallingEndpoint,
},
},
iceServers,
inputAudio: {
inputAudioState: this.inputAudioState,
userProvidedMediaStream: userProvidedAudioStream,
audioDeviceId: (_a = this.clientOptions) === null || _a === void 0 ? void 0 : _a.audioDeviceId,
},
}, this.publicEventEmitter, this.internalEventEmitter);
}
catch (error) {
throw new ClientError('Failed to initialize streaming client', ErrorCode.CLIENT_ERROR_CODE_SERVER_ERROR, 500, { cause: error instanceof Error ? error.message : String(error) });
}
this.sessionId = sessionId;
return sessionId;
});
}
startSessionIfNeeded(userProvidedMediaStream) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.sessionId || !this.streamingClient) {
yield this.startSession(userProvidedMediaStream);
if (!this.sessionId || !this.streamingClient) {
throw new ClientError('Session ID or streaming client is not available after starting session', ErrorCode.CLIENT_ERROR_CODE_SERVER_ERROR, 500, {
cause: 'Failed to initialize session properly',
});
}
}
});
}
stream(userProvidedAudioStream) {
return __awaiter(this, void 0, void 0, function* () {
yield this.startSessionIfNeeded(userProvidedAudioStream);
if (this._isStreaming) {
throw new Error('Already streaming');
}
this._isStreaming = true;
return new Promise((resolve) => {
var _a;
// set stream callbacks to capture the stream
const streams = [];
let videoReceived = false;
let audioReceived = false;
this.publicEventEmitter.addListener(AnamEvent.VIDEO_STREAM_STARTED, (videoStream) => {
streams.push(videoStream);
videoReceived = true;
if (audioReceived) {
resolve(streams);
}
});
this.publicEventEmitter.addListener(AnamEvent.AUDIO_STREAM_STARTED, (audioStream) => {
streams.push(audioStream);
audioReceived = true;
if (videoReceived) {
resolve(streams);
}
});
// start streaming
(_a = this.streamingClient) === null || _a === void 0 ? void 0 : _a.startConnection();
});
});
}
streamToVideoAndAudioElements(videoElementId, audioElementId, userProvidedMediaStream) {
return __awaiter(this, void 0, void 0, function* () {
try {
yield this.startSessionIfNeeded(userProvidedMediaStream);
}
catch (error) {
if (error instanceof ClientError) {
throw error;
}
throw new ClientError('Failed to start session', ErrorCode.CLIENT_ERROR_CODE_SERVER_ERROR, 500, {
cause: error instanceof Error ? error.message : String(error),
});
}
if (this._isStreaming) {
throw new Error('Already streaming');
}
this._isStreaming = true;
if (!this.streamingClient) {
throw new Error('Failed to stream: streaming client is not available');
}
this.streamingClient.setMediaStreamTargetsById(videoElementId, audioElementId);
this.streamingClient.startConnection();
});
}
talk(content) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.streamingClient) {
throw new Error('Failed to send talk command: session is not started. Have you called startSession?');
}
if (!this._isStreaming) {
throw new Error('Failed to send talk command: not currently streaming. Have you called stream?');
}
yield this.streamingClient.sendTalkCommand(content);
return;
});
}
sendDataMessage(message) {
if (this.streamingClient) {
this.streamingClient.sendDataMessage(message);
}
else {
throw new Error('Failed to send message: session is not started.');
}
}
stopStreaming() {
return __awaiter(this, void 0, void 0, function* () {
if (this.streamingClient) {
this.streamingClient.stopConnection();
this.streamingClient = null;
this.sessionId = null;
this._isStreaming = false;
}
});
}
isStreaming() {
return this._isStreaming;
}
setPersonaConfig(personaConfig) {
this.personaConfig = personaConfig;
}
getPersonaConfig() {
return this.personaConfig;
}
getInputAudioState() {
// if streaming client is available, make sure our state is up to date
if (this.streamingClient) {
this.inputAudioState = this.streamingClient.getInputAudioState();
}
return this.inputAudioState;
}
muteInputAudio() {
if (this.streamingClient) {
this.inputAudioState = this.streamingClient.muteInputAudio();
}
else {
this.inputAudioState = Object.assign(Object.assign({}, this.inputAudioState), { isMuted: true });
}
return this.inputAudioState;
}
unmuteInputAudio() {
if (this.streamingClient) {
this.inputAudioState = this.streamingClient.unmuteInputAudio();
}
else {
this.inputAudioState = Object.assign(Object.assign({}, this.inputAudioState), { isMuted: false });
}
return this.inputAudioState;
}
createTalkMessageStream(correlationId) {
if (!this.streamingClient) {
throw new Error('Failed to start talk message stream: session is not started.');
}
if (correlationId && correlationId.trim() === '') {
throw new Error('Failed to start talk message stream: correlationId is empty');
}
return this.streamingClient.startTalkMessageStream(correlationId);
}
/**
* Event handling
*/
addListener(event, callback) {
this.publicEventEmitter.addListener(event, callback);
}
removeListener(event, callback) {
this.publicEventEmitter.removeListener(event, callback);
}
getActiveSessionId() {
return this.sessionId;
}
}
//# sourceMappingURL=AnamClient.js.map