@hamsa-ai/voice-agents-sdk 0.1.0
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/LICENSE +21 -0
- package/README.md +98 -0
- package/dist/index.js +2 -0
- package/dist/index.js.LICENSE.txt +8 -0
- package/package.json +46 -0
- package/src/classes/audio-player-processor.worklet.js +69 -0
- package/src/classes/audio-processor.worklet.js +54 -0
- package/src/classes/audio_player.js +78 -0
- package/src/classes/audio_recorder.js +99 -0
- package/src/classes/websocket_manager.js +165 -0
- package/src/main.js +111 -0
- package/tsconfig.json +14 -0
- package/types/classes/audio-player-processor.d.ts +1 -0
- package/types/classes/audio-player-processor.worklet.d.ts +8 -0
- package/types/classes/audio-processor.d.ts +1 -0
- package/types/classes/audio-processor.worklet.d.ts +4 -0
- package/types/classes/audio_player.d.ts +18 -0
- package/types/classes/audio_recorder.d.ts +11 -0
- package/types/classes/event_listeners.d.ts +3 -0
- package/types/classes/facny_button.d.ts +17 -0
- package/types/classes/websocket_manager.d.ts +33 -0
- package/types/main.d.ts +19 -0
- package/webpack.config.js +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Hamsa AI Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Hamsa Voice Agents Web SDK
|
|
2
|
+
|
|
3
|
+
Hamsa Voice Agents Web SDK is a JavaScript library for integrating voice agents from https://dashboard.tryhamsa.com. This SDK provides a seamless way to incorporate voice interactions into your web applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install the SDK via npm:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install hamsa-voice-agents
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
First, import the package in your code:
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
import { HamsaVoiceAgent } from 'hamsa-voice-agents';
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Initialize the SDK with your API key.
|
|
22
|
+
To obtain your first API key, visit https://dashboard.tryhamsa.com
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
const agent = new HamsaVoiceAgent(API_KEY);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Start a Conversation with an Existing Agent
|
|
29
|
+
|
|
30
|
+
Start a conversation with an existing agent by calling the "start" function. You can create and manage agents in our Dashboard or using our API (see: https://docs.tryhamsa.com):
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
agent.start({
|
|
34
|
+
agentId: YOUR_AGENT_ID,
|
|
35
|
+
params: {
|
|
36
|
+
param1: "NAME",
|
|
37
|
+
param2: "NAME2"
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
When creating an agent, you can add parameters to your pre-defined values. For example, you can set your Greeting Message to: "Hello {{name}}, how can I help you today?" and pass the "name" as a parameter to use the correct name of the user.
|
|
43
|
+
|
|
44
|
+
## Pause/Resume a Conversation
|
|
45
|
+
|
|
46
|
+
To pause the conversation, call the "pause" function. This will prevent the SDK from sending or receiving new data until you resume the conversation:
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
agent.pause();
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
To resume the conversation:
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
agent.resume();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## End a Conversation
|
|
59
|
+
|
|
60
|
+
To end a conversation, simply call the "end" function:
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
agent.end();
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Events
|
|
67
|
+
|
|
68
|
+
During the conversation, the SDK emits events to update your application about the conversation status.
|
|
69
|
+
|
|
70
|
+
### Conversation Status Events
|
|
71
|
+
|
|
72
|
+
```javascript
|
|
73
|
+
agent.on("callStarted", () => { console.log("Conversation has started!"); });
|
|
74
|
+
agent.on("callEnded", () => { console.log("Conversation has ended!"); });
|
|
75
|
+
agent.on("callPaused", () => { console.log("The conversation is paused"); });
|
|
76
|
+
agent.on("callResumed", () => { console.log("Conversation has resumed"); });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Agent Status Events
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
agent.on("speaking", () => { console.log("The agent is speaking"); });
|
|
83
|
+
agent.on("listening", () => { console.log("The agent is listening"); });
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Conversation Script Events
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
agent.on("transcriptionReceived", (text) => { console.log("User speech transcription received", text); });
|
|
90
|
+
agent.on("answerReceived", (text) => { console.log("Agent answer received", text); });
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Error Events
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
agent.on("closed", () => { console.log("Conversation was closed"); });
|
|
97
|
+
agent.on("error", (e) => { console.log("Error was received", e); });
|
|
98
|
+
```
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! For license information please see index.js.LICENSE.txt */
|
|
2
|
+
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("Hamsa Voice-Agents SDK",[],e):"object"==typeof exports?exports["Hamsa Voice-Agents SDK"]=e():t["Hamsa Voice-Agents SDK"]=e()}(this,(()=>(()=>{var t={870:(t,e,r)=>{t.exports=r(540)('(()=>{class s extends AudioWorkletProcessor{constructor(){super(),this.audioData=[],this.isPaused=!1,this.marks=[],this.isDone=!1,this.port.onmessage=s=>{"enqueue"===s.data.type?(this.audioData.push(...s.data.audioSamples),this.isPaused=!1,this.isDone=!1):"pause"===s.data.type?this.isPaused=!0:"resume"===s.data.type?this.isPaused=!1:"addMark"===s.data.type?this.marks.push(s.data.markName):"clear"===s.data.type&&this.clearAllData()}}clearAllData(){this.audioData=[],this.marks=[],this.isPaused=!0}process(s,t){const a=t[0];if(this.isPaused){for(let s=0;s<a.length;s++)a[s].fill(0);return!0}for(let s=0;s<a.length;s++){const t=a[s],e=this.audioData.splice(0,t.length);e.length>0?t.set(e):t.fill(0)}if(0!==this.audioData.length||this.isDone||(this.isDone=!0,this.port.postMessage({type:"finished"})),this.marks.length>0&&0===this.audioData.length){const s=this.marks.shift();this.port.postMessage({type:"mark",markName:s})}return!0}}registerProcessor("audio-player-processor",s)})();')},428:(t,e,r)=>{t.exports=r(540)('(()=>{class e extends AudioWorkletProcessor{encodeBase64(e){const s=["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9","+","/"];let o,r="";const t=e.length;for(o=2;o<t;o+=3)r+=s[e[o-2]>>2],r+=s[(3&e[o-2])<<4|e[o-1]>>4],r+=s[(15&e[o-1])<<2|e[o]>>6],r+=s[63&e[o]];return o===t+1&&(r+=s[e[o-2]>>2],r+=s[(3&e[o-2])<<4],r+="=="),o===t&&(r+=s[e[o-2]>>2],r+=s[(3&e[o-2])<<4|e[o-1]>>4],r+=s[(15&e[o-1])<<2],r+="="),r}process(e,s,o){const r=e[0];if(r&&r[0]){const e=r[0],s=new Float32Array(e.length);s.set(e);const o=new Uint8Array(s.buffer),t=this.encodeBase64(o);this.port.postMessage({event:"media",streamSid:"WEBSDK",media:{payload:t}})}return!0}}registerProcessor("audio-processor",e)})();')},540:t=>{t.exports=function(t){try{var e;try{(e=new(window.BlobBuilder||window.WebKitBlobBuilder||window.MozBlobBuilder||window.MSBlobBuilder)).append(t),e=e.getBlob("application/javascript; charset=utf-8")}catch(r){e=new Blob([t],{type:"application/javascript; charset=utf-8"})}return URL.createObjectURL(e)}catch(e){return"data:application/javascript,"+encodeURIComponent(t)}}},526:(t,e)=>{"use strict";e.byteLength=function(t){var e=a(t),r=e[0],n=e[1];return 3*(r+n)/4-n},e.toByteArray=function(t){var e,r,i=a(t),s=i[0],u=i[1],f=new o(function(t,e,r){return 3*(e+r)/4-r}(0,s,u)),h=0,c=u>0?s-4:s;for(r=0;r<c;r+=4)e=n[t.charCodeAt(r)]<<18|n[t.charCodeAt(r+1)]<<12|n[t.charCodeAt(r+2)]<<6|n[t.charCodeAt(r+3)],f[h++]=e>>16&255,f[h++]=e>>8&255,f[h++]=255&e;return 2===u&&(e=n[t.charCodeAt(r)]<<2|n[t.charCodeAt(r+1)]>>4,f[h++]=255&e),1===u&&(e=n[t.charCodeAt(r)]<<10|n[t.charCodeAt(r+1)]<<4|n[t.charCodeAt(r+2)]>>2,f[h++]=e>>8&255,f[h++]=255&e),f},e.fromByteArray=function(t){for(var e,n=t.length,o=n%3,i=[],s=16383,a=0,f=n-o;a<f;a+=s)i.push(u(t,a,a+s>f?f:a+s));return 1===o?(e=t[n-1],i.push(r[e>>2]+r[e<<4&63]+"==")):2===o&&(e=(t[n-2]<<8)+t[n-1],i.push(r[e>>10]+r[e>>4&63]+r[e<<2&63]+"=")),i.join("")};for(var r=[],n=[],o="undefined"!=typeof Uint8Array?Uint8Array:Array,i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s=0;s<64;++s)r[s]=i[s],n[i.charCodeAt(s)]=s;function a(t){var e=t.length;if(e%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var r=t.indexOf("=");return-1===r&&(r=e),[r,r===e?0:4-r%4]}function u(t,e,n){for(var o,i,s=[],a=e;a<n;a+=3)o=(t[a]<<16&16711680)+(t[a+1]<<8&65280)+(255&t[a+2]),s.push(r[(i=o)>>18&63]+r[i>>12&63]+r[i>>6&63]+r[63&i]);return s.join("")}n["-".charCodeAt(0)]=62,n["_".charCodeAt(0)]=63},287:(t,e,r)=>{"use strict";const n=r(526),o=r(251),i="function"==typeof Symbol&&"function"==typeof Symbol.for?Symbol.for("nodejs.util.inspect.custom"):null;e.hp=u,e.IS=50;const s=2147483647;function a(t){if(t>s)throw new RangeError('The value "'+t+'" is invalid for option "size"');const e=new Uint8Array(t);return Object.setPrototypeOf(e,u.prototype),e}function u(t,e,r){if("number"==typeof t){if("string"==typeof e)throw new TypeError('The "string" argument must be of type string. Received type number');return c(t)}return f(t,e,r)}function f(t,e,r){if("string"==typeof t)return function(t,e){if("string"==typeof e&&""!==e||(e="utf8"),!u.isEncoding(e))throw new TypeError("Unknown encoding: "+e);const r=0|y(t,e);let n=a(r);const o=n.write(t,e);return o!==r&&(n=n.slice(0,o)),n}(t,e);if(ArrayBuffer.isView(t))return function(t){if(Y(t,Uint8Array)){const e=new Uint8Array(t);return p(e.buffer,e.byteOffset,e.byteLength)}return l(t)}(t);if(null==t)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof t);if(Y(t,ArrayBuffer)||t&&Y(t.buffer,ArrayBuffer))return p(t,e,r);if("undefined"!=typeof SharedArrayBuffer&&(Y(t,SharedArrayBuffer)||t&&Y(t.buffer,SharedArrayBuffer)))return p(t,e,r);if("number"==typeof t)throw new TypeError('The "value" argument must not be of type number. Received type number');const n=t.valueOf&&t.valueOf();if(null!=n&&n!==t)return u.from(n,e,r);const o=function(t){if(u.isBuffer(t)){const e=0|d(t.length),r=a(e);return 0===r.length||t.copy(r,0,0,e),r}return void 0!==t.length?"number"!=typeof t.length||H(t.length)?a(0):l(t):"Buffer"===t.type&&Array.isArray(t.data)?l(t.data):void 0}(t);if(o)return o;if("undefined"!=typeof Symbol&&null!=Symbol.toPrimitive&&"function"==typeof t[Symbol.toPrimitive])return u.from(t[Symbol.toPrimitive]("string"),e,r);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof t)}function h(t){if("number"!=typeof t)throw new TypeError('"size" argument must be of type number');if(t<0)throw new RangeError('The value "'+t+'" is invalid for option "size"')}function c(t){return h(t),a(t<0?0:0|d(t))}function l(t){const e=t.length<0?0:0|d(t.length),r=a(e);for(let n=0;n<e;n+=1)r[n]=255&t[n];return r}function p(t,e,r){if(e<0||t.byteLength<e)throw new RangeError('"offset" is outside of buffer bounds');if(t.byteLength<e+(r||0))throw new RangeError('"length" is outside of buffer bounds');let n;return n=void 0===e&&void 0===r?new Uint8Array(t):void 0===r?new Uint8Array(t,e):new Uint8Array(t,e,r),Object.setPrototypeOf(n,u.prototype),n}function d(t){if(t>=s)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+s.toString(16)+" bytes");return 0|t}function y(t,e){if(u.isBuffer(t))return t.length;if(ArrayBuffer.isView(t)||Y(t,ArrayBuffer))return t.byteLength;if("string"!=typeof t)throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof t);const r=t.length,n=arguments.length>2&&!0===arguments[2];if(!n&&0===r)return 0;let o=!1;for(;;)switch(e){case"ascii":case"latin1":case"binary":return r;case"utf8":case"utf-8":return J(t).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*r;case"hex":return r>>>1;case"base64":return V(t).length;default:if(o)return n?-1:J(t).length;e=(""+e).toLowerCase(),o=!0}}function g(t,e,r){let n=!1;if((void 0===e||e<0)&&(e=0),e>this.length)return"";if((void 0===r||r>this.length)&&(r=this.length),r<=0)return"";if((r>>>=0)<=(e>>>=0))return"";for(t||(t="utf8");;)switch(t){case"hex":return U(this,e,r);case"utf8":case"utf-8":return L(this,e,r);case"ascii":return R(this,e,r);case"latin1":case"binary":return _(this,e,r);case"base64":return C(this,e,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return k(this,e,r);default:if(n)throw new TypeError("Unknown encoding: "+t);t=(t+"").toLowerCase(),n=!0}}function m(t,e,r){const n=t[e];t[e]=t[r],t[r]=n}function w(t,e,r,n,o){if(0===t.length)return-1;if("string"==typeof r?(n=r,r=0):r>2147483647?r=2147483647:r<-2147483648&&(r=-2147483648),H(r=+r)&&(r=o?0:t.length-1),r<0&&(r=t.length+r),r>=t.length){if(o)return-1;r=t.length-1}else if(r<0){if(!o)return-1;r=0}if("string"==typeof e&&(e=u.from(e,n)),u.isBuffer(e))return 0===e.length?-1:b(t,e,r,n,o);if("number"==typeof e)return e&=255,"function"==typeof Uint8Array.prototype.indexOf?o?Uint8Array.prototype.indexOf.call(t,e,r):Uint8Array.prototype.lastIndexOf.call(t,e,r):b(t,[e],r,n,o);throw new TypeError("val must be string, number or Buffer")}function b(t,e,r,n,o){let i,s=1,a=t.length,u=e.length;if(void 0!==n&&("ucs2"===(n=String(n).toLowerCase())||"ucs-2"===n||"utf16le"===n||"utf-16le"===n)){if(t.length<2||e.length<2)return-1;s=2,a/=2,u/=2,r/=2}function f(t,e){return 1===s?t[e]:t.readUInt16BE(e*s)}if(o){let n=-1;for(i=r;i<a;i++)if(f(t,i)===f(e,-1===n?0:i-n)){if(-1===n&&(n=i),i-n+1===u)return n*s}else-1!==n&&(i-=i-n),n=-1}else for(r+u>a&&(r=a-u),i=r;i>=0;i--){let r=!0;for(let n=0;n<u;n++)if(f(t,i+n)!==f(e,n)){r=!1;break}if(r)return i}return-1}function v(t,e,r,n){r=Number(r)||0;const o=t.length-r;n?(n=Number(n))>o&&(n=o):n=o;const i=e.length;let s;for(n>i/2&&(n=i/2),s=0;s<n;++s){const n=parseInt(e.substr(2*s,2),16);if(H(n))return s;t[r+s]=n}return s}function E(t,e,r,n){return G(J(e,t.length-r),t,r,n)}function B(t,e,r,n){return G(function(t){const e=[];for(let r=0;r<t.length;++r)e.push(255&t.charCodeAt(r));return e}(e),t,r,n)}function A(t,e,r,n){return G(V(e),t,r,n)}function S(t,e,r,n){return G(function(t,e){let r,n,o;const i=[];for(let s=0;s<t.length&&!((e-=2)<0);++s)r=t.charCodeAt(s),n=r>>8,o=r%256,i.push(o),i.push(n);return i}(e,t.length-r),t,r,n)}function C(t,e,r){return 0===e&&r===t.length?n.fromByteArray(t):n.fromByteArray(t.slice(e,r))}function L(t,e,r){r=Math.min(t.length,r);const n=[];let o=e;for(;o<r;){const e=t[o];let i=null,s=e>239?4:e>223?3:e>191?2:1;if(o+s<=r){let r,n,a,u;switch(s){case 1:e<128&&(i=e);break;case 2:r=t[o+1],128==(192&r)&&(u=(31&e)<<6|63&r,u>127&&(i=u));break;case 3:r=t[o+1],n=t[o+2],128==(192&r)&&128==(192&n)&&(u=(15&e)<<12|(63&r)<<6|63&n,u>2047&&(u<55296||u>57343)&&(i=u));break;case 4:r=t[o+1],n=t[o+2],a=t[o+3],128==(192&r)&&128==(192&n)&&128==(192&a)&&(u=(15&e)<<18|(63&r)<<12|(63&n)<<6|63&a,u>65535&&u<1114112&&(i=u))}}null===i?(i=65533,s=1):i>65535&&(i-=65536,n.push(i>>>10&1023|55296),i=56320|1023&i),n.push(i),o+=s}return function(t){const e=t.length;if(e<=I)return String.fromCharCode.apply(String,t);let r="",n=0;for(;n<e;)r+=String.fromCharCode.apply(String,t.slice(n,n+=I));return r}(n)}u.TYPED_ARRAY_SUPPORT=function(){try{const t=new Uint8Array(1),e={foo:function(){return 42}};return Object.setPrototypeOf(e,Uint8Array.prototype),Object.setPrototypeOf(t,e),42===t.foo()}catch(t){return!1}}(),u.TYPED_ARRAY_SUPPORT||"undefined"==typeof console||"function"!=typeof console.error||console.error("This browser lacks typed array (Uint8Array) support which is required by `buffer` v5.x. Use `buffer` v4.x if you require old browser support."),Object.defineProperty(u.prototype,"parent",{enumerable:!0,get:function(){if(u.isBuffer(this))return this.buffer}}),Object.defineProperty(u.prototype,"offset",{enumerable:!0,get:function(){if(u.isBuffer(this))return this.byteOffset}}),u.poolSize=8192,u.from=function(t,e,r){return f(t,e,r)},Object.setPrototypeOf(u.prototype,Uint8Array.prototype),Object.setPrototypeOf(u,Uint8Array),u.alloc=function(t,e,r){return function(t,e,r){return h(t),t<=0?a(t):void 0!==e?"string"==typeof r?a(t).fill(e,r):a(t).fill(e):a(t)}(t,e,r)},u.allocUnsafe=function(t){return c(t)},u.allocUnsafeSlow=function(t){return c(t)},u.isBuffer=function(t){return null!=t&&!0===t._isBuffer&&t!==u.prototype},u.compare=function(t,e){if(Y(t,Uint8Array)&&(t=u.from(t,t.offset,t.byteLength)),Y(e,Uint8Array)&&(e=u.from(e,e.offset,e.byteLength)),!u.isBuffer(t)||!u.isBuffer(e))throw new TypeError('The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array');if(t===e)return 0;let r=t.length,n=e.length;for(let o=0,i=Math.min(r,n);o<i;++o)if(t[o]!==e[o]){r=t[o],n=e[o];break}return r<n?-1:n<r?1:0},u.isEncoding=function(t){switch(String(t).toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"latin1":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return!0;default:return!1}},u.concat=function(t,e){if(!Array.isArray(t))throw new TypeError('"list" argument must be an Array of Buffers');if(0===t.length)return u.alloc(0);let r;if(void 0===e)for(e=0,r=0;r<t.length;++r)e+=t[r].length;const n=u.allocUnsafe(e);let o=0;for(r=0;r<t.length;++r){let e=t[r];if(Y(e,Uint8Array))o+e.length>n.length?(u.isBuffer(e)||(e=u.from(e)),e.copy(n,o)):Uint8Array.prototype.set.call(n,e,o);else{if(!u.isBuffer(e))throw new TypeError('"list" argument must be an Array of Buffers');e.copy(n,o)}o+=e.length}return n},u.byteLength=y,u.prototype._isBuffer=!0,u.prototype.swap16=function(){const t=this.length;if(t%2!=0)throw new RangeError("Buffer size must be a multiple of 16-bits");for(let e=0;e<t;e+=2)m(this,e,e+1);return this},u.prototype.swap32=function(){const t=this.length;if(t%4!=0)throw new RangeError("Buffer size must be a multiple of 32-bits");for(let e=0;e<t;e+=4)m(this,e,e+3),m(this,e+1,e+2);return this},u.prototype.swap64=function(){const t=this.length;if(t%8!=0)throw new RangeError("Buffer size must be a multiple of 64-bits");for(let e=0;e<t;e+=8)m(this,e,e+7),m(this,e+1,e+6),m(this,e+2,e+5),m(this,e+3,e+4);return this},u.prototype.toString=function(){const t=this.length;return 0===t?"":0===arguments.length?L(this,0,t):g.apply(this,arguments)},u.prototype.toLocaleString=u.prototype.toString,u.prototype.equals=function(t){if(!u.isBuffer(t))throw new TypeError("Argument must be a Buffer");return this===t||0===u.compare(this,t)},u.prototype.inspect=function(){let t="";const r=e.IS;return t=this.toString("hex",0,r).replace(/(.{2})/g,"$1 ").trim(),this.length>r&&(t+=" ... "),"<Buffer "+t+">"},i&&(u.prototype[i]=u.prototype.inspect),u.prototype.compare=function(t,e,r,n,o){if(Y(t,Uint8Array)&&(t=u.from(t,t.offset,t.byteLength)),!u.isBuffer(t))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof t);if(void 0===e&&(e=0),void 0===r&&(r=t?t.length:0),void 0===n&&(n=0),void 0===o&&(o=this.length),e<0||r>t.length||n<0||o>this.length)throw new RangeError("out of range index");if(n>=o&&e>=r)return 0;if(n>=o)return-1;if(e>=r)return 1;if(this===t)return 0;let i=(o>>>=0)-(n>>>=0),s=(r>>>=0)-(e>>>=0);const a=Math.min(i,s),f=this.slice(n,o),h=t.slice(e,r);for(let t=0;t<a;++t)if(f[t]!==h[t]){i=f[t],s=h[t];break}return i<s?-1:s<i?1:0},u.prototype.includes=function(t,e,r){return-1!==this.indexOf(t,e,r)},u.prototype.indexOf=function(t,e,r){return w(this,t,e,r,!0)},u.prototype.lastIndexOf=function(t,e,r){return w(this,t,e,r,!1)},u.prototype.write=function(t,e,r,n){if(void 0===e)n="utf8",r=this.length,e=0;else if(void 0===r&&"string"==typeof e)n=e,r=this.length,e=0;else{if(!isFinite(e))throw new Error("Buffer.write(string, encoding, offset[, length]) is no longer supported");e>>>=0,isFinite(r)?(r>>>=0,void 0===n&&(n="utf8")):(n=r,r=void 0)}const o=this.length-e;if((void 0===r||r>o)&&(r=o),t.length>0&&(r<0||e<0)||e>this.length)throw new RangeError("Attempt to write outside buffer bounds");n||(n="utf8");let i=!1;for(;;)switch(n){case"hex":return v(this,t,e,r);case"utf8":case"utf-8":return E(this,t,e,r);case"ascii":case"latin1":case"binary":return B(this,t,e,r);case"base64":return A(this,t,e,r);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return S(this,t,e,r);default:if(i)throw new TypeError("Unknown encoding: "+n);n=(""+n).toLowerCase(),i=!0}},u.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};const I=4096;function R(t,e,r){let n="";r=Math.min(t.length,r);for(let o=e;o<r;++o)n+=String.fromCharCode(127&t[o]);return n}function _(t,e,r){let n="";r=Math.min(t.length,r);for(let o=e;o<r;++o)n+=String.fromCharCode(t[o]);return n}function U(t,e,r){const n=t.length;(!e||e<0)&&(e=0),(!r||r<0||r>n)&&(r=n);let o="";for(let n=e;n<r;++n)o+=Z[t[n]];return o}function k(t,e,r){const n=t.slice(e,r);let o="";for(let t=0;t<n.length-1;t+=2)o+=String.fromCharCode(n[t]+256*n[t+1]);return o}function O(t,e,r){if(t%1!=0||t<0)throw new RangeError("offset is not uint");if(t+e>r)throw new RangeError("Trying to access beyond buffer length")}function P(t,e,r,n,o,i){if(!u.isBuffer(t))throw new TypeError('"buffer" argument must be a Buffer instance');if(e>o||e<i)throw new RangeError('"value" argument is out of bounds');if(r+n>t.length)throw new RangeError("Index out of range")}function x(t,e,r,n,o){F(e,n,o,t,r,7);let i=Number(e&BigInt(4294967295));t[r++]=i,i>>=8,t[r++]=i,i>>=8,t[r++]=i,i>>=8,t[r++]=i;let s=Number(e>>BigInt(32)&BigInt(4294967295));return t[r++]=s,s>>=8,t[r++]=s,s>>=8,t[r++]=s,s>>=8,t[r++]=s,r}function T(t,e,r,n,o){F(e,n,o,t,r,7);let i=Number(e&BigInt(4294967295));t[r+7]=i,i>>=8,t[r+6]=i,i>>=8,t[r+5]=i,i>>=8,t[r+4]=i;let s=Number(e>>BigInt(32)&BigInt(4294967295));return t[r+3]=s,s>>=8,t[r+2]=s,s>>=8,t[r+1]=s,s>>=8,t[r]=s,r+8}function M(t,e,r,n,o,i){if(r+n>t.length)throw new RangeError("Index out of range");if(r<0)throw new RangeError("Index out of range")}function N(t,e,r,n,i){return e=+e,r>>>=0,i||M(t,0,r,4),o.write(t,e,r,n,23,4),r+4}function j(t,e,r,n,i){return e=+e,r>>>=0,i||M(t,0,r,8),o.write(t,e,r,n,52,8),r+8}u.prototype.slice=function(t,e){const r=this.length;(t=~~t)<0?(t+=r)<0&&(t=0):t>r&&(t=r),(e=void 0===e?r:~~e)<0?(e+=r)<0&&(e=0):e>r&&(e=r),e<t&&(e=t);const n=this.subarray(t,e);return Object.setPrototypeOf(n,u.prototype),n},u.prototype.readUintLE=u.prototype.readUIntLE=function(t,e,r){t>>>=0,e>>>=0,r||O(t,e,this.length);let n=this[t],o=1,i=0;for(;++i<e&&(o*=256);)n+=this[t+i]*o;return n},u.prototype.readUintBE=u.prototype.readUIntBE=function(t,e,r){t>>>=0,e>>>=0,r||O(t,e,this.length);let n=this[t+--e],o=1;for(;e>0&&(o*=256);)n+=this[t+--e]*o;return n},u.prototype.readUint8=u.prototype.readUInt8=function(t,e){return t>>>=0,e||O(t,1,this.length),this[t]},u.prototype.readUint16LE=u.prototype.readUInt16LE=function(t,e){return t>>>=0,e||O(t,2,this.length),this[t]|this[t+1]<<8},u.prototype.readUint16BE=u.prototype.readUInt16BE=function(t,e){return t>>>=0,e||O(t,2,this.length),this[t]<<8|this[t+1]},u.prototype.readUint32LE=u.prototype.readUInt32LE=function(t,e){return t>>>=0,e||O(t,4,this.length),(this[t]|this[t+1]<<8|this[t+2]<<16)+16777216*this[t+3]},u.prototype.readUint32BE=u.prototype.readUInt32BE=function(t,e){return t>>>=0,e||O(t,4,this.length),16777216*this[t]+(this[t+1]<<16|this[t+2]<<8|this[t+3])},u.prototype.readBigUInt64LE=Q((function(t){K(t>>>=0,"offset");const e=this[t],r=this[t+7];void 0!==e&&void 0!==r||z(t,this.length-8);const n=e+256*this[++t]+65536*this[++t]+this[++t]*2**24,o=this[++t]+256*this[++t]+65536*this[++t]+r*2**24;return BigInt(n)+(BigInt(o)<<BigInt(32))})),u.prototype.readBigUInt64BE=Q((function(t){K(t>>>=0,"offset");const e=this[t],r=this[t+7];void 0!==e&&void 0!==r||z(t,this.length-8);const n=e*2**24+65536*this[++t]+256*this[++t]+this[++t],o=this[++t]*2**24+65536*this[++t]+256*this[++t]+r;return(BigInt(n)<<BigInt(32))+BigInt(o)})),u.prototype.readIntLE=function(t,e,r){t>>>=0,e>>>=0,r||O(t,e,this.length);let n=this[t],o=1,i=0;for(;++i<e&&(o*=256);)n+=this[t+i]*o;return o*=128,n>=o&&(n-=Math.pow(2,8*e)),n},u.prototype.readIntBE=function(t,e,r){t>>>=0,e>>>=0,r||O(t,e,this.length);let n=e,o=1,i=this[t+--n];for(;n>0&&(o*=256);)i+=this[t+--n]*o;return o*=128,i>=o&&(i-=Math.pow(2,8*e)),i},u.prototype.readInt8=function(t,e){return t>>>=0,e||O(t,1,this.length),128&this[t]?-1*(255-this[t]+1):this[t]},u.prototype.readInt16LE=function(t,e){t>>>=0,e||O(t,2,this.length);const r=this[t]|this[t+1]<<8;return 32768&r?4294901760|r:r},u.prototype.readInt16BE=function(t,e){t>>>=0,e||O(t,2,this.length);const r=this[t+1]|this[t]<<8;return 32768&r?4294901760|r:r},u.prototype.readInt32LE=function(t,e){return t>>>=0,e||O(t,4,this.length),this[t]|this[t+1]<<8|this[t+2]<<16|this[t+3]<<24},u.prototype.readInt32BE=function(t,e){return t>>>=0,e||O(t,4,this.length),this[t]<<24|this[t+1]<<16|this[t+2]<<8|this[t+3]},u.prototype.readBigInt64LE=Q((function(t){K(t>>>=0,"offset");const e=this[t],r=this[t+7];void 0!==e&&void 0!==r||z(t,this.length-8);const n=this[t+4]+256*this[t+5]+65536*this[t+6]+(r<<24);return(BigInt(n)<<BigInt(32))+BigInt(e+256*this[++t]+65536*this[++t]+this[++t]*2**24)})),u.prototype.readBigInt64BE=Q((function(t){K(t>>>=0,"offset");const e=this[t],r=this[t+7];void 0!==e&&void 0!==r||z(t,this.length-8);const n=(e<<24)+65536*this[++t]+256*this[++t]+this[++t];return(BigInt(n)<<BigInt(32))+BigInt(this[++t]*2**24+65536*this[++t]+256*this[++t]+r)})),u.prototype.readFloatLE=function(t,e){return t>>>=0,e||O(t,4,this.length),o.read(this,t,!0,23,4)},u.prototype.readFloatBE=function(t,e){return t>>>=0,e||O(t,4,this.length),o.read(this,t,!1,23,4)},u.prototype.readDoubleLE=function(t,e){return t>>>=0,e||O(t,8,this.length),o.read(this,t,!0,52,8)},u.prototype.readDoubleBE=function(t,e){return t>>>=0,e||O(t,8,this.length),o.read(this,t,!1,52,8)},u.prototype.writeUintLE=u.prototype.writeUIntLE=function(t,e,r,n){t=+t,e>>>=0,r>>>=0,n||P(this,t,e,r,Math.pow(2,8*r)-1,0);let o=1,i=0;for(this[e]=255&t;++i<r&&(o*=256);)this[e+i]=t/o&255;return e+r},u.prototype.writeUintBE=u.prototype.writeUIntBE=function(t,e,r,n){t=+t,e>>>=0,r>>>=0,n||P(this,t,e,r,Math.pow(2,8*r)-1,0);let o=r-1,i=1;for(this[e+o]=255&t;--o>=0&&(i*=256);)this[e+o]=t/i&255;return e+r},u.prototype.writeUint8=u.prototype.writeUInt8=function(t,e,r){return t=+t,e>>>=0,r||P(this,t,e,1,255,0),this[e]=255&t,e+1},u.prototype.writeUint16LE=u.prototype.writeUInt16LE=function(t,e,r){return t=+t,e>>>=0,r||P(this,t,e,2,65535,0),this[e]=255&t,this[e+1]=t>>>8,e+2},u.prototype.writeUint16BE=u.prototype.writeUInt16BE=function(t,e,r){return t=+t,e>>>=0,r||P(this,t,e,2,65535,0),this[e]=t>>>8,this[e+1]=255&t,e+2},u.prototype.writeUint32LE=u.prototype.writeUInt32LE=function(t,e,r){return t=+t,e>>>=0,r||P(this,t,e,4,4294967295,0),this[e+3]=t>>>24,this[e+2]=t>>>16,this[e+1]=t>>>8,this[e]=255&t,e+4},u.prototype.writeUint32BE=u.prototype.writeUInt32BE=function(t,e,r){return t=+t,e>>>=0,r||P(this,t,e,4,4294967295,0),this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t,e+4},u.prototype.writeBigUInt64LE=Q((function(t,e=0){return x(this,t,e,BigInt(0),BigInt("0xffffffffffffffff"))})),u.prototype.writeBigUInt64BE=Q((function(t,e=0){return T(this,t,e,BigInt(0),BigInt("0xffffffffffffffff"))})),u.prototype.writeIntLE=function(t,e,r,n){if(t=+t,e>>>=0,!n){const n=Math.pow(2,8*r-1);P(this,t,e,r,n-1,-n)}let o=0,i=1,s=0;for(this[e]=255&t;++o<r&&(i*=256);)t<0&&0===s&&0!==this[e+o-1]&&(s=1),this[e+o]=(t/i|0)-s&255;return e+r},u.prototype.writeIntBE=function(t,e,r,n){if(t=+t,e>>>=0,!n){const n=Math.pow(2,8*r-1);P(this,t,e,r,n-1,-n)}let o=r-1,i=1,s=0;for(this[e+o]=255&t;--o>=0&&(i*=256);)t<0&&0===s&&0!==this[e+o+1]&&(s=1),this[e+o]=(t/i|0)-s&255;return e+r},u.prototype.writeInt8=function(t,e,r){return t=+t,e>>>=0,r||P(this,t,e,1,127,-128),t<0&&(t=255+t+1),this[e]=255&t,e+1},u.prototype.writeInt16LE=function(t,e,r){return t=+t,e>>>=0,r||P(this,t,e,2,32767,-32768),this[e]=255&t,this[e+1]=t>>>8,e+2},u.prototype.writeInt16BE=function(t,e,r){return t=+t,e>>>=0,r||P(this,t,e,2,32767,-32768),this[e]=t>>>8,this[e+1]=255&t,e+2},u.prototype.writeInt32LE=function(t,e,r){return t=+t,e>>>=0,r||P(this,t,e,4,2147483647,-2147483648),this[e]=255&t,this[e+1]=t>>>8,this[e+2]=t>>>16,this[e+3]=t>>>24,e+4},u.prototype.writeInt32BE=function(t,e,r){return t=+t,e>>>=0,r||P(this,t,e,4,2147483647,-2147483648),t<0&&(t=4294967295+t+1),this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t,e+4},u.prototype.writeBigInt64LE=Q((function(t,e=0){return x(this,t,e,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),u.prototype.writeBigInt64BE=Q((function(t,e=0){return T(this,t,e,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))})),u.prototype.writeFloatLE=function(t,e,r){return N(this,t,e,!0,r)},u.prototype.writeFloatBE=function(t,e,r){return N(this,t,e,!1,r)},u.prototype.writeDoubleLE=function(t,e,r){return j(this,t,e,!0,r)},u.prototype.writeDoubleBE=function(t,e,r){return j(this,t,e,!1,r)},u.prototype.copy=function(t,e,r,n){if(!u.isBuffer(t))throw new TypeError("argument should be a Buffer");if(r||(r=0),n||0===n||(n=this.length),e>=t.length&&(e=t.length),e||(e=0),n>0&&n<r&&(n=r),n===r)return 0;if(0===t.length||0===this.length)return 0;if(e<0)throw new RangeError("targetStart out of bounds");if(r<0||r>=this.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("sourceEnd out of bounds");n>this.length&&(n=this.length),t.length-e<n-r&&(n=t.length-e+r);const o=n-r;return this===t&&"function"==typeof Uint8Array.prototype.copyWithin?this.copyWithin(e,r,n):Uint8Array.prototype.set.call(t,this.subarray(r,n),e),o},u.prototype.fill=function(t,e,r,n){if("string"==typeof t){if("string"==typeof e?(n=e,e=0,r=this.length):"string"==typeof r&&(n=r,r=this.length),void 0!==n&&"string"!=typeof n)throw new TypeError("encoding must be a string");if("string"==typeof n&&!u.isEncoding(n))throw new TypeError("Unknown encoding: "+n);if(1===t.length){const e=t.charCodeAt(0);("utf8"===n&&e<128||"latin1"===n)&&(t=e)}}else"number"==typeof t?t&=255:"boolean"==typeof t&&(t=Number(t));if(e<0||this.length<e||this.length<r)throw new RangeError("Out of range index");if(r<=e)return this;let o;if(e>>>=0,r=void 0===r?this.length:r>>>0,t||(t=0),"number"==typeof t)for(o=e;o<r;++o)this[o]=t;else{const i=u.isBuffer(t)?t:u.from(t,n),s=i.length;if(0===s)throw new TypeError('The value "'+t+'" is invalid for argument "value"');for(o=0;o<r-e;++o)this[o+e]=i[o%s]}return this};const $={};function D(t,e,r){$[t]=class extends r{constructor(){super(),Object.defineProperty(this,"message",{value:e.apply(this,arguments),writable:!0,configurable:!0}),this.name=`${this.name} [${t}]`,this.stack,delete this.name}get code(){return t}set code(t){Object.defineProperty(this,"code",{configurable:!0,enumerable:!0,value:t,writable:!0})}toString(){return`${this.name} [${t}]: ${this.message}`}}}function W(t){let e="",r=t.length;const n="-"===t[0]?1:0;for(;r>=n+4;r-=3)e=`_${t.slice(r-3,r)}${e}`;return`${t.slice(0,r)}${e}`}function F(t,e,r,n,o,i){if(t>r||t<e){const n="bigint"==typeof e?"n":"";let o;throw o=i>3?0===e||e===BigInt(0)?`>= 0${n} and < 2${n} ** ${8*(i+1)}${n}`:`>= -(2${n} ** ${8*(i+1)-1}${n}) and < 2 ** ${8*(i+1)-1}${n}`:`>= ${e}${n} and <= ${r}${n}`,new $.ERR_OUT_OF_RANGE("value",o,t)}!function(t,e,r){K(e,"offset"),void 0!==t[e]&&void 0!==t[e+r]||z(e,t.length-(r+1))}(n,o,i)}function K(t,e){if("number"!=typeof t)throw new $.ERR_INVALID_ARG_TYPE(e,"number",t)}function z(t,e,r){if(Math.floor(t)!==t)throw K(t,r),new $.ERR_OUT_OF_RANGE(r||"offset","an integer",t);if(e<0)throw new $.ERR_BUFFER_OUT_OF_BOUNDS;throw new $.ERR_OUT_OF_RANGE(r||"offset",`>= ${r?1:0} and <= ${e}`,t)}D("ERR_BUFFER_OUT_OF_BOUNDS",(function(t){return t?`${t} is outside of buffer bounds`:"Attempt to access memory outside buffer bounds"}),RangeError),D("ERR_INVALID_ARG_TYPE",(function(t,e){return`The "${t}" argument must be of type number. Received type ${typeof e}`}),TypeError),D("ERR_OUT_OF_RANGE",(function(t,e,r){let n=`The value of "${t}" is out of range.`,o=r;return Number.isInteger(r)&&Math.abs(r)>2**32?o=W(String(r)):"bigint"==typeof r&&(o=String(r),(r>BigInt(2)**BigInt(32)||r<-(BigInt(2)**BigInt(32)))&&(o=W(o)),o+="n"),n+=` It must be ${e}. Received ${o}`,n}),RangeError);const q=/[^+/0-9A-Za-z-_]/g;function J(t,e){let r;e=e||1/0;const n=t.length;let o=null;const i=[];for(let s=0;s<n;++s){if(r=t.charCodeAt(s),r>55295&&r<57344){if(!o){if(r>56319){(e-=3)>-1&&i.push(239,191,189);continue}if(s+1===n){(e-=3)>-1&&i.push(239,191,189);continue}o=r;continue}if(r<56320){(e-=3)>-1&&i.push(239,191,189),o=r;continue}r=65536+(o-55296<<10|r-56320)}else o&&(e-=3)>-1&&i.push(239,191,189);if(o=null,r<128){if((e-=1)<0)break;i.push(r)}else if(r<2048){if((e-=2)<0)break;i.push(r>>6|192,63&r|128)}else if(r<65536){if((e-=3)<0)break;i.push(r>>12|224,r>>6&63|128,63&r|128)}else{if(!(r<1114112))throw new Error("Invalid code point");if((e-=4)<0)break;i.push(r>>18|240,r>>12&63|128,r>>6&63|128,63&r|128)}}return i}function V(t){return n.toByteArray(function(t){if((t=(t=t.split("=")[0]).trim().replace(q,"")).length<2)return"";for(;t.length%4!=0;)t+="=";return t}(t))}function G(t,e,r,n){let o;for(o=0;o<n&&!(o+r>=e.length||o>=t.length);++o)e[o+r]=t[o];return o}function Y(t,e){return t instanceof e||null!=t&&null!=t.constructor&&null!=t.constructor.name&&t.constructor.name===e.name}function H(t){return t!=t}const Z=function(){const t="0123456789abcdef",e=new Array(256);for(let r=0;r<16;++r){const n=16*r;for(let o=0;o<16;++o)e[n+o]=t[r]+t[o]}return e}();function Q(t){return"undefined"==typeof BigInt?X:t}function X(){throw new Error("BigInt not supported")}},7:t=>{"use strict";var e,r="object"==typeof Reflect?Reflect:null,n=r&&"function"==typeof r.apply?r.apply:function(t,e,r){return Function.prototype.apply.call(t,e,r)};e=r&&"function"==typeof r.ownKeys?r.ownKeys:Object.getOwnPropertySymbols?function(t){return Object.getOwnPropertyNames(t).concat(Object.getOwnPropertySymbols(t))}:function(t){return Object.getOwnPropertyNames(t)};var o=Number.isNaN||function(t){return t!=t};function i(){i.init.call(this)}t.exports=i,t.exports.once=function(t,e){return new Promise((function(r,n){function o(r){t.removeListener(e,i),n(r)}function i(){"function"==typeof t.removeListener&&t.removeListener("error",o),r([].slice.call(arguments))}y(t,e,i,{once:!0}),"error"!==e&&function(t,e,r){"function"==typeof t.on&&y(t,"error",e,{once:!0})}(t,o)}))},i.EventEmitter=i,i.prototype._events=void 0,i.prototype._eventsCount=0,i.prototype._maxListeners=void 0;var s=10;function a(t){if("function"!=typeof t)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof t)}function u(t){return void 0===t._maxListeners?i.defaultMaxListeners:t._maxListeners}function f(t,e,r,n){var o,i,s,f;if(a(r),void 0===(i=t._events)?(i=t._events=Object.create(null),t._eventsCount=0):(void 0!==i.newListener&&(t.emit("newListener",e,r.listener?r.listener:r),i=t._events),s=i[e]),void 0===s)s=i[e]=r,++t._eventsCount;else if("function"==typeof s?s=i[e]=n?[r,s]:[s,r]:n?s.unshift(r):s.push(r),(o=u(t))>0&&s.length>o&&!s.warned){s.warned=!0;var h=new Error("Possible EventEmitter memory leak detected. "+s.length+" "+String(e)+" listeners added. Use emitter.setMaxListeners() to increase limit");h.name="MaxListenersExceededWarning",h.emitter=t,h.type=e,h.count=s.length,f=h,console&&console.warn&&console.warn(f)}return t}function h(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function c(t,e,r){var n={fired:!1,wrapFn:void 0,target:t,type:e,listener:r},o=h.bind(n);return o.listener=r,n.wrapFn=o,o}function l(t,e,r){var n=t._events;if(void 0===n)return[];var o=n[e];return void 0===o?[]:"function"==typeof o?r?[o.listener||o]:[o]:r?function(t){for(var e=new Array(t.length),r=0;r<e.length;++r)e[r]=t[r].listener||t[r];return e}(o):d(o,o.length)}function p(t){var e=this._events;if(void 0!==e){var r=e[t];if("function"==typeof r)return 1;if(void 0!==r)return r.length}return 0}function d(t,e){for(var r=new Array(e),n=0;n<e;++n)r[n]=t[n];return r}function y(t,e,r,n){if("function"==typeof t.on)n.once?t.once(e,r):t.on(e,r);else{if("function"!=typeof t.addEventListener)throw new TypeError('The "emitter" argument must be of type EventEmitter. Received type '+typeof t);t.addEventListener(e,(function o(i){n.once&&t.removeEventListener(e,o),r(i)}))}}Object.defineProperty(i,"defaultMaxListeners",{enumerable:!0,get:function(){return s},set:function(t){if("number"!=typeof t||t<0||o(t))throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received '+t+".");s=t}}),i.init=function(){void 0!==this._events&&this._events!==Object.getPrototypeOf(this)._events||(this._events=Object.create(null),this._eventsCount=0),this._maxListeners=this._maxListeners||void 0},i.prototype.setMaxListeners=function(t){if("number"!=typeof t||t<0||o(t))throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received '+t+".");return this._maxListeners=t,this},i.prototype.getMaxListeners=function(){return u(this)},i.prototype.emit=function(t){for(var e=[],r=1;r<arguments.length;r++)e.push(arguments[r]);var o="error"===t,i=this._events;if(void 0!==i)o=o&&void 0===i.error;else if(!o)return!1;if(o){var s;if(e.length>0&&(s=e[0]),s instanceof Error)throw s;var a=new Error("Unhandled error."+(s?" ("+s.message+")":""));throw a.context=s,a}var u=i[t];if(void 0===u)return!1;if("function"==typeof u)n(u,this,e);else{var f=u.length,h=d(u,f);for(r=0;r<f;++r)n(h[r],this,e)}return!0},i.prototype.addListener=function(t,e){return f(this,t,e,!1)},i.prototype.on=i.prototype.addListener,i.prototype.prependListener=function(t,e){return f(this,t,e,!0)},i.prototype.once=function(t,e){return a(e),this.on(t,c(this,t,e)),this},i.prototype.prependOnceListener=function(t,e){return a(e),this.prependListener(t,c(this,t,e)),this},i.prototype.removeListener=function(t,e){var r,n,o,i,s;if(a(e),void 0===(n=this._events))return this;if(void 0===(r=n[t]))return this;if(r===e||r.listener===e)0==--this._eventsCount?this._events=Object.create(null):(delete n[t],n.removeListener&&this.emit("removeListener",t,r.listener||e));else if("function"!=typeof r){for(o=-1,i=r.length-1;i>=0;i--)if(r[i]===e||r[i].listener===e){s=r[i].listener,o=i;break}if(o<0)return this;0===o?r.shift():function(t,e){for(;e+1<t.length;e++)t[e]=t[e+1];t.pop()}(r,o),1===r.length&&(n[t]=r[0]),void 0!==n.removeListener&&this.emit("removeListener",t,s||e)}return this},i.prototype.off=i.prototype.removeListener,i.prototype.removeAllListeners=function(t){var e,r,n;if(void 0===(r=this._events))return this;if(void 0===r.removeListener)return 0===arguments.length?(this._events=Object.create(null),this._eventsCount=0):void 0!==r[t]&&(0==--this._eventsCount?this._events=Object.create(null):delete r[t]),this;if(0===arguments.length){var o,i=Object.keys(r);for(n=0;n<i.length;++n)"removeListener"!==(o=i[n])&&this.removeAllListeners(o);return this.removeAllListeners("removeListener"),this._events=Object.create(null),this._eventsCount=0,this}if("function"==typeof(e=r[t]))this.removeListener(t,e);else if(void 0!==e)for(n=e.length-1;n>=0;n--)this.removeListener(t,e[n]);return this},i.prototype.listeners=function(t){return l(this,t,!0)},i.prototype.rawListeners=function(t){return l(this,t,!1)},i.listenerCount=function(t,e){return"function"==typeof t.listenerCount?t.listenerCount(e):p.call(t,e)},i.prototype.listenerCount=p,i.prototype.eventNames=function(){return this._eventsCount>0?e(this._events):[]}},251:(t,e)=>{e.read=function(t,e,r,n,o){var i,s,a=8*o-n-1,u=(1<<a)-1,f=u>>1,h=-7,c=r?o-1:0,l=r?-1:1,p=t[e+c];for(c+=l,i=p&(1<<-h)-1,p>>=-h,h+=a;h>0;i=256*i+t[e+c],c+=l,h-=8);for(s=i&(1<<-h)-1,i>>=-h,h+=n;h>0;s=256*s+t[e+c],c+=l,h-=8);if(0===i)i=1-f;else{if(i===u)return s?NaN:1/0*(p?-1:1);s+=Math.pow(2,n),i-=f}return(p?-1:1)*s*Math.pow(2,i-n)},e.write=function(t,e,r,n,o,i){var s,a,u,f=8*i-o-1,h=(1<<f)-1,c=h>>1,l=23===o?Math.pow(2,-24)-Math.pow(2,-77):0,p=n?0:i-1,d=n?1:-1,y=e<0||0===e&&1/e<0?1:0;for(e=Math.abs(e),isNaN(e)||e===1/0?(a=isNaN(e)?1:0,s=h):(s=Math.floor(Math.log(e)/Math.LN2),e*(u=Math.pow(2,-s))<1&&(s--,u*=2),(e+=s+c>=1?l/u:l*Math.pow(2,1-c))*u>=2&&(s++,u/=2),s+c>=h?(a=0,s=h):s+c>=1?(a=(e*u-1)*Math.pow(2,o),s+=c):(a=e*Math.pow(2,c-1)*Math.pow(2,o),s=0));o>=8;t[r+p]=255&a,p+=d,a/=256,o-=8);for(s=s<<o|a,f+=o;f>0;t[r+p]=255&s,p+=d,s/=256,f-=8);t[r+p-d]|=128*y}}},e={};function r(n){var o=e[n];if(void 0!==o)return o.exports;var i=e[n]={exports:{}};return t[n](i,i.exports,r),i.exports}r.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return r.d(e,{a:e}),e},r.d=(t,e)=>{for(var n in e)r.o(e,n)&&!r.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},r.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var n={};return(()=>{"use strict";r.r(n),r.d(n,{HamsaVoiceAgent:()=>c});var t=r(287),e=r(870),o=r.n(e);class i{constructor(t,e,r){const n=window.AudioContext||window.webkitAudioContext;this.audioContext=new n({sampleRate:16e3}),this.ws=t,this.isPaused=!1,this.onSpeakingCB=e,this.onListeningCB=r,this.isPlaying=!1,this.initAudioWorklet()}async initAudioWorklet(){this.audioContext.audioWorklet.addModule(o()).then((()=>{this.processor=new AudioWorkletNode(this.audioContext,"audio-player-processor"),this.processor.port.onmessage=t=>{"mark"===t.data.type?this.ws.send(JSON.stringify({event:"mark",streamSid:"WEBSDK",mark:{name:t.data.markName}})):"finished"===t.data.type&&this.updatePlayingState(!1)},this.processor.connect(this.audioContext.destination)}))}enqueueAudio(e){const r=t.hp.from(e,"base64"),n=this.pcm16ToFloat32(r);this.processor.port.postMessage({type:"enqueue",audioSamples:n}),this.updatePlayingState(!0)}pause(){this.isPaused=!0,this.processor.port.postMessage({type:"pause"})}resume(){this.isPaused=!1,this.processor.port.postMessage({type:"resume"})}stopAndClear(){this.processor.port.postMessage({type:"clear"}),this.updatePlayingState(!1)}addMark(t){this.processor.port.postMessage({type:"addMark",markName:t})}pcm16ToFloat32(t){const e=new Float32Array(t.length/2),r=new DataView(t.buffer);for(let n=0,o=0;n<t.byteLength;n+=2,o++){const t=r.getInt16(n,!0);e[o]=t/32768}return e}updatePlayingState(t){t&&!this.isPlaying?(this.isPlaying=!0,this.onSpeakingCB&&this.onSpeakingCB()):!t&&this.isPlaying&&(this.isPlaying=!1,this.onListeningCB&&this.onListeningCB())}}var s=r(428),a=r.n(s);class u{constructor(){this.audioContext=null,this.mediaStreamSource=null,this.audioWorkletNode=null,this.mediaStream=null,this.isPaused=!1}async startStreaming(t){try{this.mediaStream=await navigator.mediaDevices.getUserMedia({audio:{echoCancellation:!0,noiseSuppression:!0,autoGainControl:!1},video:!1}),this.audioContext=new AudioContext({sampleRate:16e3}),this.audioContext.audioWorklet.addModule(a()).then((()=>{this.mediaStreamSource=this.audioContext.createMediaStreamSource(this.mediaStream),this.audioWorkletNode=new AudioWorkletNode(this.audioContext,"audio-processor"),this.mediaStreamSource.connect(this.audioWorkletNode),this.audioWorkletNode.connect(this.audioContext.destination),this.audioWorkletNode.port.onmessage=e=>{this.isPaused||t&&t.readyState===WebSocket.OPEN&&t.send(JSON.stringify(e.data))}})).catch((t=>{console.error("Failed to load AudioWorklet module",t)}))}catch(t){"NotAllowedError"===t.name?(console.error("Microphone access denied by the user.",t),alert("Microphone access was denied. Please allow access to use the audio features.")):"NotFoundError"===t.name||"DevicesNotFoundError"===t.name?(console.error("No microphone device found.",t),alert("No microphone was found on this device.")):"NotReadableError"===t.name||"TrackStartError"===t.name?(console.error("Microphone is already in use by another application.",t),alert("The microphone is currently in use by another application.")):"AbortError"===t.name?(console.error("The user aborted the request.",t),alert("Microphone access request was aborted. Please try again.")):(console.error("Error accessing the microphone: ",t),alert("An unknown error occurred while trying to access the microphone."))}}pause(){this.isPaused=!0}resume(){this.isPaused=!1}stop(){this.mediaStream&&this.mediaStream.getTracks().forEach((t=>t.stop())),this.audioWorkletNode&&this.audioWorkletNode.disconnect(),this.mediaStreamSource&&this.mediaStreamSource.disconnect(),this.audioContext&&this.audioContext.close(),this.audioContext=null,this.mediaStreamSource=null,this.audioWorkletNode=null,this.mediaStream=null,this.isPaused=!1}}class f{constructor(t,e,r,n,o,i,s,a,u,f,h,c){this.url=`${t}/${e}?api_key=${c}`,this.ws=null,this.isConnected=!1,this.audioPlayer=null,this.audioRecorder=null,this.last_transcription_date=new Date,this.last_voice_byte_date=new Date,this.is_media=!1,this.onErrorCB=r,this.onStartCB=n,this.onTransciprtionRecievedCB=o,this.onAnswerRecievedCB=i,this.onSpeakingCB=s,this.onListeningCB=a,this.onClosedCB=u,this.voiceEnablement=f,this.tools=h,this.apiKey=c}startCall(){try{this.ws||(this.ws=new WebSocket(this.url),this.ws.onopen=this.onOpen.bind(this),this.ws.onmessage=this.onMessage.bind(this),this.ws.onclose=this.onClose.bind(this),this.ws.onerror=this.onError.bind(this),this.audioPlayer=new i(this.ws,this.onSpeakingCB,this.onListeningCB),this.audioRecorder=new u)}catch(t){console.log(t)}}onOpen(){this.ws.send(JSON.stringify({event:"start",streamSid:"WEBSDK"})),this.isConnected=!0,this.audioRecorder.startStreaming(this.ws),this.onStartCB&&this.onStartCB()}onMessage(t){const e=JSON.parse(t.data);switch(e.event){case"media":e.media&&this.audioPlayer.enqueueAudio(e.media.payload);break;case"clear":this.audioPlayer.stopAndClear();break;case"mark":this.audioPlayer.addMark(e.mark.name);break;case"transcription":this.onTransciprtionRecievedCB&&this.onTransciprtionRecievedCB(e.content);break;case"answer":this.onAnswerRecievedCB&&this.onAnswerRecievedCB(e.content);break;case"tools":const t=this.run_tools(e.content);this.ws.send(JSON.stringify({event:"tools_response",tools_response:t,streamSid:"WEBSDK"}))}}onClose(t){this.onClosedCB&&this.onClosedCB(),this.audioPlayer.stopAndClear(),this.audioRecorder.stop(),this.isConnected=!1,this.ws=null}onError(t){this.onErrorCB&&this.onErrorCB(t)}endCall(){this.ws&&(this.audioPlayer.stopAndClear(),this.ws.send(JSON.stringify({event:"stop"})),this.audioRecorder.stop(),this.#t(),this.onClosedCB&&this.onClosedCB())}pauseCall(){this.audioPlayer.pause(),this.audioRecorder.pause()}resumeCall(){this.audioPlayer.resume(),this.audioRecorder.resume()}run_tools(t){const e=[];return t.forEach((t=>{if("function"===t.type){const r=this.#e(t.function.name),n=t.function.name,o=JSON.parse(t.function.arguments);if(r&&"function"==typeof r.fn){const i=r.fn(...Object.values(o));e.push({id:t.id,function:{name:n,response:i}})}else e.push({id:t.id,function:{name:n,response:"Error could not find the function"}}),console.log(`Function ${n} is not defined`)}})),e}#e(t){return this.tools.find((e=>e.function_name===t))||null}#t(){this.ws.readyState===WebSocket.OPEN&&this.ws.close(1e3,"Normal Closure")}}var h=r(7);class c extends h.EventEmitter{constructor(t){super(),this.webSocketManager=null,this.apiKey=t,this.API_URL="https://api.tryhamsa.com",this.WS_URL="wss://bots.tryhamsa.com/stream"}async start({agentId:t=null,params:e={},voiceEnablement:r=!1,tools:n=[]}){try{const o=await this.#r(t,e,r,n);this.webSocketManager=new f(this.WS_URL,o,(t=>this.emit("error",t)),(()=>this.emit("start")),(t=>this.emit("transcriptionReceived",t)),(t=>this.emit("answerReceived",t)),(()=>this.emit("speaking")),(()=>this.emit("listening")),(()=>this.emit("closed")),r,n,this.apiKey),this.webSocketManager.startCall(),this.emit("callStarted")}catch(t){this.emit("error",new Error("Error in starting the call! Make sure you initialized the client with init()."))}}end(){try{this.webSocketManager.endCall(),this.emit("callEnded")}catch(t){this.emit("error",new Error("Error in ending the call! Make sure you initialized the client with init()."))}}pause(){this.webSocketManager.pauseCall(),this.emit("callPaused")}resume(){this.webSocketManager.resumeCall(),this.emit("callResumed")}async#r(t,e,r,n){const o={Authorization:`Token ${this.apiKey}`,"Content-Type":"application/json"},i={voiceAgentId:t,params:e,voiceEnablement:r,tools:r&&n?this.#n(n):[]},s={method:"POST",headers:o,body:JSON.stringify(i),redirect:"follow"};try{const t=await fetch(`${this.API_URL}/v1/voice-agents/conversation-init`,s);return(await t.json()).data.jobId}catch(t){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."))}}#n(t){return t.map((t=>({type:"function",function:{name:t.function_name,description:t.description,parameters:{type:"object",properties:t.parameters?.reduce(((t,e)=>(t[e.name]={type:e.type,description:e.description},t)),{})||{},required:t.required||[]}}})))}}})(),n})()));
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hamsa-ai/voice-agents-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Hamsa AI- Voice Agents Javascript SDK",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "types/main.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "npm install",
|
|
9
|
+
"prepare": "npx tsc && npx webpack"
|
|
10
|
+
},
|
|
11
|
+
"author": "Hamsa AI Inc.",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"audio-worklet-loader": "^1.1.0",
|
|
15
|
+
"babel-loader": "^9.1.3",
|
|
16
|
+
"buffer": "^6.0.3",
|
|
17
|
+
"css-loader": "^7.1.2",
|
|
18
|
+
"file-loader": "^6.2.0",
|
|
19
|
+
"style-loader": "^4.0.0",
|
|
20
|
+
"typescript": "^5.5.4",
|
|
21
|
+
"webpack": "^5.91.0",
|
|
22
|
+
"webpack-cli": "^5.1.4",
|
|
23
|
+
"webpack-node-externals": "^3.0.0"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"events": "^3.3.0"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/hamsa-ai/voice-agents-sdk.git"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"arab",
|
|
34
|
+
"voice",
|
|
35
|
+
"agent",
|
|
36
|
+
"web",
|
|
37
|
+
"nodejs",
|
|
38
|
+
"sdk",
|
|
39
|
+
"hamsa",
|
|
40
|
+
"ai"
|
|
41
|
+
],
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/hamsa-ai/voice-agents-sdk/issues"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/hamsa-ai/voice-agents-sdk#readme"
|
|
46
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
class AudioPlayerProcessor extends AudioWorkletProcessor {
|
|
2
|
+
constructor() {
|
|
3
|
+
super();
|
|
4
|
+
this.audioData = [];
|
|
5
|
+
this.isPaused = false;
|
|
6
|
+
this.marks = [];
|
|
7
|
+
this.isDone = false;
|
|
8
|
+
|
|
9
|
+
this.port.onmessage = (event) => {
|
|
10
|
+
if (event.data.type === 'enqueue') {
|
|
11
|
+
this.audioData.push(...event.data.audioSamples);
|
|
12
|
+
this.isPaused = false;
|
|
13
|
+
this.isDone = false;
|
|
14
|
+
} else if (event.data.type === 'pause') {
|
|
15
|
+
this.isPaused = true;
|
|
16
|
+
} else if (event.data.type === 'resume') {
|
|
17
|
+
this.isPaused = false;
|
|
18
|
+
} else if (event.data.type === 'addMark') {
|
|
19
|
+
this.marks.push(event.data.markName);
|
|
20
|
+
} else if (event.data.type === 'clear') {
|
|
21
|
+
this.clearAllData();
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
clearAllData() {
|
|
27
|
+
this.audioData = []; // Clear the audio data buffer
|
|
28
|
+
this.marks = []; // Clear any pending marks
|
|
29
|
+
this.isPaused = true; // Optionally, pause processing to ensure no data is played
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
process(inputs, outputs) {
|
|
33
|
+
const output = outputs[0];
|
|
34
|
+
|
|
35
|
+
if (this.isPaused) {
|
|
36
|
+
for (let channel = 0; channel < output.length; channel++) {
|
|
37
|
+
output[channel].fill(0); // Output silence if paused or cleared
|
|
38
|
+
}
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (let channel = 0; channel < output.length; channel++) {
|
|
43
|
+
const outputData = output[channel];
|
|
44
|
+
const inputData = this.audioData.splice(0, outputData.length);
|
|
45
|
+
|
|
46
|
+
if (inputData.length > 0) {
|
|
47
|
+
outputData.set(inputData);
|
|
48
|
+
} else {
|
|
49
|
+
outputData.fill(0); // Output silence when no data is available
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (this.audioData.length === 0 && !this.isDone) {
|
|
53
|
+
this.isDone = true;
|
|
54
|
+
this.port.postMessage({ type: 'finished' });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Process marks if all audio data has been played
|
|
58
|
+
if (this.marks.length > 0 && this.audioData.length === 0) {
|
|
59
|
+
const mark_name = this.marks.shift();
|
|
60
|
+
this.port.postMessage({ type: 'mark', markName: mark_name });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
return true; // Keep the processor active
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
registerProcessor('audio-player-processor', AudioPlayerProcessor);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
class AudioProcessor extends AudioWorkletProcessor {
|
|
2
|
+
encodeBase64(bytes) {
|
|
3
|
+
const base64abc = [
|
|
4
|
+
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
|
5
|
+
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
|
6
|
+
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
|
7
|
+
'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
let result = '';
|
|
11
|
+
let i;
|
|
12
|
+
const l = bytes.length;
|
|
13
|
+
for (i = 2; i < l; i += 3) {
|
|
14
|
+
result += base64abc[bytes[i - 2] >> 2];
|
|
15
|
+
result += base64abc[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
|
|
16
|
+
result += base64abc[((bytes[i - 1] & 0x0f) << 2) | (bytes[i] >> 6)];
|
|
17
|
+
result += base64abc[bytes[i] & 0x3f];
|
|
18
|
+
}
|
|
19
|
+
if (i === l + 1) { // 1 octet yet to write
|
|
20
|
+
result += base64abc[bytes[i - 2] >> 2];
|
|
21
|
+
result += base64abc[(bytes[i - 2] & 0x03) << 4];
|
|
22
|
+
result += '==';
|
|
23
|
+
}
|
|
24
|
+
if (i === l) { // 2 octets yet to write
|
|
25
|
+
result += base64abc[bytes[i - 2] >> 2];
|
|
26
|
+
result += base64abc[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
|
|
27
|
+
result += base64abc[(bytes[i - 1] & 0x0f) << 2];
|
|
28
|
+
result += '=';
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
process(inputs, outputs, parameters) {
|
|
34
|
+
const input = inputs[0];
|
|
35
|
+
if (input && input[0]) {
|
|
36
|
+
const inputData = input[0];
|
|
37
|
+
const rawAudioData = new Float32Array(inputData.length);
|
|
38
|
+
rawAudioData.set(inputData);
|
|
39
|
+
|
|
40
|
+
// Convert the audio data to a Uint8Array for base64 encoding
|
|
41
|
+
const uint8Array = new Uint8Array(rawAudioData.buffer);
|
|
42
|
+
|
|
43
|
+
// Use the custom base64 encoding function
|
|
44
|
+
const base64String = this.encodeBase64(uint8Array);
|
|
45
|
+
|
|
46
|
+
// Send the base64 string to the main thread via the port
|
|
47
|
+
this.port.postMessage({ event: 'media', streamSid: 'WEBSDK', media: { payload: base64String } });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
registerProcessor('audio-processor', AudioProcessor);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
import AudioPlayerProcessor from './audio-player-processor.worklet.js';
|
|
3
|
+
|
|
4
|
+
export default class AudioPlayer {
|
|
5
|
+
constructor(ws, onSpeaking, onListening) {
|
|
6
|
+
const AudioContext = window.AudioContext || window.webkitAudioContext;
|
|
7
|
+
this.audioContext = new AudioContext({ sampleRate: 16000 });
|
|
8
|
+
this.ws = ws;
|
|
9
|
+
this.isPaused = false; // Added a flag to keep track of pause state
|
|
10
|
+
this.onSpeakingCB = onSpeaking;
|
|
11
|
+
this.onListeningCB = onListening;
|
|
12
|
+
this.isPlaying = false;
|
|
13
|
+
this.initAudioWorklet();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async initAudioWorklet() {
|
|
17
|
+
this.audioContext.audioWorklet.addModule(AudioPlayerProcessor).then(() => {
|
|
18
|
+
this.processor = new AudioWorkletNode(this.audioContext, 'audio-player-processor');
|
|
19
|
+
this.processor.port.onmessage = (event) => {
|
|
20
|
+
if (event.data.type === 'mark') {
|
|
21
|
+
this.ws.send(JSON.stringify({ event: 'mark', streamSid: 'WEBSDK', mark: { name: event.data.markName } }));
|
|
22
|
+
} else if (event.data.type === 'finished') {
|
|
23
|
+
this.updatePlayingState(false);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
this.processor.connect(this.audioContext.destination);
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
enqueueAudio(base64Data) {
|
|
32
|
+
const binaryData = Buffer.from(base64Data, 'base64');
|
|
33
|
+
const audioSamples = this.pcm16ToFloat32(binaryData);
|
|
34
|
+
this.processor.port.postMessage({ type: 'enqueue', audioSamples });
|
|
35
|
+
this.updatePlayingState(true);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pause() {
|
|
39
|
+
this.isPaused = true;
|
|
40
|
+
this.processor.port.postMessage({ type: 'pause' });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
resume() {
|
|
44
|
+
this.isPaused = false;
|
|
45
|
+
this.processor.port.postMessage({ type: 'resume' });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
stopAndClear() {
|
|
49
|
+
this.processor.port.postMessage({ type: 'clear' });
|
|
50
|
+
this.updatePlayingState(false);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
addMark(markName) {
|
|
54
|
+
this.processor.port.postMessage({ type: 'addMark', markName });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
pcm16ToFloat32(pcm16Array) {
|
|
58
|
+
const float32Array = new Float32Array(pcm16Array.length / 2);
|
|
59
|
+
const dataView = new DataView(pcm16Array.buffer);
|
|
60
|
+
|
|
61
|
+
for (let i = 0, j = 0; i < pcm16Array.byteLength; i += 2, j++) {
|
|
62
|
+
const int16Sample = dataView.getInt16(i, true);
|
|
63
|
+
float32Array[j] = int16Sample / 0x8000;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return float32Array;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
updatePlayingState(isPlaying) {
|
|
70
|
+
if (isPlaying && !this.isPlaying) {
|
|
71
|
+
this.isPlaying = true;
|
|
72
|
+
if (this.onSpeakingCB) this.onSpeakingCB(); // Trigger the speaking callback
|
|
73
|
+
} else if (!isPlaying && this.isPlaying) {
|
|
74
|
+
this.isPlaying = false;
|
|
75
|
+
if (this.onListeningCB) this.onListeningCB(); // Trigger the listening callback
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import AudioProcessor from './audio-processor.worklet.js';
|
|
2
|
+
|
|
3
|
+
export default class AudioRecorder {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.audioContext = null;
|
|
6
|
+
this.mediaStreamSource = null;
|
|
7
|
+
this.audioWorkletNode = null;
|
|
8
|
+
this.mediaStream = null;
|
|
9
|
+
this.isPaused = false;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async startStreaming(ws) {
|
|
13
|
+
try {
|
|
14
|
+
this.mediaStream = await navigator.mediaDevices.getUserMedia({
|
|
15
|
+
audio: {
|
|
16
|
+
echoCancellation: true,
|
|
17
|
+
noiseSuppression: true,
|
|
18
|
+
autoGainControl: false
|
|
19
|
+
},
|
|
20
|
+
video: false
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
this.audioContext = new AudioContext({ sampleRate: 16000 });
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
this.audioContext.audioWorklet.addModule(AudioProcessor).then(()=>{
|
|
27
|
+
this.mediaStreamSource = this.audioContext.createMediaStreamSource(this.mediaStream);
|
|
28
|
+
this.audioWorkletNode = new AudioWorkletNode(this.audioContext, 'audio-processor');
|
|
29
|
+
|
|
30
|
+
// Connect the media stream source to the AudioWorkletNode
|
|
31
|
+
this.mediaStreamSource.connect(this.audioWorkletNode);
|
|
32
|
+
this.audioWorkletNode.connect(this.audioContext.destination);
|
|
33
|
+
|
|
34
|
+
// Handle messages from the AudioWorkletProcessor
|
|
35
|
+
this.audioWorkletNode.port.onmessage = (event) => {
|
|
36
|
+
if (this.isPaused) return; // Pause processing if needed
|
|
37
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
38
|
+
ws.send(JSON.stringify(event.data));
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}).catch((e) => {
|
|
42
|
+
console.error('Failed to load AudioWorklet module', e);
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error.name === 'NotAllowedError') {
|
|
47
|
+
console.error("Microphone access denied by the user.", error);
|
|
48
|
+
alert("Microphone access was denied. Please allow access to use the audio features.");
|
|
49
|
+
} else if (error.name === 'NotFoundError' || error.name === 'DevicesNotFoundError') {
|
|
50
|
+
console.error("No microphone device found.", error);
|
|
51
|
+
alert("No microphone was found on this device.");
|
|
52
|
+
} else if (error.name === 'NotReadableError' || error.name === 'TrackStartError') {
|
|
53
|
+
console.error("Microphone is already in use by another application.", error);
|
|
54
|
+
alert("The microphone is currently in use by another application.");
|
|
55
|
+
} else if (error.name === 'AbortError') {
|
|
56
|
+
console.error("The user aborted the request.", error);
|
|
57
|
+
alert("Microphone access request was aborted. Please try again.");
|
|
58
|
+
} else {
|
|
59
|
+
console.error("Error accessing the microphone: ", error);
|
|
60
|
+
alert("An unknown error occurred while trying to access the microphone.");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
pause() {
|
|
66
|
+
this.isPaused = true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
resume() {
|
|
70
|
+
this.isPaused = false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
stop() {
|
|
74
|
+
// Stop the media stream tracks to release the microphone
|
|
75
|
+
if (this.mediaStream) {
|
|
76
|
+
this.mediaStream.getTracks().forEach(track => track.stop());
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Disconnect the audio processing nodes and close the audio context
|
|
80
|
+
if (this.audioWorkletNode) {
|
|
81
|
+
this.audioWorkletNode.disconnect();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (this.mediaStreamSource) {
|
|
85
|
+
this.mediaStreamSource.disconnect();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (this.audioContext) {
|
|
89
|
+
this.audioContext.close();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Reset variables to their initial state
|
|
93
|
+
this.audioContext = null;
|
|
94
|
+
this.mediaStreamSource = null;
|
|
95
|
+
this.audioWorkletNode = null;
|
|
96
|
+
this.mediaStream = null;
|
|
97
|
+
this.isPaused = false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
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";
|
|
@@ -0,0 +1 @@
|
|
|
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";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export default class AudioPlayer {
|
|
2
|
+
constructor(ws: any, onSpeaking: any, onListening: any);
|
|
3
|
+
audioContext: any;
|
|
4
|
+
ws: any;
|
|
5
|
+
isPaused: boolean;
|
|
6
|
+
onSpeakingCB: any;
|
|
7
|
+
onListeningCB: any;
|
|
8
|
+
isPlaying: boolean;
|
|
9
|
+
initAudioWorklet(): Promise<void>;
|
|
10
|
+
processor: AudioWorkletNode;
|
|
11
|
+
enqueueAudio(base64Data: any): void;
|
|
12
|
+
pause(): void;
|
|
13
|
+
resume(): void;
|
|
14
|
+
stopAndClear(): void;
|
|
15
|
+
addMark(markName: any): void;
|
|
16
|
+
pcm16ToFloat32(pcm16Array: any): Float32Array;
|
|
17
|
+
updatePlayingState(isPlaying: any): void;
|
|
18
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default class AudioRecorder {
|
|
2
|
+
audioContext: AudioContext;
|
|
3
|
+
mediaStreamSource: MediaStreamAudioSourceNode;
|
|
4
|
+
audioWorkletNode: AudioWorkletNode;
|
|
5
|
+
mediaStream: MediaStream;
|
|
6
|
+
isPaused: boolean;
|
|
7
|
+
startStreaming(ws: any): Promise<void>;
|
|
8
|
+
pause(): void;
|
|
9
|
+
resume(): void;
|
|
10
|
+
stop(): void;
|
|
11
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export default class WebSocketManager {
|
|
2
|
+
constructor(url: any, conversationId: any, onError: any, onStart: any, onTransciprtionRecieved: any, onAnswerRecieved: any, onSpeaking: any, onListening: any, onClosed: any, voiceEnablement: any, tools: any, apiKey: any);
|
|
3
|
+
url: string;
|
|
4
|
+
ws: WebSocket;
|
|
5
|
+
isConnected: boolean;
|
|
6
|
+
audioPlayer: AudioPlayer;
|
|
7
|
+
audioRecorder: AudioRecorder;
|
|
8
|
+
last_transcription_date: Date;
|
|
9
|
+
last_voice_byte_date: Date;
|
|
10
|
+
is_media: boolean;
|
|
11
|
+
onErrorCB: any;
|
|
12
|
+
onStartCB: any;
|
|
13
|
+
onTransciprtionRecievedCB: any;
|
|
14
|
+
onAnswerRecievedCB: any;
|
|
15
|
+
onSpeakingCB: any;
|
|
16
|
+
onListeningCB: any;
|
|
17
|
+
onClosedCB: any;
|
|
18
|
+
voiceEnablement: any;
|
|
19
|
+
tools: any;
|
|
20
|
+
apiKey: any;
|
|
21
|
+
startCall(): void;
|
|
22
|
+
onOpen(): void;
|
|
23
|
+
onMessage(event: any): void;
|
|
24
|
+
onClose(event: any): void;
|
|
25
|
+
onError(error: any): void;
|
|
26
|
+
endCall(): void;
|
|
27
|
+
pauseCall(): void;
|
|
28
|
+
resumeCall(): void;
|
|
29
|
+
run_tools(tools_array: any): any[];
|
|
30
|
+
#private;
|
|
31
|
+
}
|
|
32
|
+
import AudioPlayer from './audio_player';
|
|
33
|
+
import AudioRecorder from './audio_recorder';
|
package/types/main.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export class HamsaVoiceAgent extends EventEmitter<[never]> {
|
|
2
|
+
constructor(apiKey: any);
|
|
3
|
+
webSocketManager: WebSocketManager;
|
|
4
|
+
apiKey: any;
|
|
5
|
+
API_URL: string;
|
|
6
|
+
WS_URL: string;
|
|
7
|
+
start({ agentId, params, voiceEnablement, tools }: {
|
|
8
|
+
agentId?: any;
|
|
9
|
+
params?: {};
|
|
10
|
+
voiceEnablement?: boolean;
|
|
11
|
+
tools?: any[];
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
end(): void;
|
|
14
|
+
pause(): void;
|
|
15
|
+
resume(): void;
|
|
16
|
+
#private;
|
|
17
|
+
}
|
|
18
|
+
import { EventEmitter } from 'events';
|
|
19
|
+
import WebSocketManager from './classes/websocket_manager';
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
};
|