@hamsa-ai/voice-agents-sdk 0.1.1 → 0.2.1
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/README.md +6 -6
- package/dist/index.cjs.js +2 -0
- package/dist/index.esm.js +2 -0
- package/dist/index.esm.js.LICENSE.txt +8 -0
- package/dist/index.umd.js +2 -0
- package/dist/index.umd.js.LICENSE.txt +8 -0
- package/package.json +23 -5
- package/types/classes/audio_player.d.ts +57 -8
- package/types/classes/audio_recorder.d.ts +29 -3
- package/types/classes/websocket_manager.d.ts +71 -15
- package/types/main.d.ts +29 -6
- package/dist/index.js +0 -2
- package/src/classes/audio-player-processor.worklet.js +0 -69
- package/src/classes/audio-processor.worklet.js +0 -54
- package/src/classes/audio_player.js +0 -78
- package/src/classes/audio_recorder.js +0 -99
- package/src/classes/websocket_manager.js +0 -165
- package/src/main.js +0 -111
- package/tsconfig.json +0 -14
- package/types/classes/audio-player-processor.d.ts +0 -1
- package/types/classes/audio-processor.d.ts +0 -1
- package/types/classes/event_listeners.d.ts +0 -3
- package/types/classes/facny_button.d.ts +0 -17
- package/webpack.config.js +0 -44
- /package/dist/{index.js.LICENSE.txt → index.cjs.js.LICENSE.txt} +0 -0
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import AudioPlayer from './audio_player'
|
|
2
|
-
import AudioRecorder from './audio_recorder'
|
|
3
|
-
|
|
4
|
-
export default class WebSocketManager {
|
|
5
|
-
constructor(
|
|
6
|
-
url,
|
|
7
|
-
conversationId,
|
|
8
|
-
onError,
|
|
9
|
-
onStart,
|
|
10
|
-
onTransciprtionRecieved,
|
|
11
|
-
onAnswerRecieved,
|
|
12
|
-
onSpeaking,
|
|
13
|
-
onListening,
|
|
14
|
-
onClosed,
|
|
15
|
-
voiceEnablement,
|
|
16
|
-
tools,
|
|
17
|
-
apiKey
|
|
18
|
-
) {
|
|
19
|
-
this.url = `${url}/${conversationId}?api_key=${apiKey}`;
|
|
20
|
-
this.ws = null;
|
|
21
|
-
this.isConnected = false;
|
|
22
|
-
this.audioPlayer = null;
|
|
23
|
-
this.audioRecorder = null;
|
|
24
|
-
this.last_transcription_date = new Date();
|
|
25
|
-
this.last_voice_byte_date = new Date();
|
|
26
|
-
this.is_media = false;
|
|
27
|
-
this.onErrorCB = onError;
|
|
28
|
-
this.onStartCB = onStart;
|
|
29
|
-
this.onTransciprtionRecievedCB = onTransciprtionRecieved;
|
|
30
|
-
this.onAnswerRecievedCB = onAnswerRecieved;
|
|
31
|
-
this.onSpeakingCB = onSpeaking;
|
|
32
|
-
this.onListeningCB = onListening;
|
|
33
|
-
this.onClosedCB = onClosed;
|
|
34
|
-
this.voiceEnablement = voiceEnablement;
|
|
35
|
-
this.tools = tools;
|
|
36
|
-
this.apiKey = apiKey;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
startCall() {
|
|
40
|
-
try {
|
|
41
|
-
if (!this.ws) {
|
|
42
|
-
this.ws = new WebSocket(this.url);
|
|
43
|
-
this.ws.onopen = this.onOpen.bind(this);
|
|
44
|
-
this.ws.onmessage = this.onMessage.bind(this);
|
|
45
|
-
this.ws.onclose = this.onClose.bind(this);
|
|
46
|
-
this.ws.onerror = this.onError.bind(this);
|
|
47
|
-
this.audioPlayer = new AudioPlayer(this.ws, this.onSpeakingCB, this.onListeningCB)
|
|
48
|
-
this.audioRecorder = new AudioRecorder()
|
|
49
|
-
}
|
|
50
|
-
}catch(e) {
|
|
51
|
-
console.log(e)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
onOpen() {
|
|
56
|
-
this.ws.send(JSON.stringify({ event: 'start', streamSid: 'WEBSDK' }));
|
|
57
|
-
this.isConnected = true;
|
|
58
|
-
this.audioRecorder.startStreaming(this.ws);
|
|
59
|
-
if (this.onStartCB) this.onStartCB()
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
onMessage(event) {
|
|
63
|
-
const message = JSON.parse(event.data);
|
|
64
|
-
switch (message.event) {
|
|
65
|
-
case 'media':
|
|
66
|
-
if (message.media) {
|
|
67
|
-
this.audioPlayer.enqueueAudio(message.media.payload);
|
|
68
|
-
}
|
|
69
|
-
break;
|
|
70
|
-
case 'clear':
|
|
71
|
-
this.audioPlayer.stopAndClear();
|
|
72
|
-
break;
|
|
73
|
-
case 'mark':
|
|
74
|
-
this.audioPlayer.addMark(message.mark.name);
|
|
75
|
-
break;
|
|
76
|
-
case 'transcription':
|
|
77
|
-
if (this.onTransciprtionRecievedCB) this.onTransciprtionRecievedCB(message.content)
|
|
78
|
-
break;
|
|
79
|
-
case 'answer':
|
|
80
|
-
if (this.onAnswerRecievedCB) this.onAnswerRecievedCB(message.content)
|
|
81
|
-
break;
|
|
82
|
-
case 'tools':
|
|
83
|
-
const tools_response = this.run_tools(message.content)
|
|
84
|
-
this.ws.send(JSON.stringify({ event: 'tools_response', tools_response: tools_response, streamSid: 'WEBSDK' }));
|
|
85
|
-
break;
|
|
86
|
-
default:
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
onClose(event) {
|
|
92
|
-
if (this.onClosedCB) this.onClosedCB()
|
|
93
|
-
this.audioPlayer.stopAndClear();
|
|
94
|
-
this.audioRecorder.stop();
|
|
95
|
-
this.isConnected = false;
|
|
96
|
-
this.ws = null
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
onError(error) {
|
|
100
|
-
if (this.onErrorCB) this.onErrorCB(error)
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
endCall() {
|
|
105
|
-
if (this.ws) {
|
|
106
|
-
this.audioPlayer.stopAndClear();
|
|
107
|
-
this.ws.send(JSON.stringify({ event: 'stop' }));
|
|
108
|
-
this.audioRecorder.stop();
|
|
109
|
-
this.#closeWebSocket()
|
|
110
|
-
if (this.onClosedCB) this.onClosedCB()
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
pauseCall() {
|
|
115
|
-
this.audioPlayer.pause()
|
|
116
|
-
this.audioRecorder.pause()
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
resumeCall() {
|
|
120
|
-
this.audioPlayer.resume()
|
|
121
|
-
this.audioRecorder.resume()
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
run_tools(tools_array) {
|
|
125
|
-
const results = [];
|
|
126
|
-
tools_array.forEach(item => {
|
|
127
|
-
if (item.type === 'function') {
|
|
128
|
-
const selected_function = this.#findFunctionByName(item.function.name)
|
|
129
|
-
const functionName = item.function.name;
|
|
130
|
-
const functionArgs = JSON.parse(item.function.arguments);
|
|
131
|
-
if (selected_function && typeof selected_function["fn"] === 'function') {
|
|
132
|
-
const response = selected_function["fn"](...Object.values(functionArgs));
|
|
133
|
-
results.push({
|
|
134
|
-
id: item.id,
|
|
135
|
-
function: {
|
|
136
|
-
name: functionName,
|
|
137
|
-
response: response
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
} else {
|
|
141
|
-
results.push({
|
|
142
|
-
id: item.id,
|
|
143
|
-
function: {
|
|
144
|
-
name: functionName,
|
|
145
|
-
response: "Error could not find the function"
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
console.log(`Function ${functionName} is not defined`);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
return results;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
#findFunctionByName(functionName) {
|
|
157
|
-
return this.tools.find(item => item.function_name === functionName) || null;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
#closeWebSocket() {
|
|
161
|
-
if (this.ws.readyState === WebSocket.OPEN) {
|
|
162
|
-
this.ws.close(1000, 'Normal Closure');
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
package/src/main.js
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import WebSocketManager from './classes/websocket_manager';
|
|
2
|
-
import { EventEmitter } from 'events';
|
|
3
|
-
|
|
4
|
-
export class HamsaVoiceAgent extends EventEmitter {
|
|
5
|
-
constructor(apiKey) {
|
|
6
|
-
super();
|
|
7
|
-
this.webSocketManager = null;
|
|
8
|
-
this.apiKey = apiKey;
|
|
9
|
-
this.API_URL = "https://api.tryhamsa.com"
|
|
10
|
-
this.WS_URL = "wss://bots.tryhamsa.com/stream"
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
async start({
|
|
14
|
-
agentId = null,
|
|
15
|
-
params = {},
|
|
16
|
-
voiceEnablement = false,
|
|
17
|
-
tools = []
|
|
18
|
-
}) {
|
|
19
|
-
try {
|
|
20
|
-
const conversationdId = await this.#init_conversation(agentId, params, voiceEnablement, tools);
|
|
21
|
-
this.webSocketManager = new WebSocketManager(
|
|
22
|
-
this.WS_URL,
|
|
23
|
-
conversationdId,
|
|
24
|
-
(error) => this.emit('error', error),
|
|
25
|
-
() => this.emit('start'),
|
|
26
|
-
(transcription) => this.emit('transcriptionReceived', transcription),
|
|
27
|
-
(answer) => this.emit('answerReceived', answer),
|
|
28
|
-
() => this.emit('speaking'),
|
|
29
|
-
() => this.emit('listening'),
|
|
30
|
-
() => this.emit('closed'),
|
|
31
|
-
voiceEnablement,
|
|
32
|
-
tools,
|
|
33
|
-
this.apiKey
|
|
34
|
-
);
|
|
35
|
-
this.webSocketManager.startCall();
|
|
36
|
-
this.emit('callStarted');
|
|
37
|
-
} catch (e) {
|
|
38
|
-
this.emit('error', new Error("Error in starting the call! Make sure you initialized the client with init()."));
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
end() {
|
|
43
|
-
try {
|
|
44
|
-
this.webSocketManager.endCall();
|
|
45
|
-
this.emit('callEnded');
|
|
46
|
-
} catch (e) {
|
|
47
|
-
this.emit('error', new Error("Error in ending the call! Make sure you initialized the client with init()."));
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
pause() {
|
|
52
|
-
this.webSocketManager.pauseCall();
|
|
53
|
-
this.emit('callPaused');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
resume() {
|
|
57
|
-
this.webSocketManager.resumeCall();
|
|
58
|
-
this.emit('callResumed');
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async #init_conversation(voiceAgentId, params, voiceEnablement, tools) {
|
|
62
|
-
const headers = {
|
|
63
|
-
"Authorization": `Token ${this.apiKey}`,
|
|
64
|
-
"Content-Type": "application/json"
|
|
65
|
-
}
|
|
66
|
-
const llmtools = (voiceEnablement && tools) ? this.#convertToolsToLLMTools(tools) : []
|
|
67
|
-
const body = {
|
|
68
|
-
voiceAgentId,
|
|
69
|
-
params,
|
|
70
|
-
voiceEnablement,
|
|
71
|
-
tools: llmtools
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const requestOptions = {
|
|
75
|
-
method: "POST",
|
|
76
|
-
headers: headers,
|
|
77
|
-
body: JSON.stringify(body),
|
|
78
|
-
redirect: "follow"
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
const response = await fetch(`${this.API_URL}/v1/voice-agents/conversation-init`, requestOptions);
|
|
83
|
-
const result = await response.json();
|
|
84
|
-
return result["data"]["jobId"]
|
|
85
|
-
} catch (error) {
|
|
86
|
-
this.emit('error', new Error("Error in initializing the call. Please double-check your API_KEY and ensure you have sufficient funds in your balance."));
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
#convertToolsToLLMTools(tools) {
|
|
91
|
-
return tools.map(item => ({
|
|
92
|
-
type: "function",
|
|
93
|
-
function: {
|
|
94
|
-
name: item.function_name,
|
|
95
|
-
description: item.description,
|
|
96
|
-
parameters: {
|
|
97
|
-
type: "object",
|
|
98
|
-
properties: item.parameters?.reduce((acc, param) => {
|
|
99
|
-
acc[param.name] = {
|
|
100
|
-
type: param.type,
|
|
101
|
-
description: param.description
|
|
102
|
-
};
|
|
103
|
-
return acc;
|
|
104
|
-
}, {}) || {},
|
|
105
|
-
required: item.required || []
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}));
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
package/tsconfig.json
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"declaration": true,
|
|
4
|
-
"emitDeclarationOnly": true,
|
|
5
|
-
"outDir": "./types",
|
|
6
|
-
"target": "ES6",
|
|
7
|
-
"module": "ES6",
|
|
8
|
-
"allowJs": true,
|
|
9
|
-
"moduleResolution": "node", // Try "nodenext" if "node" doesn't work
|
|
10
|
-
"esModuleInterop": true
|
|
11
|
-
},
|
|
12
|
-
"include": ["src/**/*.js"],
|
|
13
|
-
"exclude": ["types"]
|
|
14
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
declare const audioProcessorURL: "\nclass AudioPlayerProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n this.audioData = [];\n this.isPaused = false;\n this.marks = [];\n this.isDone = false;\n\n this.port.onmessage = (event) => {\n if (event.data.type === 'enqueue') {\n this.audioData.push(...event.data.audioSamples);\n this.isPaused = false;\n this.isDone = false;\n } else if (event.data.type === 'pause') {\n this.isPaused = true;\n } else if (event.data.type === 'resume') {\n this.isPaused = false;\n } else if (event.data.type === 'addMark') {\n this.marks.push(event.data.markName);\n } else if (event.data.type === 'clear') {\n this.clearAllData();\n }\n };\n }\n\n clearAllData() {\n this.audioData = []; // Clear the audio data buffer\n this.marks = []; // Clear any pending marks\n this.isPaused = true; // Optionally, pause processing to ensure no data is played\n }\n\n process(inputs, outputs) {\n const output = outputs[0];\n\n if (this.isPaused) {\n for (let channel = 0; channel < output.length; channel++) {\n output[channel].fill(0); // Output silence if paused or cleared\n }\n return true;\n }\n\n for (let channel = 0; channel < output.length; channel++) {\n const outputData = output[channel];\n const inputData = this.audioData.splice(0, outputData.length);\n\n if (inputData.length > 0) {\n outputData.set(inputData);\n } else {\n outputData.fill(0); // Output silence when no data is available\n }\n }\n if (this.audioData.length === 0 && !this.isDone) {\n this.isDone = true; \n this.port.postMessage({ type: 'finished' });\n }\n\n // Process marks if all audio data has been played\n if (this.marks.length > 0 && this.audioData.length === 0) {\n const mark_name = this.marks.shift();\n this.port.postMessage({ type: 'mark', markName: mark_name });\n }\n\n\n\n return true; // Keep the processor active\n }\n}\n\nregisterProcessor('audio-player-processor', AudioPlayerProcessor);\n";
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
declare const audioRecorderProcessor: "\nclass AudioProcessor extends AudioWorkletProcessor {\n encodeBase64(bytes) {\n const base64abc = [\n 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',\n 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',\n 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',\n 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'\n ];\n\n let result = '';\n let i;\n const l = bytes.length;\n for (i = 2; i < l; i += 3) {\n result += base64abc[bytes[i - 2] >> 2];\n result += base64abc[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];\n result += base64abc[((bytes[i - 1] & 0x0f) << 2) | (bytes[i] >> 6)];\n result += base64abc[bytes[i] & 0x3f];\n }\n if (i === l + 1) { // 1 octet yet to write\n result += base64abc[bytes[i - 2] >> 2];\n result += base64abc[(bytes[i - 2] & 0x03) << 4];\n result += '==';\n }\n if (i === l) { // 2 octets yet to write\n result += base64abc[bytes[i - 2] >> 2];\n result += base64abc[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];\n result += base64abc[(bytes[i - 1] & 0x0f) << 2];\n result += '=';\n }\n return result;\n }\n\n process(inputs, outputs, parameters) {\n const input = inputs[0];\n if (input && input[0]) {\n const inputData = input[0];\n const rawAudioData = new Float32Array(inputData.length);\n rawAudioData.set(inputData);\n\n // Convert the audio data to a Uint8Array for base64 encoding\n const uint8Array = new Uint8Array(rawAudioData.buffer);\n\n // Use the custom base64 encoding function\n const base64String = this.encodeBase64(uint8Array);\n\n // Send the base64 string to the main thread via the port\n this.port.postMessage({ event: 'media', streamSid: 'WEBSDK', media: { payload: base64String } });\n }\n\n return true;\n }\n}\n\nregisterProcessor('audio-processor', AudioProcessor);\n";
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export default class FancyButton {
|
|
2
|
-
constructor(voiceEnablement: any);
|
|
3
|
-
isCallStarted: boolean;
|
|
4
|
-
iframe: HTMLIFrameElement;
|
|
5
|
-
isIframeLoaded: boolean;
|
|
6
|
-
voiceEnablement: any;
|
|
7
|
-
createFloatingButton(WSManager: any): void;
|
|
8
|
-
WSManager: any;
|
|
9
|
-
toggleCall(): void;
|
|
10
|
-
startCall(): void;
|
|
11
|
-
endCall(): void;
|
|
12
|
-
updateButtonAppearance(): void;
|
|
13
|
-
startWaveAnimation(): void;
|
|
14
|
-
stopWaveAnimation(): void;
|
|
15
|
-
createIframe(callback: any): void;
|
|
16
|
-
removeIframe(): void;
|
|
17
|
-
}
|
package/webpack.config.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
|
-
|
|
3
|
-
module.exports = {
|
|
4
|
-
entry: './src/main.js',
|
|
5
|
-
output: {
|
|
6
|
-
path: path.resolve(__dirname, 'dist'),
|
|
7
|
-
filename: 'index.js',
|
|
8
|
-
library: {
|
|
9
|
-
name: 'Hamsa Voice-Agents SDK',
|
|
10
|
-
type: 'umd', // UMD ensures compatibility across environments
|
|
11
|
-
},
|
|
12
|
-
globalObject: 'this',
|
|
13
|
-
umdNamedDefine: true
|
|
14
|
-
},
|
|
15
|
-
target: 'web', // 'web' should cover most browser-based environments
|
|
16
|
-
module: {
|
|
17
|
-
rules: [
|
|
18
|
-
{
|
|
19
|
-
test: /\.worklet\.js/,
|
|
20
|
-
loader: "audio-worklet-loader",
|
|
21
|
-
options: {
|
|
22
|
-
inline: "no-fallback",
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
test: /\.js$/,
|
|
27
|
-
exclude: /node_modules/,
|
|
28
|
-
use: 'babel-loader',
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
test: /\.css$/,
|
|
32
|
-
use: ['style-loader', 'css-loader'],
|
|
33
|
-
},
|
|
34
|
-
]
|
|
35
|
-
},
|
|
36
|
-
resolve: {
|
|
37
|
-
extensions: ['.js'],
|
|
38
|
-
fallback: {
|
|
39
|
-
"events": require.resolve("events/")
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
mode: 'production',
|
|
43
|
-
externals: [], // Define any external dependencies that should not be bundled
|
|
44
|
-
};
|
|
File without changes
|