assemblyai 4.34.5 → 4.35.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.
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).assemblyai={})}(this,(function(e){"use strict";class t extends Error{constructor(e="DualChannelCapture requires a browser environment (AudioContext is undefined)."){super(e),this.name="BrowserOnlyError"}}function s(e,t){var s={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&t.indexOf(n)<0&&(s[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(n=Object.getOwnPropertySymbols(e);i<n.length;i++)t.indexOf(n[i])<0&&Object.prototype.propertyIsEnumerable.call(e,n[i])&&(s[n[i]]=e[n[i]])}return s}function n(e,t,s,n){return new(s||(s=Promise))((function(i,r){function o(e){try{l(n.next(e))}catch(e){r(e)}}function a(e){try{l(n.throw(e))}catch(e){r(e)}}function l(e){var t;e.done?i(e.value):(t=e.value,t instanceof s?t:new s((function(e){e(t)}))).then(o,a)}l((n=n.apply(e,t||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;const i={cache:"no-store"};let r="";"undefined"!=typeof navigator&&navigator.userAgent&&(r+=navigator.userAgent);const o={sdk:{name:"JavaScript",version:"4.34.5"}};"undefined"!=typeof process&&(process.versions.node&&-1===r.indexOf("Node")&&(o.runtime_env={name:"Node",version:process.versions.node}),process.versions.bun&&-1===r.indexOf("Bun")&&(o.runtime_env={name:"Bun",version:process.versions.bun})),"undefined"!=typeof Deno&&process.versions.bun&&-1===r.indexOf("Deno")&&(o.runtime_env={name:"Deno",version:Deno.version.deno});class a{constructor(e){var t;this.params=e,!1===e.userAgent?this.userAgent=void 0:this.userAgent=(t=e.userAgent||{},r+(!1===t?"":" AssemblyAI/1.0 ("+Object.entries(Object.assign(Object.assign({},o),t)).map((([e,t])=>t?`${e}=${t.name}/${t.version}`:"")).join(" ")+")"))}fetch(e,t){return n(this,void 0,void 0,(function*(){t=Object.assign(Object.assign({},i),t);let s={Authorization:this.params.apiKey,"Content-Type":"application/json"};(null==i?void 0:i.headers)&&(s=Object.assign(Object.assign({},s),i.headers)),(null==t?void 0:t.headers)&&(s=Object.assign(Object.assign({},s),t.headers)),this.userAgent&&(s["User-Agent"]=this.userAgent,"undefined"!=typeof window&&"chrome"in window&&(s["AssemblyAI-Agent"]=this.userAgent)),t.headers=s,e.startsWith("http")||(e=this.params.baseUrl+e);const n=yield fetch(e,t);if(n.status>=400){let e;const t=yield n.text();if(t){try{e=JSON.parse(t)}catch(e){}if(null==e?void 0:e.error)throw new Error(e.error);throw new Error(t)}throw new Error(`HTTP Error: ${n.status} ${n.statusText}`)}return n}))}fetchJson(e,t){return n(this,void 0,void 0,(function*(){return(yield this.fetch(e,t)).json()}))}}class l extends a{summary(e,t){return this.fetchJson("/lemur/v3/generate/summary",{method:"POST",body:JSON.stringify(e),signal:t})}questionAnswer(e,t){return this.fetchJson("/lemur/v3/generate/question-answer",{method:"POST",body:JSON.stringify(e),signal:t})}actionItems(e,t){return this.fetchJson("/lemur/v3/generate/action-items",{method:"POST",body:JSON.stringify(e),signal:t})}task(e,t){return this.fetchJson("/lemur/v3/generate/task",{method:"POST",body:JSON.stringify(e),signal:t})}getResponse(e,t){return this.fetchJson(`/lemur/v3/${e}`,{signal:t})}purgeRequestData(e,t){return this.fetchJson(`/lemur/v3/${e}`,{method:"DELETE",signal:t})}}const{WritableStream:c}="undefined"!=typeof window?window:"undefined"!=typeof global?global:globalThis;var h,d;const u=null!==(d=null!==(h=null!==WebSocket&&void 0!==WebSocket?WebSocket:null===global||void 0===global?void 0:global.WebSocket)&&void 0!==h?h:null===window||void 0===window?void 0:window.WebSocket)&&void 0!==d?d:null===self||void 0===self?void 0:self.WebSocket,m=(e,t)=>t?new u(e,t):new u(e),p={[4e3]:"Sample rate must be a positive integer",[4001]:"Not Authorized",[4002]:"Insufficient funds",[4003]:"This feature is paid-only and requires you to add a credit card. Please visit https://app.assemblyai.com/ to add a credit card to your account.",[4004]:"Session ID does not exist",[4008]:"Session has expired",[4010]:"Session is closed",[4029]:"Rate limited",[4030]:"Unique session violation",[4031]:"Session Timeout",[4032]:"Audio too short",[4033]:"Audio too long",[4034]:"Audio too small to transcode",[4100]:"Bad JSON",[4101]:"Bad schema",[4102]:"Too many streams",[4103]:"This session has been reconnected. This WebSocket is no longer valid.",[1013]:"Reconnect attempts exhausted",[4104]:"Could not parse word boost parameter"};class f extends Error{}const v={[3005]:"Server error",[3006]:"Input validation error",[3007]:"Audio chunk duration violation",[3008]:"Session expired: maximum session duration exceeded",[3009]:"Too many concurrent sessions",[4e3]:"Sample rate must be a positive integer",[4001]:"Not Authorized",[4002]:"Insufficient funds",[4003]:"This feature is paid-only and requires you to add a credit card. Please visit https://app.assemblyai.com/ to add a credit card to your account.",[4004]:"Session ID does not exist",[4008]:"Session has expired",[4010]:"Session is closed",[4029]:"Rate limited",[4030]:"Unique session violation",[4031]:"Session Timeout",[4032]:"Audio too short",[4033]:"Audio too long",[4034]:"Audio too small to transcode",[4101]:"Bad schema",[4102]:"Too many streams",[4103]:"This session has been reconnected. This WebSocket is no longer valid."};class g extends Error{}const y='{"terminate_session":true}';class w{constructor(e){var t,s;if(this.listeners={},this.realtimeUrl=null!==(t=e.realtimeUrl)&&void 0!==t?t:"wss://api.assemblyai.com/v2/realtime/ws",this.sampleRate=null!==(s=e.sampleRate)&&void 0!==s?s:16e3,this.wordBoost=e.wordBoost,this.encoding=e.encoding,this.endUtteranceSilenceThreshold=e.endUtteranceSilenceThreshold,this.disablePartialTranscripts=e.disablePartialTranscripts,"token"in e&&e.token&&(this.token=e.token),"apiKey"in e&&e.apiKey&&(this.apiKey=e.apiKey),!this.token&&!this.apiKey)throw new Error("API key or temporary token is required.")}connectionUrl(){const e=new URL(this.realtimeUrl);if("wss:"!==e.protocol)throw new Error("Invalid protocol, must be wss");const t=new URLSearchParams;return this.token&&t.set("token",this.token),t.set("sample_rate",this.sampleRate.toString()),this.wordBoost&&this.wordBoost.length>0&&t.set("word_boost",JSON.stringify(this.wordBoost)),this.encoding&&t.set("encoding",this.encoding),t.set("enable_extra_session_information","true"),this.disablePartialTranscripts&&t.set("disable_partial_transcripts",this.disablePartialTranscripts.toString()),e.search=t.toString(),e}on(e,t){this.listeners[e]=t}connect(){return new Promise((e=>{if(this.socket)throw new Error("Already connected");const t=this.connectionUrl();this.token?this.socket=m(t.toString()):(console.warn("API key authentication is not supported for the RealtimeTranscriber in browser environment. Use temporary token authentication instead.\nLearn more at https://github.com/AssemblyAI/assemblyai-node-sdk/blob/main/docs/compat.md#browser-compatibility."),this.socket=m(t.toString(),{headers:{Authorization:this.apiKey}})),this.socket.binaryType="arraybuffer",this.socket.onopen=()=>{void 0!==this.endUtteranceSilenceThreshold&&null!==this.endUtteranceSilenceThreshold&&this.configureEndUtteranceSilenceThreshold(this.endUtteranceSilenceThreshold)},this.socket.onclose=({code:e,reason:t})=>{var s,n;t||e in p&&(t=p[e]),null===(n=(s=this.listeners).close)||void 0===n||n.call(s,e,t)},this.socket.onerror=e=>{var t,s,n,i;e.error?null===(s=(t=this.listeners).error)||void 0===s||s.call(t,e.error):null===(i=(n=this.listeners).error)||void 0===i||i.call(n,new Error(e.message))},this.socket.onmessage=({data:t})=>{var s,n,i,r,o,a,l,c,h,d,u,m,p,v,g;const y=JSON.parse(t.toString());if("error"in y)null===(n=(s=this.listeners).error)||void 0===n||n.call(s,new f(y.error));else switch(y.message_type){case"SessionBegins":{const t={sessionId:y.session_id,expiresAt:new Date(y.expires_at)};e(t),null===(r=(i=this.listeners).open)||void 0===r||r.call(i,t);break}case"PartialTranscript":y.created=new Date(y.created),null===(a=(o=this.listeners).transcript)||void 0===a||a.call(o,y),null===(c=(l=this.listeners)["transcript.partial"])||void 0===c||c.call(l,y);break;case"FinalTranscript":y.created=new Date(y.created),null===(d=(h=this.listeners).transcript)||void 0===d||d.call(h,y),null===(m=(u=this.listeners)["transcript.final"])||void 0===m||m.call(u,y);break;case"SessionInformation":null===(v=(p=this.listeners).session_information)||void 0===v||v.call(p,y);break;case"SessionTerminated":null===(g=this.sessionTerminatedResolve)||void 0===g||g.call(this)}}}))}sendAudio(e){this.send(e)}stream(){return new c({write:e=>{this.sendAudio(e)}})}forceEndUtterance(){this.send('{"force_end_utterance":true}')}configureEndUtteranceSilenceThreshold(e){this.send(`{"end_utterance_silence_threshold":${e}}`)}send(e){if(!this.socket||this.socket.readyState!==this.socket.OPEN)throw new Error("Socket is not open for communication");this.socket.send(e)}close(){return n(this,arguments,void 0,(function*(e=!0){var t;if(this.socket){if(this.socket.readyState===this.socket.OPEN)if(e){const e=new Promise((e=>{this.sessionTerminatedResolve=e}));this.socket.send(y),yield e}else this.socket.send(y);(null===(t=this.socket)||void 0===t?void 0:t.removeAllListeners)&&this.socket.removeAllListeners(),this.socket.close()}this.listeners={},this.socket=void 0}))}}class b extends a{constructor(e){super(e),this.rtFactoryParams=e}createService(e){return this.transcriber(e)}transcriber(e){const t=Object.assign({},e);return t.token||t.apiKey||(t.apiKey=this.rtFactoryParams.apiKey),new w(t)}createTemporaryToken(e){return n(this,void 0,void 0,(function*(){return(yield this.fetchJson("/v2/realtime/token",{method:"POST",body:JSON.stringify(e)})).token}))}}function S(e){return e.startsWith("http")||e.startsWith("https")||e.startsWith("data:")?null:e.startsWith("file://")?e.substring(7):e.startsWith("file:")?e.substring(5):e}class k extends a{constructor(e,t){super(e),this.files=t}transcribe(e,t){return n(this,void 0,void 0,(function*(){const s=yield this.submit(e);return yield this.waitUntilReady(s.id,t)}))}submit(e){return n(this,void 0,void 0,(function*(){let t,n;if("audio"in e){const{audio:i}=e,r=s(e,["audio"]);if("string"==typeof i){const e=S(i);t=null!==e?yield this.files.upload(e):i.startsWith("data:")?yield this.files.upload(i):i}else t=yield this.files.upload(i);n=Object.assign(Object.assign({},r),{audio_url:t})}else n=e;return yield this.fetchJson("/v2/transcript",{method:"POST",body:JSON.stringify(n)})}))}create(e,t){return n(this,void 0,void 0,(function*(){var s;const n=S(e.audio_url);if(null!==n){const t=yield this.files.upload(n);e.audio_url=t}const i=yield this.fetchJson("/v2/transcript",{method:"POST",body:JSON.stringify(e)});return null===(s=null==t?void 0:t.poll)||void 0===s||s?yield this.waitUntilReady(i.id,t):i}))}waitUntilReady(e,t){return n(this,void 0,void 0,(function*(){var s,n;const i=null!==(s=null==t?void 0:t.pollingInterval)&&void 0!==s?s:3e3,r=null!==(n=null==t?void 0:t.pollingTimeout)&&void 0!==n?n:-1,o=Date.now();for(;;){const t=yield this.get(e);if("completed"===t.status||"error"===t.status)return t;if(r>0&&Date.now()-o>r)throw new Error("Polling timeout");yield new Promise((e=>setTimeout(e,i)))}}))}get(e){return this.fetchJson(`/v2/transcript/${e}`)}list(e){return n(this,void 0,void 0,(function*(){let t="/v2/transcript";"string"==typeof e?t=e:e&&(t=`${t}?${new URLSearchParams(Object.keys(e).map((t=>{var s;return[t,(null===(s=e[t])||void 0===s?void 0:s.toString())||""]})))}`);const s=yield this.fetchJson(t);for(const e of s.transcripts)e.created=new Date(e.created),e.completed&&(e.completed=new Date(e.completed));return s}))}delete(e){return this.fetchJson(`/v2/transcript/${e}`,{method:"DELETE"})}wordSearch(e,t){const s=new URLSearchParams({words:t.join(",")});return this.fetchJson(`/v2/transcript/${e}/word-search?${s.toString()}`)}sentences(e){return this.fetchJson(`/v2/transcript/${e}/sentences`)}paragraphs(e){return this.fetchJson(`/v2/transcript/${e}/paragraphs`)}subtitles(e){return n(this,arguments,void 0,(function*(e,t="srt",s){let n=`/v2/transcript/${e}/${t}`;if(s){const e=new URLSearchParams;e.set("chars_per_caption",s.toString()),n+=`?${e.toString()}`}const i=yield this.fetch(n);return yield i.text()}))}redactions(e){return this.redactedAudio(e)}redactedAudio(e){return this.fetchJson(`/v2/transcript/${e}/redacted-audio`)}redactedAudioFile(e){return n(this,void 0,void 0,(function*(){const{redacted_audio_url:t,status:s}=yield this.redactedAudio(e);if("redacted_audio_ready"!==s)throw new Error(`Redacted audio status is ${s}`);const n=yield fetch(t);if(!n.ok)throw new Error(`Failed to fetch redacted audio: ${n.statusText}`);return{arrayBuffer:n.arrayBuffer.bind(n),blob:n.blob.bind(n),body:n.body,bodyUsed:n.bodyUsed}}))}}class _ extends a{upload(e){return n(this,void 0,void 0,(function*(){let t;t="string"==typeof e?e.startsWith("data:")?function(e){const t=e.split(","),s=t[0].match(/:(.*?);/)[1],n=atob(t[1]);let i=n.length;const r=new Uint8Array(i);for(;i--;)r[i]=n.charCodeAt(i);return new Blob([r],{type:s})}(e):yield function(e){return n(this,void 0,void 0,(function*(){throw new Error("Interacting with the file system is not supported in this environment.")}))}():e;return(yield this.fetchJson("/v2/upload",{method:"POST",body:t,headers:{"Content-Type":"application/octet-stream"},duplex:"half"})).upload_url}))}}class T{constructor(e={}){var t,s,n,i;this.hangoverRemaining=0,this.thresholdRatio=null!==(t=e.thresholdRatio)&&void 0!==t?t:3,this.noiseFloorAlpha=null!==(s=e.noiseFloorAlpha)&&void 0!==s?s:.05,this.hangoverFrames=null!==(n=e.hangoverFrames)&&void 0!==n?n:10,this.initialNoiseFloor=null!==(i=e.initialNoiseFloor)&&void 0!==i?i:1e-4,this.noiseFloor=this.initialNoiseFloor}process(e){let t=0;for(let s=0;s<e.length;s++)t+=e[s]*e[s];const s=e.length>0?Math.sqrt(t/e.length):0;let n=s>this.noiseFloor*this.thresholdRatio;return n?this.hangoverRemaining=this.hangoverFrames:this.hangoverRemaining>0?(this.hangoverRemaining--,n=!0):this.noiseFloor=this.noiseFloor*(1-this.noiseFloorAlpha)+s*this.noiseFloorAlpha,{active:n,energy:s}}reset(){this.noiseFloor=this.initialNoiseFloor,this.hangoverRemaining=0}}class A{constructor(e){this.windowMs=e,this.frames=[],this.head=0}pushFrame(e){this.frames.push(e);const t=e.ts-this.windowMs;for(;this.head<this.frames.length&&this.frames[this.head].ts<t;)this.head++;this.head>1024&&2*this.head>this.frames.length&&(this.frames=this.frames.slice(this.head),this.head=0)}framesInWindow(e,t){const s=[];for(let n=this.head;n<this.frames.length;n++){const i=this.frames[n];if(!(i.ts<e)){if(i.ts>t)break;s.push(i)}}return s}clear(){this.frames=[],this.head=0}}function P(e,t,s){const n=function(e){var t;const s=new Map;for(const n of e)n.active&&s.set(n.channel,(null!==(t=s.get(n.channel))&&void 0!==t?t:0)+n.rms);return s}(t.framesInWindow(e.start,e.end));if(0===n.size)return"unknown";const i=[...n.entries()].sort(((e,t)=>t[1]-e[1]));if(1===i.length)return i[0][0];const[r,o]=i[0],[a,l]=i[1];return o>=s.dominanceRatio*l||o>l?r:l>o?a:"unknown"}function x(e){var t;const s=new Map;for(const n of e){if(!n.channel||"unknown"===n.channel)continue;const e=Math.max(0,n.end-n.start);s.set(n.channel,(null!==(t=s.get(n.channel))&&void 0!==t?t:0)+e)}if(0===s.size)return"unknown";const n=[...s.entries()].sort(((e,t)=>t[1]-e[1]));if(1===n.length)return n[0][0];const[i,r]=n[0],[,o]=n[1];return r===o?"unknown":i}function R(e,t,s){for(const n of e.words)n.channel=P(n,t,s);e.channel=x(e.words)}const O='{"type":"Terminate"}';class E{constructor(e){var t,s,n,i,r,o,a,l,c;if(this.listeners={},this.isDualChannel=!1,this.vadFrameSamples=0,this.minChunkSamples=0,this.maxChunkSamples=0,this.params=Object.assign(Object.assign({},e),{websocketBaseUrl:e.websocketBaseUrl||"wss://streaming.assemblyai.com/v3/ws"}),"token"in e&&e.token&&(this.token=e.token),"apiKey"in e&&e.apiKey&&(this.apiKey=e.apiKey),!this.token&&!this.apiKey)throw new Error("API key or temporary token is required.");if(e.channels){if(2!==e.channels.length)throw new Error("StreamingTranscriber.channels must have exactly 2 entries.");const h=e.channels.map((e=>e.name));if(new Set(h).size!==h.length)throw new Error("StreamingTranscriber.channels names must be unique.");this.isDualChannel=!0,this.channelNames=h;const d=null!==(t=e.channelAttribution)&&void 0!==t?t:{};this.attributionParams={dominanceRatio:null!==(s=d.dominanceRatio)&&void 0!==s?s:4,timelineWindowMs:null!==(n=d.timelineWindowMs)&&void 0!==n?n:3e4,createVad:null!==(i=d.createVad)&&void 0!==i?i:()=>new T,flushIntervalMs:null!==(r=d.flushIntervalMs)&&void 0!==r?r:50,resolveUnknownChannelsMethod:null!==(o=d.resolveUnknownChannelsMethod)&&void 0!==o?o:"window",resolutionWindowWords:null!==(a=d.resolutionWindowWords)&&void 0!==a?a:2,speakerHistoryMinRmsEvidence:null!==(l=d.speakerHistoryMinRmsEvidence)&&void 0!==l?l:.5,speakerHistoryDominanceRatio:null!==(c=d.speakerHistoryDominanceRatio)&&void 0!==c?c:3},"speaker-history"===this.attributionParams.resolveUnknownChannelsMethod&&(this.speakerHistory=new Map),this.vadFrameSamples=Math.max(1,Math.round(.02*e.sampleRate)),this.minChunkSamples=Math.max(1,Math.round(.05*e.sampleRate)),this.maxChunkSamples=Math.max(this.minChunkSamples,Math.round(.2*e.sampleRate)),this.channelBuffers=new Map(h.map((e=>[e,[]]))),this.channelSamplesReceived=new Map(h.map((e=>[e,0]))),this.channelVadFloatBuffers=new Map(h.map((e=>[e,new Float32Array(this.vadFrameSamples)]))),this.channelVadBufferIdx=new Map(h.map((e=>[e,0]))),this.channelVads=new Map(h.map((e=>[e,this.attributionParams.createVad(e)]))),this.timeline=new A(this.attributionParams.timelineWindowMs)}}connectionUrl(){var e,t;const s=new URL(null!==(e=this.params.websocketBaseUrl)&&void 0!==e?e:"");if("wss:"!==s.protocol)throw new Error("Invalid protocol, must be wss");const n=new URLSearchParams;this.token&&n.set("token",this.token),n.set("sample_rate",this.params.sampleRate.toString()),this.params.endOfTurnConfidenceThreshold&&n.set("end_of_turn_confidence_threshold",this.params.endOfTurnConfidenceThreshold.toString()),void 0!==this.params.minEndOfTurnSilenceWhenConfident&&(void 0!==this.params.minTurnSilence?console.warn("[Deprecation Warning] Both `minEndOfTurnSilenceWhenConfident` and `minTurnSilence` are set. Using `minTurnSilence`; `minEndOfTurnSilenceWhenConfident` is deprecated."):console.warn("[Deprecation Warning] `minEndOfTurnSilenceWhenConfident` is deprecated and will be removed in a future release. Please use `minTurnSilence` instead."));const i=null!==(t=this.params.minTurnSilence)&&void 0!==t?t:this.params.minEndOfTurnSilenceWhenConfident;return void 0!==i&&n.set("min_turn_silence",i.toString()),this.params.maxTurnSilence&&n.set("max_turn_silence",this.params.maxTurnSilence.toString()),void 0!==this.params.vadThreshold&&n.set("vad_threshold",this.params.vadThreshold.toString()),this.params.formatTurns&&n.set("format_turns",this.params.formatTurns.toString()),this.params.encoding&&n.set("encoding",this.params.encoding.toString()),this.params.keytermsPrompt?n.set("keyterms_prompt",JSON.stringify(this.params.keytermsPrompt)):this.params.keyterms&&(console.warn("[Deprecation Warning] `keyterms` is deprecated and will be removed in a future release. Please use `keytermsPrompt` instead."),n.set("keyterms_prompt",JSON.stringify(this.params.keyterms))),this.params.prompt&&n.set("prompt",this.params.prompt),this.params.agentContext&&n.set("agent_context",this.params.agentContext),this.params.filterProfanity&&n.set("filter_profanity",this.params.filterProfanity.toString()),"u3-pro"===this.params.speechModel&&console.warn("[Deprecation Warning] The speech model `u3-pro` is deprecated and will be removed in a future release. Please use `u3-rt-pro` instead."),void 0!==this.params.speechModel&&n.set("speech_model",this.params.speechModel.toString()),void 0!==this.params.languageCode&&n.set("language_code",this.params.languageCode),void 0!==this.params.languageDetection&&n.set("language_detection",this.params.languageDetection.toString()),this.params.domain&&n.set("domain",this.params.domain),void 0!==this.params.inactivityTimeout&&n.set("inactivity_timeout",this.params.inactivityTimeout.toString()),void 0!==this.params.speakerLabels&&n.set("speaker_labels",this.params.speakerLabels.toString()),void 0!==this.params.maxSpeakers&&n.set("max_speakers",this.params.maxSpeakers.toString()),this.params.voiceFocus&&n.set("voice_focus",this.params.voiceFocus),void 0!==this.params.voiceFocusThreshold&&n.set("voice_focus_threshold",this.params.voiceFocusThreshold.toString()),void 0!==this.params.continuousPartials&&n.set("continuous_partials",this.params.continuousPartials.toString()),void 0!==this.params.interruptionDelay&&n.set("interruption_delay",this.params.interruptionDelay.toString()),void 0!==this.params.turnLeftPadMs&&n.set("turn_left_pad_ms",this.params.turnLeftPadMs.toString()),this.params.customerSupportAudioCapture&&(console.warn("`customerSupportAudioCapture=true` will record session audio. Only enable this when explicitly coordinating with AssemblyAI support."),n.set("_customer_support_audio_capture",this.params.customerSupportAudioCapture.toString())),this.params.webhookUrl&&n.set("webhook_url",this.params.webhookUrl),this.params.webhookAuthHeaderName&&n.set("webhook_auth_header_name",this.params.webhookAuthHeaderName),this.params.webhookAuthHeaderValue&&n.set("webhook_auth_header_value",this.params.webhookAuthHeaderValue),void 0!==this.params.includePartialTurns&&n.set("include_partial_turns",this.params.includePartialTurns.toString()),void 0!==this.params.redactPii&&n.set("redact_pii",this.params.redactPii.toString()),void 0!==this.params.redactPiiPolicies&&n.set("redact_pii_policies",JSON.stringify(this.params.redactPiiPolicies)),void 0!==this.params.redactPiiSub&&n.set("redact_pii_sub",this.params.redactPiiSub),void 0!==this.params.mode&&n.set("mode",this.params.mode),void 0!==this.params.llmGateway&&n.set("llm_gateway",JSON.stringify(this.params.llmGateway)),s.search=n.toString(),s}on(e,t){this.listeners[e]=t}connect(){return new Promise((e=>{if(this.socket)throw new Error("Already connected");const t=this.connectionUrl();this.token?this.socket=m(t.toString()):(console.warn("API key authentication is not supported for the StreamingTranscriber in browser environment. Use temporary token authentication instead.\nLearn more at https://github.com/AssemblyAI/assemblyai-node-sdk/blob/main/docs/compat.md#browser-compatibility."),this.socket=m(t.toString(),{headers:{Authorization:this.apiKey}})),this.socket.binaryType="arraybuffer",this.socket.onopen=()=>{},this.socket.onclose=({code:e,reason:t})=>{var s,n;t||e in v&&(t=v[e]),this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=void 0),null===(n=(s=this.listeners).close)||void 0===n||n.call(s,e,t)},this.socket.onerror=e=>{var t,s,n,i;e.error?null===(s=(t=this.listeners).error)||void 0===s||s.call(t,e.error):null===(i=(n=this.listeners).error)||void 0===i||i.call(n,new Error(e.message))},this.socket.onmessage=({data:t})=>{var s,n,i,r,o,a,l,c,h,d,u,m,p,f,v;const y=JSON.parse(t.toString());if("error"in y){const e=new g(y.error);return"error_code"in y&&(e.code=y.error_code),void(null===(n=(s=this.listeners).error)||void 0===n||n.call(s,e))}switch(y.type){case"Begin":e(y),null===(r=(i=this.listeners).open)||void 0===r||r.call(i,y);break;case"Turn":if(this.isDualChannel&&this.timeline&&this.attributionParams)switch(R(y,this.timeline,{dominanceRatio:this.attributionParams.dominanceRatio}),this.attributionParams.resolveUnknownChannelsMethod){case"window":this.resolveUnknownChannelsByWindow(y);break;case"speaker-history":this.resolveUnknownChannelsBySpeakerHistory(y)}null===(a=(o=this.listeners).turn)||void 0===a||a.call(o,y);break;case"SpeechStarted":null===(c=(l=this.listeners).speechStarted)||void 0===c||c.call(l,y);break;case"LLMGatewayResponse":null===(d=(h=this.listeners).llmGatewayResponse)||void 0===d||d.call(h,y);break;case"SpeakerRevision":null===(m=(u=this.listeners).speakerRevision)||void 0===m||m.call(u,y);break;case"Warning":{const e=y;console.warn(`Streaming warning (code=${e.warning_code}): ${e.warning}`),null===(f=(p=this.listeners).warning)||void 0===f||f.call(p,e);break}case"Termination":null===(v=this.sessionTerminatedResolve)||void 0===v||v.call(this)}}}))}stream(){return new c({write:e=>{this.sendAudio(e)}})}sendAudio(e,t){if(this.isDualChannel){if(!(null==t?void 0:t.channel))throw new Error("StreamingTranscriber is in dual-channel mode; sendAudio requires { channel }.");if(!this.channelNames.includes(t.channel))throw new Error(`Unknown channel "${t.channel}"; declared channels: ${this.channelNames.join(", ")}.`);this.ingestChannelAudio(t.channel,e)}else this.send(e)}ingestChannelAudio(e,t){var s,n;const i=function(e){if(e instanceof Int16Array)return e;if(ArrayBuffer.isView(e)){const t=e;return new Int16Array(t.buffer,t.byteOffset,Math.floor(t.byteLength/2))}return new Int16Array(e)}(t),r=this.channelBuffers.get(e),o=this.channelVadFloatBuffers.get(e);let a=this.channelVadBufferIdx.get(e),l=this.channelSamplesReceived.get(e);const c=this.channelVads.get(e),h=this.params.sampleRate,d=this.vadFrameSamples;for(let t=0;t<i.length;t++){const u=i[t];if(r.push(u),o[a++]=u/32768,l++,a===d){const t=c.process(o),i={ts:l/h*1e3,channel:e,active:t.active,rms:t.energy};this.timeline.pushFrame(i),null===(n=(s=this.listeners).vad)||void 0===n||n.call(s,i),a=0}}this.channelVadBufferIdx.set(e,a),this.channelSamplesReceived.set(e,l),this.flushTimer||this.startFlushTimer()}startFlushTimer(){this.flushTimer=setInterval((()=>this.flushMix()),this.attributionParams.flushIntervalMs)}flushMix(e=!1){var t,s;if(!this.channelNames||!this.channelBuffers)return;const n=this.channelNames.map((e=>this.channelBuffers.get(e))),i=n.length;for(;;){let r=1/0;for(const e of n)e.length<r&&(r=e.length);if(!Number.isFinite(r)||0===r)return;if(!e&&r<this.minChunkSamples)return;r>this.maxChunkSamples&&(r=this.maxChunkSamples);const o=new Int16Array(r);for(let e=0;e<r;e++){let t=0;for(let s=0;s<i;s++)t+=n[s][e];const s=Math.round(t/i);o[e]=s<-32768?-32768:s>32767?32767:s}for(const e of n)e.splice(0,r);try{this.send(o.buffer)}catch(e){return void(null===(s=(t=this.listeners).error)||void 0===s||s.call(t,e))}}}resolveUnknownChannelsByWindow(e){var t;if(!this.attributionParams)return;const s=this.attributionParams.resolutionWindowWords,n=e.words;let i=!1;for(let e=0;e<n.length;e++){if("unknown"!==n[e].channel)continue;const r=new Map,o=Math.max(0,e-s),a=Math.min(n.length-1,e+s);for(let s=o;s<=a;s++){if(s===e)continue;const i=n[s].channel;i&&"unknown"!==i&&r.set(i,(null!==(t=r.get(i))&&void 0!==t?t:0)+1)}if(0===r.size)continue;let l,c=0,h=!1;for(const[e,t]of r)t>c?(l=e,c=t,h=!1):t===c&&(h=!0);l&&!h&&(n[e].channel=l,n[e].channelResolved=!0,i=!0)}i&&(e.channel=x(n))}resolveUnknownChannelsBySpeakerHistory(e){var t;if(!this.timeline||!this.attributionParams||!this.speakerHistory)return;const s=this.attributionParams.speakerHistoryMinRmsEvidence,n=this.attributionParams.speakerHistoryDominanceRatio;for(const s of e.words){if(!s.speaker)continue;const e=this.timeline.framesInWindow(s.start,s.end);let n=this.speakerHistory.get(s.speaker);n||(n=new Map,this.speakerHistory.set(s.speaker,n));for(const s of e)s.active&&n.set(s.channel,(null!==(t=n.get(s.channel))&&void 0!==t?t:0)+s.rms)}let i=!1;for(const t of e.words){if("unknown"!==t.channel||!t.speaker)continue;const e=this.speakerHistory.get(t.speaker);if(!e||0===e.size)continue;let r,o=0,a=0,l=0;for(const[t,s]of e)o+=s,s>a?(l=a,a=s,r=t):s>l&&(l=s);o<s||(l>0&&a<n*l||r&&(t.channel=r,t.channelResolved=!0,i=!0))}i&&(e.channel=x(e.words))}updateConfiguration(e){const{min_end_of_turn_silence_when_confident:t,min_turn_silence:n}=e,i=s(e,["min_end_of_turn_silence_when_confident","min_turn_silence"]);void 0!==t&&(void 0!==n?console.warn("[Deprecation Warning] Both `min_end_of_turn_silence_when_confident` and `min_turn_silence` are set. Using `min_turn_silence`; `min_end_of_turn_silence_when_confident` is deprecated."):console.warn("[Deprecation Warning] `min_end_of_turn_silence_when_confident` is deprecated and will be removed in a future release. Please use `min_turn_silence` instead."));const r=null!=n?n:t,o=Object.assign(Object.assign({type:"UpdateConfiguration"},i),void 0!==r?{min_turn_silence:r}:{});this.send(JSON.stringify(o))}forceEndpoint(){this.send(JSON.stringify({type:"ForceEndpoint"}))}send(e){if(!this.socket||this.socket.readyState!==this.socket.OPEN)throw new Error("Socket is not open for communication");this.socket.send(e)}close(){return n(this,arguments,void 0,(function*(e=!0){var t;if(this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=void 0,this.flushMix(!0)),this.socket){if(this.socket.readyState===this.socket.OPEN)if(e){const e=new Promise((e=>{this.sessionTerminatedResolve=e}));this.socket.send(O),yield e}else this.socket.send(O);(null===(t=this.socket)||void 0===t?void 0:t.removeAllListeners)&&this.socket.removeAllListeners(),this.socket.close()}this.listeners={},this.socket=void 0}))}}class U extends a{constructor(e){super(e),this.baseServiceParams=e}transcriber(e){const t=Object.assign({},e);return t.token||t.apiKey||(t.apiKey=this.baseServiceParams.apiKey),new E(t)}createTemporaryToken(e){return n(this,void 0,void 0,(function*(){const t=new URLSearchParams;Object.entries(e).forEach((([e,s])=>{null!=s&&t.append(e,String(s))}));const s=t.toString(),n=s?`/v3/token?${s}`:"/v3/token";return(yield this.fetchJson(n,{method:"GET"})).token}))}}e.AssemblyAI=class{constructor(e){e.baseUrl=e.baseUrl||"https://api.assemblyai.com",e.baseUrl&&e.baseUrl.endsWith("/")&&(e.baseUrl=e.baseUrl.slice(0,-1)),this.files=new _(e),this.transcripts=new k(e,this.files),this.lemur=new l(e),this.realtime=new b(e),this.streaming=new U(Object.assign(Object.assign({},e),{baseUrl:e.streamingBaseUrl||"https://streaming.assemblyai.com"}))}},e.BrowserOnlyError=t,e.DualChannelCapture=class{constructor(e){var s;if(this.running=!1,void 0===globalThis.AudioContext)throw new t;this.params={micStream:e.micStream,systemStream:e.systemStream,transcriber:e.transcriber,targetSampleRate:null!==(s=e.targetSampleRate)&&void 0!==s?s:16e3}}on(e,t){"error"===e&&(this.errorListener=t)}start(){return n(this,void 0,void 0,(function*(){if(this.running)throw new Error("DualChannelCapture already started");this.context=new AudioContext;const e=new Blob(['\nclass Pcm16EncoderProcessor extends AudioWorkletProcessor {\n constructor(options) {\n super();\n const opts = (options && options.processorOptions) || {};\n this.targetRate = opts.targetRate || 16000;\n this.chunkMs = opts.chunkMs || 50;\n this.ratio = sampleRate / this.targetRate;\n this.chunkSize = Math.round(this.targetRate * this.chunkMs / 1000);\n this.buffer = new Int16Array(this.chunkSize);\n this.bufferIdx = 0;\n this.samplesSent = 0;\n this.lastSample = 0;\n this.fractional = 0;\n }\n\n process(inputs) {\n const input = inputs[0];\n if (!input || input.length === 0 || !input[0] || input[0].length === 0) {\n return true;\n }\n const mono = input[0];\n let pos = this.fractional;\n while (pos < mono.length) {\n const i = Math.floor(pos);\n const frac = pos - i;\n const a = i === 0 ? this.lastSample : mono[i - 1];\n const b = mono[i];\n const sample = a + (b - a) * frac;\n const clamped = sample < -1 ? -1 : sample > 1 ? 1 : sample;\n this.buffer[this.bufferIdx++] = clamped < 0 ? clamped * 0x8000 : clamped * 0x7fff;\n if (this.bufferIdx === this.chunkSize) {\n const out = new Int16Array(this.chunkSize);\n out.set(this.buffer);\n this.samplesSent += this.chunkSize;\n this.port.postMessage(\n { pcm: out.buffer, samplesSent: this.samplesSent },\n [out.buffer],\n );\n this.bufferIdx = 0;\n }\n pos += this.ratio;\n }\n this.lastSample = mono[mono.length - 1];\n this.fractional = pos - mono.length;\n return true;\n }\n}\nregisterProcessor("aai-pcm16-encoder", Pcm16EncoderProcessor);\n'],{type:"application/javascript"}),t=URL.createObjectURL(e);try{yield this.context.audioWorklet.addModule(t)}finally{URL.revokeObjectURL(t)}this.micSource=this.context.createMediaStreamSource(this.params.micStream),this.sysSource=this.context.createMediaStreamSource(this.params.systemStream),this.micEncoder=this.makeEncoder("mic"),this.sysEncoder=this.makeEncoder("system"),this.micSource.connect(this.micEncoder),this.sysSource.connect(this.sysEncoder),this.running=!0}))}makeEncoder(e){const t=new AudioWorkletNode(this.context,"aai-pcm16-encoder",{numberOfInputs:1,numberOfOutputs:0,channelCount:1,channelCountMode:"explicit",channelInterpretation:"speakers",processorOptions:{targetRate:this.params.targetSampleRate,chunkMs:50}});return t.port.onmessage=t=>{var s;try{this.params.transcriber.sendAudio(t.data.pcm,{channel:e})}catch(e){null===(s=this.errorListener)||void 0===s||s.call(this,e)}},t}stop(){return n(this,void 0,void 0,(function*(){var e,t,s,n,i,r;if(this.running){this.running=!1;try{null===(e=this.micEncoder)||void 0===e||e.port.close(),null===(t=this.sysEncoder)||void 0===t||t.port.close(),null===(s=this.micEncoder)||void 0===s||s.disconnect(),null===(n=this.sysEncoder)||void 0===n||n.disconnect(),null===(i=this.micSource)||void 0===i||i.disconnect(),null===(r=this.sysSource)||void 0===r||r.disconnect()}catch(e){}this.context&&"closed"!==this.context.state&&(yield this.context.close()),this.context=void 0,this.micSource=void 0,this.sysSource=void 0,this.micEncoder=void 0,this.sysEncoder=void 0}}))}},e.EnergyVad=T,e.FileService=_,e.LemurService=l,e.LinearResampler=class{constructor(e,t){if(this.sourceRate=e,this.targetRate=t,this.lastSample=0,this.fractional=0,e<=0||t<=0)throw new Error("sourceRate and targetRate must be positive");this.ratio=e/t}process(e){var t;if(this.sourceRate===this.targetRate)return e;const s=new Float32Array(Math.ceil(e.length/this.ratio)+1);let n=0,i=this.fractional;for(;i<e.length;){const t=Math.floor(i),r=i-t,o=0===t?this.lastSample:e[t-1],a=e[t];s[n++]=o+(a-o)*r,i+=this.ratio}return this.lastSample=null!==(t=e[e.length-1])&&void 0!==t?t:this.lastSample,this.fractional=i-e.length,s.subarray(0,n)}reset(){this.lastSample=0,this.fractional=0}},e.RealtimeService=class extends w{},e.RealtimeServiceFactory=class extends b{},e.RealtimeTranscriber=w,e.RealtimeTranscriberFactory=b,e.StreamingTranscriber=E,e.TranscriptService=k,e.VadTimeline=A,e.attributeTurn=R,e.attributeWord=P,e.float32ToPcm16=function(e){const t=new ArrayBuffer(2*e.length),s=new DataView(t);for(let t=0;t<e.length;t++){const n=Math.max(-1,Math.min(1,e[t]));s.setInt16(2*t,n<0?32768*n:32767*n,!0)}return t},e.rollUpTurnChannel=x}));
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).assemblyai={})}(this,(function(e){"use strict";class t extends Error{constructor(e="DualChannelCapture requires a browser environment (AudioContext is undefined)."){super(e),this.name="BrowserOnlyError"}}function s(e,t){var s={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&t.indexOf(n)<0&&(s[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(n=Object.getOwnPropertySymbols(e);i<n.length;i++)t.indexOf(n[i])<0&&Object.prototype.propertyIsEnumerable.call(e,n[i])&&(s[n[i]]=e[n[i]])}return s}function n(e,t,s,n){return new(s||(s=Promise))((function(i,r){function o(e){try{l(n.next(e))}catch(e){r(e)}}function a(e){try{l(n.throw(e))}catch(e){r(e)}}function l(e){var t;e.done?i(e.value):(t=e.value,t instanceof s?t:new s((function(e){e(t)}))).then(o,a)}l((n=n.apply(e,t||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;const i={cache:"no-store"};let r="";"undefined"!=typeof navigator&&navigator.userAgent&&(r+=navigator.userAgent);const o={sdk:{name:"JavaScript",version:"4.35.0"}};"undefined"!=typeof process&&(process.versions.node&&-1===r.indexOf("Node")&&(o.runtime_env={name:"Node",version:process.versions.node}),process.versions.bun&&-1===r.indexOf("Bun")&&(o.runtime_env={name:"Bun",version:process.versions.bun})),"undefined"!=typeof Deno&&process.versions.bun&&-1===r.indexOf("Deno")&&(o.runtime_env={name:"Deno",version:Deno.version.deno});class a{constructor(e){var t;this.params=e,!1===e.userAgent?this.userAgent=void 0:this.userAgent=(t=e.userAgent||{},r+(!1===t?"":" AssemblyAI/1.0 ("+Object.entries(Object.assign(Object.assign({},o),t)).map((([e,t])=>t?`${e}=${t.name}/${t.version}`:"")).join(" ")+")"))}fetch(e,t){return n(this,void 0,void 0,(function*(){t=Object.assign(Object.assign({},i),t);let s={Authorization:this.params.apiKey,"Content-Type":"application/json"};(null==i?void 0:i.headers)&&(s=Object.assign(Object.assign({},s),i.headers)),(null==t?void 0:t.headers)&&(s=Object.assign(Object.assign({},s),t.headers)),this.userAgent&&(s["User-Agent"]=this.userAgent,"undefined"!=typeof window&&"chrome"in window&&(s["AssemblyAI-Agent"]=this.userAgent)),t.headers=s,e.startsWith("http")||(e=this.params.baseUrl+e);const n=yield fetch(e,t);if(n.status>=400){let e;const t=yield n.text();if(t){try{e=JSON.parse(t)}catch(e){}if(null==e?void 0:e.error)throw new Error(e.error);throw new Error(t)}throw new Error(`HTTP Error: ${n.status} ${n.statusText}`)}return n}))}fetchJson(e,t){return n(this,void 0,void 0,(function*(){return(yield this.fetch(e,t)).json()}))}}class l extends a{summary(e,t){return this.fetchJson("/lemur/v3/generate/summary",{method:"POST",body:JSON.stringify(e),signal:t})}questionAnswer(e,t){return this.fetchJson("/lemur/v3/generate/question-answer",{method:"POST",body:JSON.stringify(e),signal:t})}actionItems(e,t){return this.fetchJson("/lemur/v3/generate/action-items",{method:"POST",body:JSON.stringify(e),signal:t})}task(e,t){return this.fetchJson("/lemur/v3/generate/task",{method:"POST",body:JSON.stringify(e),signal:t})}getResponse(e,t){return this.fetchJson(`/lemur/v3/${e}`,{signal:t})}purgeRequestData(e,t){return this.fetchJson(`/lemur/v3/${e}`,{method:"DELETE",signal:t})}}const{WritableStream:c}="undefined"!=typeof window?window:"undefined"!=typeof global?global:globalThis;var h,d;const u=null!==(d=null!==(h=null!==WebSocket&&void 0!==WebSocket?WebSocket:null===global||void 0===global?void 0:global.WebSocket)&&void 0!==h?h:null===window||void 0===window?void 0:window.WebSocket)&&void 0!==d?d:null===self||void 0===self?void 0:self.WebSocket,m=(e,t)=>t?new u(e,t):new u(e),p={[4e3]:"Sample rate must be a positive integer",[4001]:"Not Authorized",[4002]:"Insufficient funds",[4003]:"This feature is paid-only and requires you to add a credit card. Please visit https://app.assemblyai.com/ to add a credit card to your account.",[4004]:"Session ID does not exist",[4008]:"Session has expired",[4010]:"Session is closed",[4029]:"Rate limited",[4030]:"Unique session violation",[4031]:"Session Timeout",[4032]:"Audio too short",[4033]:"Audio too long",[4034]:"Audio too small to transcode",[4100]:"Bad JSON",[4101]:"Bad schema",[4102]:"Too many streams",[4103]:"This session has been reconnected. This WebSocket is no longer valid.",[1013]:"Reconnect attempts exhausted",[4104]:"Could not parse word boost parameter"};class f extends Error{}const v=4e3,g=4001,y=4002,w=4003,b=4101,S={[3005]:"Server error",[3006]:"Input validation error",[3007]:"Audio chunk duration violation",[3008]:"Session expired: maximum session duration exceeded",[3009]:"Too many concurrent sessions",[v]:"Sample rate must be a positive integer",[g]:"Not Authorized",[y]:"Insufficient funds",[w]:"This feature is paid-only and requires you to add a credit card. Please visit https://app.assemblyai.com/ to add a credit card to your account.",[4004]:"Session ID does not exist",[4008]:"Session has expired",[4010]:"Session is closed",[4029]:"Rate limited",[4030]:"Unique session violation",[4031]:"Session Timeout",[4032]:"Audio too short",[4033]:"Audio too long",[4034]:"Audio too small to transcode",[b]:"Bad schema",[4102]:"Too many streams",[4103]:"This session has been reconnected. This WebSocket is no longer valid."};class k extends Error{}const _='{"terminate_session":true}';class T{constructor(e){var t,s;if(this.listeners={},this.realtimeUrl=null!==(t=e.realtimeUrl)&&void 0!==t?t:"wss://api.assemblyai.com/v2/realtime/ws",this.sampleRate=null!==(s=e.sampleRate)&&void 0!==s?s:16e3,this.wordBoost=e.wordBoost,this.encoding=e.encoding,this.endUtteranceSilenceThreshold=e.endUtteranceSilenceThreshold,this.disablePartialTranscripts=e.disablePartialTranscripts,"token"in e&&e.token&&(this.token=e.token),"apiKey"in e&&e.apiKey&&(this.apiKey=e.apiKey),!this.token&&!this.apiKey)throw new Error("API key or temporary token is required.")}connectionUrl(){const e=new URL(this.realtimeUrl);if("wss:"!==e.protocol)throw new Error("Invalid protocol, must be wss");const t=new URLSearchParams;return this.token&&t.set("token",this.token),t.set("sample_rate",this.sampleRate.toString()),this.wordBoost&&this.wordBoost.length>0&&t.set("word_boost",JSON.stringify(this.wordBoost)),this.encoding&&t.set("encoding",this.encoding),t.set("enable_extra_session_information","true"),this.disablePartialTranscripts&&t.set("disable_partial_transcripts",this.disablePartialTranscripts.toString()),e.search=t.toString(),e}on(e,t){this.listeners[e]=t}connect(){return new Promise((e=>{if(this.socket)throw new Error("Already connected");const t=this.connectionUrl();this.token?this.socket=m(t.toString()):(console.warn("API key authentication is not supported for the RealtimeTranscriber in browser environment. Use temporary token authentication instead.\nLearn more at https://github.com/AssemblyAI/assemblyai-node-sdk/blob/main/docs/compat.md#browser-compatibility."),this.socket=m(t.toString(),{headers:{Authorization:this.apiKey}})),this.socket.binaryType="arraybuffer",this.socket.onopen=()=>{void 0!==this.endUtteranceSilenceThreshold&&null!==this.endUtteranceSilenceThreshold&&this.configureEndUtteranceSilenceThreshold(this.endUtteranceSilenceThreshold)},this.socket.onclose=({code:e,reason:t})=>{var s,n;t||e in p&&(t=p[e]),null===(n=(s=this.listeners).close)||void 0===n||n.call(s,e,t)},this.socket.onerror=e=>{var t,s,n,i;e.error?null===(s=(t=this.listeners).error)||void 0===s||s.call(t,e.error):null===(i=(n=this.listeners).error)||void 0===i||i.call(n,new Error(e.message))},this.socket.onmessage=({data:t})=>{var s,n,i,r,o,a,l,c,h,d,u,m,p,v,g;const y=JSON.parse(t.toString());if("error"in y)null===(n=(s=this.listeners).error)||void 0===n||n.call(s,new f(y.error));else switch(y.message_type){case"SessionBegins":{const t={sessionId:y.session_id,expiresAt:new Date(y.expires_at)};e(t),null===(r=(i=this.listeners).open)||void 0===r||r.call(i,t);break}case"PartialTranscript":y.created=new Date(y.created),null===(a=(o=this.listeners).transcript)||void 0===a||a.call(o,y),null===(c=(l=this.listeners)["transcript.partial"])||void 0===c||c.call(l,y);break;case"FinalTranscript":y.created=new Date(y.created),null===(d=(h=this.listeners).transcript)||void 0===d||d.call(h,y),null===(m=(u=this.listeners)["transcript.final"])||void 0===m||m.call(u,y);break;case"SessionInformation":null===(v=(p=this.listeners).session_information)||void 0===v||v.call(p,y);break;case"SessionTerminated":null===(g=this.sessionTerminatedResolve)||void 0===g||g.call(this)}}}))}sendAudio(e){this.send(e)}stream(){return new c({write:e=>{this.sendAudio(e)}})}forceEndUtterance(){this.send('{"force_end_utterance":true}')}configureEndUtteranceSilenceThreshold(e){this.send(`{"end_utterance_silence_threshold":${e}}`)}send(e){if(!this.socket||this.socket.readyState!==this.socket.OPEN)throw new Error("Socket is not open for communication");this.socket.send(e)}close(){return n(this,arguments,void 0,(function*(e=!0){var t;if(this.socket){if(this.socket.readyState===this.socket.OPEN)if(e){const e=new Promise((e=>{this.sessionTerminatedResolve=e}));this.socket.send(_),yield e}else this.socket.send(_);(null===(t=this.socket)||void 0===t?void 0:t.removeAllListeners)&&this.socket.removeAllListeners(),this.socket.close()}this.listeners={},this.socket=void 0}))}}class A extends a{constructor(e){super(e),this.rtFactoryParams=e}createService(e){return this.transcriber(e)}transcriber(e){const t=Object.assign({},e);return t.token||t.apiKey||(t.apiKey=this.rtFactoryParams.apiKey),new T(t)}createTemporaryToken(e){return n(this,void 0,void 0,(function*(){return(yield this.fetchJson("/v2/realtime/token",{method:"POST",body:JSON.stringify(e)})).token}))}}function P(e){return e.startsWith("http")||e.startsWith("https")||e.startsWith("data:")?null:e.startsWith("file://")?e.substring(7):e.startsWith("file:")?e.substring(5):e}class x extends a{constructor(e,t){super(e),this.files=t}transcribe(e,t){return n(this,void 0,void 0,(function*(){const s=yield this.submit(e);return yield this.waitUntilReady(s.id,t)}))}submit(e){return n(this,void 0,void 0,(function*(){let t,n;if("audio"in e){const{audio:i}=e,r=s(e,["audio"]);if("string"==typeof i){const e=P(i);t=null!==e?yield this.files.upload(e):i.startsWith("data:")?yield this.files.upload(i):i}else t=yield this.files.upload(i);n=Object.assign(Object.assign({},r),{audio_url:t})}else n=e;return yield this.fetchJson("/v2/transcript",{method:"POST",body:JSON.stringify(n)})}))}create(e,t){return n(this,void 0,void 0,(function*(){var s;const n=P(e.audio_url);if(null!==n){const t=yield this.files.upload(n);e.audio_url=t}const i=yield this.fetchJson("/v2/transcript",{method:"POST",body:JSON.stringify(e)});return null===(s=null==t?void 0:t.poll)||void 0===s||s?yield this.waitUntilReady(i.id,t):i}))}waitUntilReady(e,t){return n(this,void 0,void 0,(function*(){var s,n;const i=null!==(s=null==t?void 0:t.pollingInterval)&&void 0!==s?s:3e3,r=null!==(n=null==t?void 0:t.pollingTimeout)&&void 0!==n?n:-1,o=Date.now();for(;;){const t=yield this.get(e);if("completed"===t.status||"error"===t.status)return t;if(r>0&&Date.now()-o>r)throw new Error("Polling timeout");yield new Promise((e=>setTimeout(e,i)))}}))}get(e){return this.fetchJson(`/v2/transcript/${e}`)}list(e){return n(this,void 0,void 0,(function*(){let t="/v2/transcript";"string"==typeof e?t=e:e&&(t=`${t}?${new URLSearchParams(Object.keys(e).map((t=>{var s;return[t,(null===(s=e[t])||void 0===s?void 0:s.toString())||""]})))}`);const s=yield this.fetchJson(t);for(const e of s.transcripts)e.created=new Date(e.created),e.completed&&(e.completed=new Date(e.completed));return s}))}delete(e){return this.fetchJson(`/v2/transcript/${e}`,{method:"DELETE"})}wordSearch(e,t){const s=new URLSearchParams({words:t.join(",")});return this.fetchJson(`/v2/transcript/${e}/word-search?${s.toString()}`)}sentences(e){return this.fetchJson(`/v2/transcript/${e}/sentences`)}paragraphs(e){return this.fetchJson(`/v2/transcript/${e}/paragraphs`)}subtitles(e){return n(this,arguments,void 0,(function*(e,t="srt",s){let n=`/v2/transcript/${e}/${t}`;if(s){const e=new URLSearchParams;e.set("chars_per_caption",s.toString()),n+=`?${e.toString()}`}const i=yield this.fetch(n);return yield i.text()}))}redactions(e){return this.redactedAudio(e)}redactedAudio(e){return this.fetchJson(`/v2/transcript/${e}/redacted-audio`)}redactedAudioFile(e){return n(this,void 0,void 0,(function*(){const{redacted_audio_url:t,status:s}=yield this.redactedAudio(e);if("redacted_audio_ready"!==s)throw new Error(`Redacted audio status is ${s}`);const n=yield fetch(t);if(!n.ok)throw new Error(`Failed to fetch redacted audio: ${n.statusText}`);return{arrayBuffer:n.arrayBuffer.bind(n),blob:n.blob.bind(n),body:n.body,bodyUsed:n.bodyUsed}}))}}class O extends a{upload(e){return n(this,void 0,void 0,(function*(){let t;t="string"==typeof e?e.startsWith("data:")?function(e){const t=e.split(","),s=t[0].match(/:(.*?);/)[1],n=atob(t[1]);let i=n.length;const r=new Uint8Array(i);for(;i--;)r[i]=n.charCodeAt(i);return new Blob([r],{type:s})}(e):yield function(e){return n(this,void 0,void 0,(function*(){throw new Error("Interacting with the file system is not supported in this environment.")}))}():e;return(yield this.fetchJson("/v2/upload",{method:"POST",body:t,headers:{"Content-Type":"application/octet-stream"},duplex:"half"})).upload_url}))}}class R{constructor(e={}){var t,s,n,i;this.hangoverRemaining=0,this.thresholdRatio=null!==(t=e.thresholdRatio)&&void 0!==t?t:3,this.noiseFloorAlpha=null!==(s=e.noiseFloorAlpha)&&void 0!==s?s:.05,this.hangoverFrames=null!==(n=e.hangoverFrames)&&void 0!==n?n:10,this.initialNoiseFloor=null!==(i=e.initialNoiseFloor)&&void 0!==i?i:1e-4,this.noiseFloor=this.initialNoiseFloor}process(e){let t=0;for(let s=0;s<e.length;s++)t+=e[s]*e[s];const s=e.length>0?Math.sqrt(t/e.length):0;let n=s>this.noiseFloor*this.thresholdRatio;return n?this.hangoverRemaining=this.hangoverFrames:this.hangoverRemaining>0?(this.hangoverRemaining--,n=!0):this.noiseFloor=this.noiseFloor*(1-this.noiseFloorAlpha)+s*this.noiseFloorAlpha,{active:n,energy:s}}reset(){this.noiseFloor=this.initialNoiseFloor,this.hangoverRemaining=0}}class E{constructor(e){this.windowMs=e,this.frames=[],this.head=0}pushFrame(e){this.frames.push(e);const t=e.ts-this.windowMs;for(;this.head<this.frames.length&&this.frames[this.head].ts<t;)this.head++;this.head>1024&&2*this.head>this.frames.length&&(this.frames=this.frames.slice(this.head),this.head=0)}framesInWindow(e,t){const s=[];for(let n=this.head;n<this.frames.length;n++){const i=this.frames[n];if(!(i.ts<e)){if(i.ts>t)break;s.push(i)}}return s}clear(){this.frames=[],this.head=0}}function U(e,t,s){const n=function(e){var t;const s=new Map;for(const n of e)n.active&&s.set(n.channel,(null!==(t=s.get(n.channel))&&void 0!==t?t:0)+n.rms);return s}(t.framesInWindow(e.start,e.end));if(0===n.size)return"unknown";const i=[...n.entries()].sort(((e,t)=>t[1]-e[1]));if(1===i.length)return i[0][0];const[r,o]=i[0],[a,l]=i[1];return o>=s.dominanceRatio*l||o>l?r:l>o?a:"unknown"}function M(e){var t;const s=new Map;for(const n of e){if(!n.channel||"unknown"===n.channel)continue;const e=Math.max(0,n.end-n.start);s.set(n.channel,(null!==(t=s.get(n.channel))&&void 0!==t?t:0)+e)}if(0===s.size)return"unknown";const n=[...s.entries()].sort(((e,t)=>t[1]-e[1]));if(1===n.length)return n[0][0];const[i,r]=n[0],[,o]=n[1];return r===o?"unknown":i}function C(e,t,s){for(const n of e.words)n.channel=U(n,t,s);e.channel=M(e.words)}const I='{"type":"Terminate"}',W=new Set([v,g,y,w,b]);function F(e){return 1e3!==e&&!W.has(e)}class N{constructor(e){var t,s,n,i,r,o,a,l,c;if(this.listeners={},this.isDualChannel=!1,this.vadFrameSamples=0,this.minChunkSamples=0,this.maxChunkSamples=0,this.params=Object.assign(Object.assign({},e),{websocketBaseUrl:e.websocketBaseUrl||"wss://streaming.assemblyai.com/v3/ws"}),"token"in e&&e.token&&(this.token=e.token),"apiKey"in e&&e.apiKey&&(this.apiKey=e.apiKey),!this.token&&!this.apiKey)throw new Error("API key or temporary token is required.");if(e.channels){if(2!==e.channels.length)throw new Error("StreamingTranscriber.channels must have exactly 2 entries.");const h=e.channels.map((e=>e.name));if(new Set(h).size!==h.length)throw new Error("StreamingTranscriber.channels names must be unique.");this.isDualChannel=!0,this.channelNames=h;const d=null!==(t=e.channelAttribution)&&void 0!==t?t:{};this.attributionParams={dominanceRatio:null!==(s=d.dominanceRatio)&&void 0!==s?s:4,timelineWindowMs:null!==(n=d.timelineWindowMs)&&void 0!==n?n:3e4,createVad:null!==(i=d.createVad)&&void 0!==i?i:()=>new R,flushIntervalMs:null!==(r=d.flushIntervalMs)&&void 0!==r?r:50,resolveUnknownChannelsMethod:null!==(o=d.resolveUnknownChannelsMethod)&&void 0!==o?o:"window",resolutionWindowWords:null!==(a=d.resolutionWindowWords)&&void 0!==a?a:2,speakerHistoryMinRmsEvidence:null!==(l=d.speakerHistoryMinRmsEvidence)&&void 0!==l?l:.5,speakerHistoryDominanceRatio:null!==(c=d.speakerHistoryDominanceRatio)&&void 0!==c?c:3},"speaker-history"===this.attributionParams.resolveUnknownChannelsMethod&&(this.speakerHistory=new Map),this.vadFrameSamples=Math.max(1,Math.round(.02*e.sampleRate)),this.minChunkSamples=Math.max(1,Math.round(.05*e.sampleRate)),this.maxChunkSamples=Math.max(this.minChunkSamples,Math.round(.2*e.sampleRate)),this.channelBuffers=new Map(h.map((e=>[e,[]]))),this.channelSamplesReceived=new Map(h.map((e=>[e,0]))),this.channelVadFloatBuffers=new Map(h.map((e=>[e,new Float32Array(this.vadFrameSamples)]))),this.channelVadBufferIdx=new Map(h.map((e=>[e,0]))),this.channelVads=new Map(h.map((e=>[e,this.attributionParams.createVad(e)]))),this.timeline=new E(this.attributionParams.timelineWindowMs)}}connectionUrl(){var e,t;const s=new URL(null!==(e=this.params.websocketBaseUrl)&&void 0!==e?e:"");if("wss:"!==s.protocol)throw new Error("Invalid protocol, must be wss");const n=new URLSearchParams;this.token&&n.set("token",this.token),n.set("sample_rate",this.params.sampleRate.toString()),this.params.endOfTurnConfidenceThreshold&&n.set("end_of_turn_confidence_threshold",this.params.endOfTurnConfidenceThreshold.toString()),void 0!==this.params.minEndOfTurnSilenceWhenConfident&&(void 0!==this.params.minTurnSilence?console.warn("[Deprecation Warning] Both `minEndOfTurnSilenceWhenConfident` and `minTurnSilence` are set. Using `minTurnSilence`; `minEndOfTurnSilenceWhenConfident` is deprecated."):console.warn("[Deprecation Warning] `minEndOfTurnSilenceWhenConfident` is deprecated and will be removed in a future release. Please use `minTurnSilence` instead."));const i=null!==(t=this.params.minTurnSilence)&&void 0!==t?t:this.params.minEndOfTurnSilenceWhenConfident;return void 0!==i&&n.set("min_turn_silence",i.toString()),this.params.maxTurnSilence&&n.set("max_turn_silence",this.params.maxTurnSilence.toString()),void 0!==this.params.vadThreshold&&n.set("vad_threshold",this.params.vadThreshold.toString()),this.params.formatTurns&&n.set("format_turns",this.params.formatTurns.toString()),this.params.encoding&&n.set("encoding",this.params.encoding.toString()),this.params.keytermsPrompt?n.set("keyterms_prompt",JSON.stringify(this.params.keytermsPrompt)):this.params.keyterms&&(console.warn("[Deprecation Warning] `keyterms` is deprecated and will be removed in a future release. Please use `keytermsPrompt` instead."),n.set("keyterms_prompt",JSON.stringify(this.params.keyterms))),this.params.prompt&&n.set("prompt",this.params.prompt),this.params.agentContext&&n.set("agent_context",this.params.agentContext),this.params.filterProfanity&&n.set("filter_profanity",this.params.filterProfanity.toString()),"u3-pro"===this.params.speechModel&&console.warn("[Deprecation Warning] The speech model `u3-pro` is deprecated and will be removed in a future release. Please use `u3-rt-pro` instead."),void 0!==this.params.speechModel&&n.set("speech_model",this.params.speechModel.toString()),void 0!==this.params.languageCode&&n.set("language_code",this.params.languageCode),void 0!==this.params.languageDetection&&n.set("language_detection",this.params.languageDetection.toString()),this.params.domain&&n.set("domain",this.params.domain),void 0!==this.params.inactivityTimeout&&n.set("inactivity_timeout",this.params.inactivityTimeout.toString()),void 0!==this.params.speakerLabels&&n.set("speaker_labels",this.params.speakerLabels.toString()),void 0!==this.params.maxSpeakers&&n.set("max_speakers",this.params.maxSpeakers.toString()),this.params.voiceFocus&&n.set("voice_focus",this.params.voiceFocus),void 0!==this.params.voiceFocusThreshold&&n.set("voice_focus_threshold",this.params.voiceFocusThreshold.toString()),void 0!==this.params.continuousPartials&&n.set("continuous_partials",this.params.continuousPartials.toString()),void 0!==this.params.interruptionDelay&&n.set("interruption_delay",this.params.interruptionDelay.toString()),void 0!==this.params.turnLeftPadMs&&n.set("turn_left_pad_ms",this.params.turnLeftPadMs.toString()),this.params.customerSupportAudioCapture&&(console.warn("`customerSupportAudioCapture=true` will record session audio. Only enable this when explicitly coordinating with AssemblyAI support."),n.set("_customer_support_audio_capture",this.params.customerSupportAudioCapture.toString())),this.params.webhookUrl&&n.set("webhook_url",this.params.webhookUrl),this.params.webhookAuthHeaderName&&n.set("webhook_auth_header_name",this.params.webhookAuthHeaderName),this.params.webhookAuthHeaderValue&&n.set("webhook_auth_header_value",this.params.webhookAuthHeaderValue),void 0!==this.params.includePartialTurns&&n.set("include_partial_turns",this.params.includePartialTurns.toString()),void 0!==this.params.redactPii&&n.set("redact_pii",this.params.redactPii.toString()),void 0!==this.params.redactPiiPolicies&&n.set("redact_pii_policies",JSON.stringify(this.params.redactPiiPolicies)),void 0!==this.params.redactPiiSub&&n.set("redact_pii_sub",this.params.redactPiiSub),void 0!==this.params.mode&&n.set("mode",this.params.mode),void 0!==this.params.llmGateway&&n.set("llm_gateway",JSON.stringify(this.params.llmGateway)),s.search=n.toString(),s}on(e,t){this.listeners[e]=t}connect(){return n(this,void 0,void 0,(function*(){var e,t;if(this.socket)throw new Error("Already connected");const s=null!==(e=this.params.maxConnectionRetries)&&void 0!==e?e:2,n=null!==(t=this.params.connectionRetryDelay)&&void 0!==t?t:500;let i;for(let e=0;e<=s;e++)try{return yield this.connectOnce()}catch(t){i=t;if(!(!0===t.retryable)||e===s)throw t;console.warn(`Streaming connect attempt ${e+1}/${s+1} failed (${t.message}); retrying`),n>0&&(yield new Promise((e=>setTimeout(e,n))))}throw null!=i?i:new Error("Failed to connect to streaming server")}))}connectOnce(){return new Promise(((e,t)=>{var s;const n=this.connectionUrl(),i=null!==(s=this.params.connectTimeout)&&void 0!==s?s:1e3;let r,o=!1;const a=e=>{o||(o=!0,r&&clearTimeout(r),this.discardPendingSocket(),t(e))};i>0&&(r=setTimeout((()=>{const e=new k(`Streaming connection timed out after ${i}ms`);e.retryable=!0,a(e)}),i)),this.token?this.socket=m(n.toString()):(console.warn("API key authentication is not supported for the StreamingTranscriber in browser environment. Use temporary token authentication instead.\nLearn more at https://github.com/AssemblyAI/assemblyai-node-sdk/blob/main/docs/compat.md#browser-compatibility."),this.socket=m(n.toString(),{headers:{Authorization:this.apiKey}})),this.socket.binaryType="arraybuffer",this.socket.onopen=()=>{},this.socket.onclose=({code:e,reason:t})=>{var s,n;if(t||e in S&&(t=S[e]),!o){const s=new k(t||`Streaming connection closed (code=${e})`);return s.code=e,s.retryable=F(e),void a(s)}this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=void 0),null===(n=(s=this.listeners).close)||void 0===n||n.call(s,e,t)},this.socket.onerror=e=>{var t,s,n;const i=null!==(t=e.error)&&void 0!==t?t:new Error(e.message);if(!o)return i.retryable=!0,void a(i);null===(n=(s=this.listeners).error)||void 0===n||n.call(s,i)},this.socket.onmessage=({data:t})=>{var s,n,i,l,c,h,d,u,m,p,f,v,g,y,w;const b=JSON.parse(t.toString());if("error"in b){const e=new k(b.error);if("error_code"in b&&(e.code=b.error_code),!o){const t=e;return t.retryable=void 0===e.code||F(e.code),void a(t)}null===(n=(s=this.listeners).error)||void 0===n||n.call(s,e)}else{switch(b.type){case"Begin":S=b,o||(o=!0,r&&clearTimeout(r),e(S)),null===(l=(i=this.listeners).open)||void 0===l||l.call(i,b);break;case"Turn":if(this.isDualChannel&&this.timeline&&this.attributionParams)switch(C(b,this.timeline,{dominanceRatio:this.attributionParams.dominanceRatio}),this.attributionParams.resolveUnknownChannelsMethod){case"window":this.resolveUnknownChannelsByWindow(b);break;case"speaker-history":this.resolveUnknownChannelsBySpeakerHistory(b)}null===(h=(c=this.listeners).turn)||void 0===h||h.call(c,b);break;case"SpeechStarted":null===(u=(d=this.listeners).speechStarted)||void 0===u||u.call(d,b);break;case"LLMGatewayResponse":null===(p=(m=this.listeners).llmGatewayResponse)||void 0===p||p.call(m,b);break;case"SpeakerRevision":null===(v=(f=this.listeners).speakerRevision)||void 0===v||v.call(f,b);break;case"Warning":{const e=b;console.warn(`Streaming warning (code=${e.warning_code}): ${e.warning}`),null===(y=(g=this.listeners).warning)||void 0===y||y.call(g,e);break}case"Termination":null===(w=this.sessionTerminatedResolve)||void 0===w||w.call(this)}var S}}}))}discardPendingSocket(){if(this.socket){try{this.socket.removeAllListeners&&this.socket.removeAllListeners(),this.socket.close()}catch(e){}this.socket=void 0}}stream(){return new c({write:e=>{this.sendAudio(e)}})}sendAudio(e,t){if(this.isDualChannel){if(!(null==t?void 0:t.channel))throw new Error("StreamingTranscriber is in dual-channel mode; sendAudio requires { channel }.");if(!this.channelNames.includes(t.channel))throw new Error(`Unknown channel "${t.channel}"; declared channels: ${this.channelNames.join(", ")}.`);this.ingestChannelAudio(t.channel,e)}else this.send(e)}ingestChannelAudio(e,t){var s,n;const i=function(e){if(e instanceof Int16Array)return e;if(ArrayBuffer.isView(e)){const t=e;return new Int16Array(t.buffer,t.byteOffset,Math.floor(t.byteLength/2))}return new Int16Array(e)}(t),r=this.channelBuffers.get(e),o=this.channelVadFloatBuffers.get(e);let a=this.channelVadBufferIdx.get(e),l=this.channelSamplesReceived.get(e);const c=this.channelVads.get(e),h=this.params.sampleRate,d=this.vadFrameSamples;for(let t=0;t<i.length;t++){const u=i[t];if(r.push(u),o[a++]=u/32768,l++,a===d){const t=c.process(o),i={ts:l/h*1e3,channel:e,active:t.active,rms:t.energy};this.timeline.pushFrame(i),null===(n=(s=this.listeners).vad)||void 0===n||n.call(s,i),a=0}}this.channelVadBufferIdx.set(e,a),this.channelSamplesReceived.set(e,l),this.flushTimer||this.startFlushTimer()}startFlushTimer(){this.flushTimer=setInterval((()=>this.flushMix()),this.attributionParams.flushIntervalMs)}flushMix(e=!1){var t,s;if(!this.channelNames||!this.channelBuffers)return;const n=this.channelNames.map((e=>this.channelBuffers.get(e))),i=n.length;for(;;){let r=1/0;for(const e of n)e.length<r&&(r=e.length);if(!Number.isFinite(r)||0===r)return;if(!e&&r<this.minChunkSamples)return;r>this.maxChunkSamples&&(r=this.maxChunkSamples);const o=new Int16Array(r);for(let e=0;e<r;e++){let t=0;for(let s=0;s<i;s++)t+=n[s][e];const s=Math.round(t/i);o[e]=s<-32768?-32768:s>32767?32767:s}for(const e of n)e.splice(0,r);try{this.send(o.buffer)}catch(e){return void(null===(s=(t=this.listeners).error)||void 0===s||s.call(t,e))}}}resolveUnknownChannelsByWindow(e){var t;if(!this.attributionParams)return;const s=this.attributionParams.resolutionWindowWords,n=e.words;let i=!1;for(let e=0;e<n.length;e++){if("unknown"!==n[e].channel)continue;const r=new Map,o=Math.max(0,e-s),a=Math.min(n.length-1,e+s);for(let s=o;s<=a;s++){if(s===e)continue;const i=n[s].channel;i&&"unknown"!==i&&r.set(i,(null!==(t=r.get(i))&&void 0!==t?t:0)+1)}if(0===r.size)continue;let l,c=0,h=!1;for(const[e,t]of r)t>c?(l=e,c=t,h=!1):t===c&&(h=!0);l&&!h&&(n[e].channel=l,n[e].channelResolved=!0,i=!0)}i&&(e.channel=M(n))}resolveUnknownChannelsBySpeakerHistory(e){var t;if(!this.timeline||!this.attributionParams||!this.speakerHistory)return;const s=this.attributionParams.speakerHistoryMinRmsEvidence,n=this.attributionParams.speakerHistoryDominanceRatio;for(const s of e.words){if(!s.speaker)continue;const e=this.timeline.framesInWindow(s.start,s.end);let n=this.speakerHistory.get(s.speaker);n||(n=new Map,this.speakerHistory.set(s.speaker,n));for(const s of e)s.active&&n.set(s.channel,(null!==(t=n.get(s.channel))&&void 0!==t?t:0)+s.rms)}let i=!1;for(const t of e.words){if("unknown"!==t.channel||!t.speaker)continue;const e=this.speakerHistory.get(t.speaker);if(!e||0===e.size)continue;let r,o=0,a=0,l=0;for(const[t,s]of e)o+=s,s>a?(l=a,a=s,r=t):s>l&&(l=s);o<s||(l>0&&a<n*l||r&&(t.channel=r,t.channelResolved=!0,i=!0))}i&&(e.channel=M(e.words))}updateConfiguration(e){const{min_end_of_turn_silence_when_confident:t,min_turn_silence:n}=e,i=s(e,["min_end_of_turn_silence_when_confident","min_turn_silence"]);void 0!==t&&(void 0!==n?console.warn("[Deprecation Warning] Both `min_end_of_turn_silence_when_confident` and `min_turn_silence` are set. Using `min_turn_silence`; `min_end_of_turn_silence_when_confident` is deprecated."):console.warn("[Deprecation Warning] `min_end_of_turn_silence_when_confident` is deprecated and will be removed in a future release. Please use `min_turn_silence` instead."));const r=null!=n?n:t,o=Object.assign(Object.assign({type:"UpdateConfiguration"},i),void 0!==r?{min_turn_silence:r}:{});this.send(JSON.stringify(o))}forceEndpoint(){this.send(JSON.stringify({type:"ForceEndpoint"}))}keepAlive(){this.send(JSON.stringify({type:"KeepAlive"}))}send(e){if(!this.socket||this.socket.readyState!==this.socket.OPEN)throw new Error("Socket is not open for communication");this.socket.send(e)}close(){return n(this,arguments,void 0,(function*(e=!0){var t;if(this.flushTimer&&(clearInterval(this.flushTimer),this.flushTimer=void 0,this.flushMix(!0)),this.socket){if(this.socket.readyState===this.socket.OPEN)if(e){const e=new Promise((e=>{this.sessionTerminatedResolve=e}));this.socket.send(I),yield e}else this.socket.send(I);(null===(t=this.socket)||void 0===t?void 0:t.removeAllListeners)&&this.socket.removeAllListeners(),this.socket.close()}this.listeners={},this.socket=void 0}))}}class B extends a{constructor(e){super(e),this.baseServiceParams=e}transcriber(e){const t=Object.assign({},e);return t.token||t.apiKey||(t.apiKey=this.baseServiceParams.apiKey),new N(t)}createTemporaryToken(e){return n(this,void 0,void 0,(function*(){const t=new URLSearchParams;Object.entries(e).forEach((([e,s])=>{null!=s&&t.append(e,String(s))}));const s=t.toString(),n=s?`/v3/token?${s}`:"/v3/token";return(yield this.fetchJson(n,{method:"GET"})).token}))}}e.AssemblyAI=class{constructor(e){e.baseUrl=e.baseUrl||"https://api.assemblyai.com",e.baseUrl&&e.baseUrl.endsWith("/")&&(e.baseUrl=e.baseUrl.slice(0,-1)),this.files=new O(e),this.transcripts=new x(e,this.files),this.lemur=new l(e),this.realtime=new A(e),this.streaming=new B(Object.assign(Object.assign({},e),{baseUrl:e.streamingBaseUrl||"https://streaming.assemblyai.com"}))}},e.BrowserOnlyError=t,e.DualChannelCapture=class{constructor(e){var s;if(this.running=!1,void 0===globalThis.AudioContext)throw new t;this.params={micStream:e.micStream,systemStream:e.systemStream,transcriber:e.transcriber,targetSampleRate:null!==(s=e.targetSampleRate)&&void 0!==s?s:16e3}}on(e,t){"error"===e&&(this.errorListener=t)}start(){return n(this,void 0,void 0,(function*(){if(this.running)throw new Error("DualChannelCapture already started");this.context=new AudioContext;const e=new Blob(['\nclass Pcm16EncoderProcessor extends AudioWorkletProcessor {\n constructor(options) {\n super();\n const opts = (options && options.processorOptions) || {};\n this.targetRate = opts.targetRate || 16000;\n this.chunkMs = opts.chunkMs || 50;\n this.ratio = sampleRate / this.targetRate;\n this.chunkSize = Math.round(this.targetRate * this.chunkMs / 1000);\n this.buffer = new Int16Array(this.chunkSize);\n this.bufferIdx = 0;\n this.samplesSent = 0;\n this.lastSample = 0;\n this.fractional = 0;\n }\n\n process(inputs) {\n const input = inputs[0];\n if (!input || input.length === 0 || !input[0] || input[0].length === 0) {\n return true;\n }\n const mono = input[0];\n let pos = this.fractional;\n while (pos < mono.length) {\n const i = Math.floor(pos);\n const frac = pos - i;\n const a = i === 0 ? this.lastSample : mono[i - 1];\n const b = mono[i];\n const sample = a + (b - a) * frac;\n const clamped = sample < -1 ? -1 : sample > 1 ? 1 : sample;\n this.buffer[this.bufferIdx++] = clamped < 0 ? clamped * 0x8000 : clamped * 0x7fff;\n if (this.bufferIdx === this.chunkSize) {\n const out = new Int16Array(this.chunkSize);\n out.set(this.buffer);\n this.samplesSent += this.chunkSize;\n this.port.postMessage(\n { pcm: out.buffer, samplesSent: this.samplesSent },\n [out.buffer],\n );\n this.bufferIdx = 0;\n }\n pos += this.ratio;\n }\n this.lastSample = mono[mono.length - 1];\n this.fractional = pos - mono.length;\n return true;\n }\n}\nregisterProcessor("aai-pcm16-encoder", Pcm16EncoderProcessor);\n'],{type:"application/javascript"}),t=URL.createObjectURL(e);try{yield this.context.audioWorklet.addModule(t)}finally{URL.revokeObjectURL(t)}this.micSource=this.context.createMediaStreamSource(this.params.micStream),this.sysSource=this.context.createMediaStreamSource(this.params.systemStream),this.micEncoder=this.makeEncoder("mic"),this.sysEncoder=this.makeEncoder("system"),this.micSource.connect(this.micEncoder),this.sysSource.connect(this.sysEncoder),this.running=!0}))}makeEncoder(e){const t=new AudioWorkletNode(this.context,"aai-pcm16-encoder",{numberOfInputs:1,numberOfOutputs:0,channelCount:1,channelCountMode:"explicit",channelInterpretation:"speakers",processorOptions:{targetRate:this.params.targetSampleRate,chunkMs:50}});return t.port.onmessage=t=>{var s;try{this.params.transcriber.sendAudio(t.data.pcm,{channel:e})}catch(e){null===(s=this.errorListener)||void 0===s||s.call(this,e)}},t}stop(){return n(this,void 0,void 0,(function*(){var e,t,s,n,i,r;if(this.running){this.running=!1;try{null===(e=this.micEncoder)||void 0===e||e.port.close(),null===(t=this.sysEncoder)||void 0===t||t.port.close(),null===(s=this.micEncoder)||void 0===s||s.disconnect(),null===(n=this.sysEncoder)||void 0===n||n.disconnect(),null===(i=this.micSource)||void 0===i||i.disconnect(),null===(r=this.sysSource)||void 0===r||r.disconnect()}catch(e){}this.context&&"closed"!==this.context.state&&(yield this.context.close()),this.context=void 0,this.micSource=void 0,this.sysSource=void 0,this.micEncoder=void 0,this.sysEncoder=void 0}}))}},e.EnergyVad=R,e.FileService=O,e.LemurService=l,e.LinearResampler=class{constructor(e,t){if(this.sourceRate=e,this.targetRate=t,this.lastSample=0,this.fractional=0,e<=0||t<=0)throw new Error("sourceRate and targetRate must be positive");this.ratio=e/t}process(e){var t;if(this.sourceRate===this.targetRate)return e;const s=new Float32Array(Math.ceil(e.length/this.ratio)+1);let n=0,i=this.fractional;for(;i<e.length;){const t=Math.floor(i),r=i-t,o=0===t?this.lastSample:e[t-1],a=e[t];s[n++]=o+(a-o)*r,i+=this.ratio}return this.lastSample=null!==(t=e[e.length-1])&&void 0!==t?t:this.lastSample,this.fractional=i-e.length,s.subarray(0,n)}reset(){this.lastSample=0,this.fractional=0}},e.RealtimeService=class extends T{},e.RealtimeServiceFactory=class extends A{},e.RealtimeTranscriber=T,e.RealtimeTranscriberFactory=A,e.StreamingTranscriber=N,e.TranscriptService=x,e.VadTimeline=E,e.attributeTurn=C,e.attributeWord=U,e.float32ToPcm16=function(e){const t=new ArrayBuffer(2*e.length),s=new DataView(t);for(let t=0;t<e.length;t++){const n=Math.max(-1,Math.min(1,e[t]));s.setInt16(2*t,n<0?32768*n:32767*n,!0)}return t},e.rollUpTurnChannel=M}));
package/dist/browser.mjs CHANGED
@@ -28,7 +28,7 @@ if (typeof navigator !== "undefined" && navigator.userAgent) {
28
28
  defaultUserAgentString += navigator.userAgent;
29
29
  }
30
30
  const defaultUserAgent = {
31
- sdk: { name: "JavaScript", version: "4.34.5" },
31
+ sdk: { name: "JavaScript", version: "4.35.0" },
32
32
  };
33
33
  if (typeof process !== "undefined") {
34
34
  if (process.versions.node && defaultUserAgentString.indexOf("Node") === -1) {
@@ -1000,6 +1000,24 @@ function toInt16View(audio) {
1000
1000
  }
1001
1001
  const defaultStreamingUrl$1 = "wss://streaming.assemblyai.com/v3/ws";
1002
1002
  const terminateSessionMessage = `{"type":"Terminate"}`;
1003
+ const DEFAULT_CONNECT_TIMEOUT_MS = 1000;
1004
+ const DEFAULT_MAX_CONNECTION_RETRIES = 2;
1005
+ const DEFAULT_CONNECTION_RETRY_DELAY_MS = 500;
1006
+ /**
1007
+ * Close/error codes that signal a permanent client-side problem (auth,
1008
+ * billing, malformed config). A retry would hit the same failure, so the
1009
+ * connection is never retried on these.
1010
+ */
1011
+ const NON_RETRYABLE_CLOSE_CODES = new Set([
1012
+ StreamingErrorType.BadSampleRate,
1013
+ StreamingErrorType.AuthFailed,
1014
+ StreamingErrorType.InsufficientFunds,
1015
+ StreamingErrorType.FreeTierUser,
1016
+ StreamingErrorType.BadSchema,
1017
+ ]);
1018
+ function isRetryableCloseCode(code) {
1019
+ return code !== 1000 && !NON_RETRYABLE_CLOSE_CODES.has(code);
1020
+ }
1003
1021
  /**
1004
1022
  * Per-send chunk cap in milliseconds for the dual-channel mixer. The streaming
1005
1023
  * server rejects audio messages longer than 1000 ms (`Input Duration Error`).
@@ -1208,12 +1226,81 @@ class StreamingTranscriber {
1208
1226
  on(event, listener) {
1209
1227
  this.listeners[event] = listener;
1210
1228
  }
1211
- connect() {
1212
- return new Promise((resolve) => {
1213
- if (this.socket) {
1214
- throw new Error("Already connected");
1229
+ /**
1230
+ * Open the streaming session.
1231
+ *
1232
+ * Resolves with the server's `Begin` event once the handshake completes. A
1233
+ * single attempt is bounded by `connectTimeout` (default 1000ms); transient
1234
+ * failures (timeout, network drop, unexpected close) are retried up to
1235
+ * `maxConnectionRetries` times (default 2), waiting `connectionRetryDelay`
1236
+ * (default 500ms) between attempts. Permanent failures (auth, insufficient
1237
+ * funds, malformed config) are not retried.
1238
+ *
1239
+ * Unlike previously, a failed connection now rejects this promise rather
1240
+ * than only invoking the `error` listener — necessary for the caller (and
1241
+ * the retry loop) to observe the failure.
1242
+ */
1243
+ async connect() {
1244
+ if (this.socket) {
1245
+ throw new Error("Already connected");
1246
+ }
1247
+ const maxRetries = this.params.maxConnectionRetries ?? DEFAULT_MAX_CONNECTION_RETRIES;
1248
+ const retryDelay = this.params.connectionRetryDelay ?? DEFAULT_CONNECTION_RETRY_DELAY_MS;
1249
+ let lastError;
1250
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
1251
+ try {
1252
+ return await this.connectOnce();
1253
+ }
1254
+ catch (err) {
1255
+ lastError = err;
1256
+ const retryable = err.retryable === true;
1257
+ if (!retryable || attempt === maxRetries) {
1258
+ throw err;
1259
+ }
1260
+ console.warn(`Streaming connect attempt ${attempt + 1}/${maxRetries + 1} failed (${err.message}); retrying`);
1261
+ if (retryDelay > 0) {
1262
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
1263
+ }
1215
1264
  }
1265
+ }
1266
+ // The loop above always returns or throws; this only satisfies the type
1267
+ // checker that a value is produced on every path.
1268
+ throw lastError ?? new Error("Failed to connect to streaming server");
1269
+ }
1270
+ connectOnce() {
1271
+ return new Promise((resolve, reject) => {
1216
1272
  const url = this.connectionUrl();
1273
+ const timeoutMs = this.params.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT_MS;
1274
+ // `settled` flips once this attempt has resolved (`Begin`) or rejected
1275
+ // (timeout / pre-`Begin` close / error). Before it flips the socket
1276
+ // handlers drive this promise; after it flips they revert to normal
1277
+ // runtime dispatch (close / error / message listeners).
1278
+ let settled = false;
1279
+ let timer;
1280
+ const failAttempt = (error) => {
1281
+ if (settled)
1282
+ return;
1283
+ settled = true;
1284
+ if (timer)
1285
+ clearTimeout(timer);
1286
+ this.discardPendingSocket();
1287
+ reject(error);
1288
+ };
1289
+ const succeed = (begin) => {
1290
+ if (settled)
1291
+ return;
1292
+ settled = true;
1293
+ if (timer)
1294
+ clearTimeout(timer);
1295
+ resolve(begin);
1296
+ };
1297
+ if (timeoutMs > 0) {
1298
+ timer = setTimeout(() => {
1299
+ const err = new StreamingError(`Streaming connection timed out after ${timeoutMs}ms`);
1300
+ err.retryable = true;
1301
+ failAttempt(err);
1302
+ }, timeoutMs);
1303
+ }
1217
1304
  if (this.token) {
1218
1305
  this.socket = factory(url.toString());
1219
1306
  }
@@ -1234,6 +1321,15 @@ Learn more at https://github.com/AssemblyAI/assemblyai-node-sdk/blob/main/docs/c
1234
1321
  reason = StreamingErrorMessages[code];
1235
1322
  }
1236
1323
  }
1324
+ // A close before `Begin` is a failed connection attempt — reject so
1325
+ // connect() can retry (or surface a permanent failure).
1326
+ if (!settled) {
1327
+ const err = new StreamingError(reason || `Streaming connection closed (code=${code})`);
1328
+ err.code = code;
1329
+ err.retryable = isRetryableCloseCode(code);
1330
+ failAttempt(err);
1331
+ return;
1332
+ }
1237
1333
  // Stop the flush timer when the socket is gone (server-initiated close,
1238
1334
  // network drop, etc.) — otherwise subsequent ticks call send() on a
1239
1335
  // closed socket and spam the error listener.
@@ -1244,25 +1340,37 @@ Learn more at https://github.com/AssemblyAI/assemblyai-node-sdk/blob/main/docs/c
1244
1340
  this.listeners.close?.(code, reason);
1245
1341
  };
1246
1342
  this.socket.onerror = (event) => {
1247
- if (event.error)
1248
- this.listeners.error?.(event.error);
1249
- else
1250
- this.listeners.error?.(new Error(event.message));
1343
+ const error = event.error ?? new Error(event.message);
1344
+ // A socket error before `Begin` is a failed attempt → reject/retry.
1345
+ if (!settled) {
1346
+ error.retryable = true;
1347
+ failAttempt(error);
1348
+ return;
1349
+ }
1350
+ this.listeners.error?.(error);
1251
1351
  };
1252
1352
  this.socket.onmessage = ({ data }) => {
1253
1353
  const message = JSON.parse(data.toString());
1254
1354
  if ("error" in message) {
1255
1355
  const err = new StreamingError(message.error);
1256
1356
  if ("error_code" in message) {
1257
- err.code =
1258
- message.error_code;
1357
+ err.code = message.error_code;
1358
+ }
1359
+ // A server error frame before `Begin` fails the attempt; the code
1360
+ // decides whether a retry is worthwhile.
1361
+ if (!settled) {
1362
+ const attemptErr = err;
1363
+ attemptErr.retryable =
1364
+ err.code === undefined ? true : isRetryableCloseCode(err.code);
1365
+ failAttempt(attemptErr);
1366
+ return;
1259
1367
  }
1260
1368
  this.listeners.error?.(err);
1261
1369
  return;
1262
1370
  }
1263
1371
  switch (message.type) {
1264
1372
  case "Begin": {
1265
- resolve(message);
1373
+ succeed(message);
1266
1374
  this.listeners.open?.(message);
1267
1375
  break;
1268
1376
  }
@@ -1309,6 +1417,20 @@ Learn more at https://github.com/AssemblyAI/assemblyai-node-sdk/blob/main/docs/c
1309
1417
  };
1310
1418
  });
1311
1419
  }
1420
+ /** Tear down a half-open socket from a failed connection attempt. */
1421
+ discardPendingSocket() {
1422
+ if (!this.socket)
1423
+ return;
1424
+ try {
1425
+ if (this.socket.removeAllListeners)
1426
+ this.socket.removeAllListeners();
1427
+ this.socket.close();
1428
+ }
1429
+ catch {
1430
+ // Best-effort cleanup; a half-open socket may throw on close.
1431
+ }
1432
+ this.socket = undefined;
1433
+ }
1312
1434
  /**
1313
1435
  * Returns a WritableStream that pumps PCM chunks into `sendAudio`. Single-channel
1314
1436
  * only — in dual-channel mode use `sendAudio(pcm, { channel })` directly, since
@@ -1582,6 +1704,16 @@ Learn more at https://github.com/AssemblyAI/assemblyai-node-sdk/blob/main/docs/c
1582
1704
  };
1583
1705
  this.send(JSON.stringify(message));
1584
1706
  }
1707
+ /**
1708
+ * Reset the server's inactivity timer. Only needed when the session was
1709
+ * created with `inactivityTimeout` and no audio is being sent.
1710
+ */
1711
+ keepAlive() {
1712
+ const message = {
1713
+ type: "KeepAlive",
1714
+ };
1715
+ this.send(JSON.stringify(message));
1716
+ }
1585
1717
  send(data) {
1586
1718
  if (!this.socket || this.socket.readyState !== this.socket.OPEN) {
1587
1719
  throw new Error("Socket is not open for communication");
package/dist/bun.mjs CHANGED
@@ -30,7 +30,7 @@ if (typeof navigator !== "undefined" && navigator.userAgent) {
30
30
  defaultUserAgentString += navigator.userAgent;
31
31
  }
32
32
  const defaultUserAgent = {
33
- sdk: { name: "JavaScript", version: "4.34.5" },
33
+ sdk: { name: "JavaScript", version: "4.35.0" },
34
34
  };
35
35
  if (typeof process !== "undefined") {
36
36
  if (process.versions.node && defaultUserAgentString.indexOf("Node") === -1) {
@@ -981,6 +981,24 @@ function toInt16View(audio) {
981
981
  }
982
982
  const defaultStreamingUrl$1 = "wss://streaming.assemblyai.com/v3/ws";
983
983
  const terminateSessionMessage = `{"type":"Terminate"}`;
984
+ const DEFAULT_CONNECT_TIMEOUT_MS = 1000;
985
+ const DEFAULT_MAX_CONNECTION_RETRIES = 2;
986
+ const DEFAULT_CONNECTION_RETRY_DELAY_MS = 500;
987
+ /**
988
+ * Close/error codes that signal a permanent client-side problem (auth,
989
+ * billing, malformed config). A retry would hit the same failure, so the
990
+ * connection is never retried on these.
991
+ */
992
+ const NON_RETRYABLE_CLOSE_CODES = new Set([
993
+ StreamingErrorType.BadSampleRate,
994
+ StreamingErrorType.AuthFailed,
995
+ StreamingErrorType.InsufficientFunds,
996
+ StreamingErrorType.FreeTierUser,
997
+ StreamingErrorType.BadSchema,
998
+ ]);
999
+ function isRetryableCloseCode(code) {
1000
+ return code !== 1000 && !NON_RETRYABLE_CLOSE_CODES.has(code);
1001
+ }
984
1002
  /**
985
1003
  * Per-send chunk cap in milliseconds for the dual-channel mixer. The streaming
986
1004
  * server rejects audio messages longer than 1000 ms (`Input Duration Error`).
@@ -1189,12 +1207,81 @@ class StreamingTranscriber {
1189
1207
  on(event, listener) {
1190
1208
  this.listeners[event] = listener;
1191
1209
  }
1192
- connect() {
1193
- return new Promise((resolve) => {
1194
- if (this.socket) {
1195
- throw new Error("Already connected");
1210
+ /**
1211
+ * Open the streaming session.
1212
+ *
1213
+ * Resolves with the server's `Begin` event once the handshake completes. A
1214
+ * single attempt is bounded by `connectTimeout` (default 1000ms); transient
1215
+ * failures (timeout, network drop, unexpected close) are retried up to
1216
+ * `maxConnectionRetries` times (default 2), waiting `connectionRetryDelay`
1217
+ * (default 500ms) between attempts. Permanent failures (auth, insufficient
1218
+ * funds, malformed config) are not retried.
1219
+ *
1220
+ * Unlike previously, a failed connection now rejects this promise rather
1221
+ * than only invoking the `error` listener — necessary for the caller (and
1222
+ * the retry loop) to observe the failure.
1223
+ */
1224
+ async connect() {
1225
+ if (this.socket) {
1226
+ throw new Error("Already connected");
1227
+ }
1228
+ const maxRetries = this.params.maxConnectionRetries ?? DEFAULT_MAX_CONNECTION_RETRIES;
1229
+ const retryDelay = this.params.connectionRetryDelay ?? DEFAULT_CONNECTION_RETRY_DELAY_MS;
1230
+ let lastError;
1231
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
1232
+ try {
1233
+ return await this.connectOnce();
1234
+ }
1235
+ catch (err) {
1236
+ lastError = err;
1237
+ const retryable = err.retryable === true;
1238
+ if (!retryable || attempt === maxRetries) {
1239
+ throw err;
1240
+ }
1241
+ console.warn(`Streaming connect attempt ${attempt + 1}/${maxRetries + 1} failed (${err.message}); retrying`);
1242
+ if (retryDelay > 0) {
1243
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
1244
+ }
1196
1245
  }
1246
+ }
1247
+ // The loop above always returns or throws; this only satisfies the type
1248
+ // checker that a value is produced on every path.
1249
+ throw lastError ?? new Error("Failed to connect to streaming server");
1250
+ }
1251
+ connectOnce() {
1252
+ return new Promise((resolve, reject) => {
1197
1253
  const url = this.connectionUrl();
1254
+ const timeoutMs = this.params.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT_MS;
1255
+ // `settled` flips once this attempt has resolved (`Begin`) or rejected
1256
+ // (timeout / pre-`Begin` close / error). Before it flips the socket
1257
+ // handlers drive this promise; after it flips they revert to normal
1258
+ // runtime dispatch (close / error / message listeners).
1259
+ let settled = false;
1260
+ let timer;
1261
+ const failAttempt = (error) => {
1262
+ if (settled)
1263
+ return;
1264
+ settled = true;
1265
+ if (timer)
1266
+ clearTimeout(timer);
1267
+ this.discardPendingSocket();
1268
+ reject(error);
1269
+ };
1270
+ const succeed = (begin) => {
1271
+ if (settled)
1272
+ return;
1273
+ settled = true;
1274
+ if (timer)
1275
+ clearTimeout(timer);
1276
+ resolve(begin);
1277
+ };
1278
+ if (timeoutMs > 0) {
1279
+ timer = setTimeout(() => {
1280
+ const err = new StreamingError(`Streaming connection timed out after ${timeoutMs}ms`);
1281
+ err.retryable = true;
1282
+ failAttempt(err);
1283
+ }, timeoutMs);
1284
+ }
1198
1285
  if (this.token) {
1199
1286
  this.socket = factory(url.toString());
1200
1287
  }
@@ -1211,6 +1298,15 @@ class StreamingTranscriber {
1211
1298
  reason = StreamingErrorMessages[code];
1212
1299
  }
1213
1300
  }
1301
+ // A close before `Begin` is a failed connection attempt — reject so
1302
+ // connect() can retry (or surface a permanent failure).
1303
+ if (!settled) {
1304
+ const err = new StreamingError(reason || `Streaming connection closed (code=${code})`);
1305
+ err.code = code;
1306
+ err.retryable = isRetryableCloseCode(code);
1307
+ failAttempt(err);
1308
+ return;
1309
+ }
1214
1310
  // Stop the flush timer when the socket is gone (server-initiated close,
1215
1311
  // network drop, etc.) — otherwise subsequent ticks call send() on a
1216
1312
  // closed socket and spam the error listener.
@@ -1221,25 +1317,37 @@ class StreamingTranscriber {
1221
1317
  this.listeners.close?.(code, reason);
1222
1318
  };
1223
1319
  this.socket.onerror = (event) => {
1224
- if (event.error)
1225
- this.listeners.error?.(event.error);
1226
- else
1227
- this.listeners.error?.(new Error(event.message));
1320
+ const error = event.error ?? new Error(event.message);
1321
+ // A socket error before `Begin` is a failed attempt → reject/retry.
1322
+ if (!settled) {
1323
+ error.retryable = true;
1324
+ failAttempt(error);
1325
+ return;
1326
+ }
1327
+ this.listeners.error?.(error);
1228
1328
  };
1229
1329
  this.socket.onmessage = ({ data }) => {
1230
1330
  const message = JSON.parse(data.toString());
1231
1331
  if ("error" in message) {
1232
1332
  const err = new StreamingError(message.error);
1233
1333
  if ("error_code" in message) {
1234
- err.code =
1235
- message.error_code;
1334
+ err.code = message.error_code;
1335
+ }
1336
+ // A server error frame before `Begin` fails the attempt; the code
1337
+ // decides whether a retry is worthwhile.
1338
+ if (!settled) {
1339
+ const attemptErr = err;
1340
+ attemptErr.retryable =
1341
+ err.code === undefined ? true : isRetryableCloseCode(err.code);
1342
+ failAttempt(attemptErr);
1343
+ return;
1236
1344
  }
1237
1345
  this.listeners.error?.(err);
1238
1346
  return;
1239
1347
  }
1240
1348
  switch (message.type) {
1241
1349
  case "Begin": {
1242
- resolve(message);
1350
+ succeed(message);
1243
1351
  this.listeners.open?.(message);
1244
1352
  break;
1245
1353
  }
@@ -1286,6 +1394,20 @@ class StreamingTranscriber {
1286
1394
  };
1287
1395
  });
1288
1396
  }
1397
+ /** Tear down a half-open socket from a failed connection attempt. */
1398
+ discardPendingSocket() {
1399
+ if (!this.socket)
1400
+ return;
1401
+ try {
1402
+ if (this.socket.removeAllListeners)
1403
+ this.socket.removeAllListeners();
1404
+ this.socket.close();
1405
+ }
1406
+ catch {
1407
+ // Best-effort cleanup; a half-open socket may throw on close.
1408
+ }
1409
+ this.socket = undefined;
1410
+ }
1289
1411
  /**
1290
1412
  * Returns a WritableStream that pumps PCM chunks into `sendAudio`. Single-channel
1291
1413
  * only — in dual-channel mode use `sendAudio(pcm, { channel })` directly, since
@@ -1559,6 +1681,16 @@ class StreamingTranscriber {
1559
1681
  };
1560
1682
  this.send(JSON.stringify(message));
1561
1683
  }
1684
+ /**
1685
+ * Reset the server's inactivity timer. Only needed when the session was
1686
+ * created with `inactivityTimeout` and no audio is being sent.
1687
+ */
1688
+ keepAlive() {
1689
+ const message = {
1690
+ type: "KeepAlive",
1691
+ };
1692
+ this.send(JSON.stringify(message));
1693
+ }
1562
1694
  send(data) {
1563
1695
  if (!this.socket || this.socket.readyState !== this.socket.OPEN) {
1564
1696
  throw new Error("Socket is not open for communication");