@animated-waffle/client 0.4.0 → 0.4.1-run170.3
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/dist/chunk-4OFU2T5E.js +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +24 -1
- package/dist/internal.d.ts +9 -1
- package/dist/internal.js +1 -1
- package/package.json +3 -2
- package/dist/chunk-BXRNROI7.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var o=new WeakMap;function i(n,t,a){let e=o.get(n)??new Map,r=e.get(t)??new Set;return r.add(a),e.set(t,r),o.set(n,e),()=>r.delete(a)}function l(n,t,a){o.get(n)?.get(t)?.forEach(e=>{e(a)})}export{i as a,l as b};
|
package/dist/index.d.ts
CHANGED
|
@@ -181,6 +181,8 @@ interface WaffleClientBaseOptions {
|
|
|
181
181
|
* turn this off, or the voice is heard twice.
|
|
182
182
|
*/
|
|
183
183
|
audioPlayback?: boolean;
|
|
184
|
+
/** Opt in to paired PCM and generated motion, played together by one SDK audio clock. */
|
|
185
|
+
avatarMotion?: boolean;
|
|
184
186
|
/** Self-reported integration family for observability only. Defaults to SDK. */
|
|
185
187
|
reportedSource?: 'Web' | 'SDK' | 'Client' | 'Unknown';
|
|
186
188
|
/**
|
|
@@ -292,6 +294,14 @@ declare class WaffleClient implements WaffleSessionClient {
|
|
|
292
294
|
private readyWaiter;
|
|
293
295
|
private audio;
|
|
294
296
|
private audioMuted;
|
|
297
|
+
private motionSync;
|
|
298
|
+
private motionAgentIdentity;
|
|
299
|
+
private motionPreparing;
|
|
300
|
+
private motionFeedback;
|
|
301
|
+
private motionFeedbackRevision;
|
|
302
|
+
private motionFeedbackAcknowledged;
|
|
303
|
+
private motionInterruptBarrier;
|
|
304
|
+
private motionFeedbackSending;
|
|
295
305
|
private currentRemoteAudioTrack;
|
|
296
306
|
private activeInputMode;
|
|
297
307
|
private agentIdentity;
|
|
@@ -343,7 +353,14 @@ declare class WaffleClient implements WaffleSessionClient {
|
|
|
343
353
|
private handleParticipant;
|
|
344
354
|
private handleDisconnected;
|
|
345
355
|
private setAudioTrack;
|
|
356
|
+
private stopAudioElement;
|
|
346
357
|
private stopAudio;
|
|
358
|
+
private waitForMotionParticipant;
|
|
359
|
+
private prepareMotionSync;
|
|
360
|
+
private sendMotionFeedback;
|
|
361
|
+
/** Await this interruption's ACK, not an ongoing stream of later listening progress. */
|
|
362
|
+
private waitForMotionInterruption;
|
|
363
|
+
private failMotion;
|
|
347
364
|
private completeTranscriptExecution;
|
|
348
365
|
private clearTranscriptProgress;
|
|
349
366
|
private setStatus;
|
package/dist/index.js
CHANGED
|
@@ -1 +1,24 @@
|
|
|
1
|
-
import{b as _}from"./chunk-BXRNROI7.js";import{DataPacket_Kind as U,ParticipantKind as W,Room as G,RoomEvent as P,RpcError as M,Track as C}from"livekit-client";var D=15e3,K=3e4,q="https://animated-waffle.narya.ai",A=/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,L=/^sha256:[a-f0-9]{64}$/,i=class extends Error{constructor(t,n,o,a="session",s){super(t,s);this.status=n;this.code=o;this.stage=a;this.name="WaffleError"}status;code;stage;rpcMethod;livekitErrorCode},I={APPLICATION_ERROR:1500,CONNECTION_TIMEOUT:1501,RESPONSE_TIMEOUT:1502,RECIPIENT_DISCONNECTED:1503},j=new Set([I.CONNECTION_TIMEOUT,I.RESPONSE_TIMEOUT]);function V(r){return r instanceof M&&j.has(r.code)}function H(r){return r===I.RECIPIENT_DISCONNECTED?"push_to_talk_agent_unavailable":r===I.CONNECTION_TIMEOUT||r===I.RESPONSE_TIMEOUT?"push_to_talk_timeout":"push_to_talk_failed"}function B(r){return r===I.RECIPIENT_DISCONNECTED?"The Waffle agent dropped out for a moment. Release and press again to talk.":r===I.CONNECTION_TIMEOUT||r===I.RESPONSE_TIMEOUT?"The voice turn took too long to reach the agent. Release and try again.":"That voice turn didn\u2019t go through. Press and hold to talk again."}function $(r,e){let t=r instanceof M?r.code:void 0,n=new i(B(t),void 0,H(t),"transport",{cause:r});return n.rpcMethod=e,typeof t=="number"&&(n.livekitErrorCode=t),n}function T(r,e,t){return r instanceof i?r:new i(r instanceof Error?r.message:t,void 0,void 0,e,{cause:r})}function S(r){return r!==null&&typeof r=="object"&&!Array.isArray(r)?r:null}function J(r=q){let e;try{e=new URL(r)}catch{throw new i("apiOrigin must be a valid URL.",void 0,"invalid_api_origin")}let t=e.hostname==="localhost"||e.hostname==="127.0.0.1"||e.hostname==="[::1]";if(e.protocol!=="https:"&&!(e.protocol==="http:"&&t)||e.username||e.password||e.pathname!=="/"&&e.pathname!==""||e.search||e.hash)throw new i("apiOrigin must be an HTTPS origin or an HTTP loopback origin.",void 0,"invalid_api_origin");return e.origin}function v(r,e){if(typeof r!="string"||r.trim().length===0)throw new i(`Session response is missing ${e}.`);return r}function z(r){let e=v(r,"avatar.asset_hash");if(!L.test(e))throw new i("Session response contains an invalid Avatar asset hash.");return e}function x(r){return typeof r=="number"&&Number.isFinite(r)?r:void 0}function g(r){return typeof r=="string"&&r.length>0?r:void 0}function R(r,e){let t=v(r,e);try{let n=new URL(t),o=n.hostname==="localhost"||n.hostname==="127.0.0.1"||n.hostname==="[::1]";if(n.protocol!=="https:"&&!(n.protocol==="http:"&&o))throw new Error;if(n.username||n.password||n.hash)throw new Error;return n.toString()}catch{throw new i(`Received an invalid ${e}.`)}}function Z(r,e){let t=v(r,e);if(!/^\d{4}-\d{2}-\d{2}T/.test(t)||!Number.isFinite(Date.parse(t)))throw new i(`Received an invalid ${e}.`);return t}function O(r,e){let t=g(r[`${e}.speech_id`]),n=Number(r[`${e}.speech_step_index`]);return t&&Number.isSafeInteger(n)&&n>0?JSON.stringify([t,n]):null}function Y(r,e,t,n){let o=S(r);if(!o)throw new i("Session response must be a JSON object.");if(o.protocol_version!==2)throw new i("Session response has an unsupported protocol version.");let a=S(o.connection);if(!a||a.transport!=="webrtc")throw new i("Session response is missing a WebRTC connection.");let s=v(a.url,"connection.url");try{let p=new URL(s),k=p.hostname==="localhost"||p.hostname==="127.0.0.1"||p.hostname==="[::1]";if(p.protocol!=="wss:"&&!(p.protocol==="ws:"&&k))throw new Error}catch{throw new i("Session response contains an invalid connection URL.")}let l=v(o.agent_id,"agent_id");if(l!==t)throw new i("Session response contains a mismatched Agent id.");let c=g(o.agent_studio_url);if(c)try{let p=new URL(c),k=p.hostname==="localhost"||p.hostname==="127.0.0.1"||p.hostname==="[::1]";if(p.protocol!=="https:"&&!(p.protocol==="http:"&&k))throw new Error}catch{throw new i("Session response contains an invalid Agent Studio URL.")}let d=g(o.avatar_id),f=S(o.avatar),b=null;if(d||f){if(!d||!f||v(f.id,"avatar.id")!==d)throw new i("Session response contains mismatched Avatar metadata.");let p=v(f.engine,"avatar.engine");if(p!=="vrm")throw new i("Session response contains an unsupported Avatar engine.");let k=v(f.model_url,"avatar.model_url");try{let w=new URL(k),y=w.hostname==="localhost"||w.hostname==="127.0.0.1"||w.hostname==="[::1]";if(w.protocol!=="https:"&&!(w.protocol==="http:"&&y))throw new Error}catch{throw new i("Session response contains an invalid Avatar model URL.")}b={id:d,name:v(f.name,"avatar.name"),engine:p,modelUrl:k,assetHash:z(f.asset_hash),runtimeProfile:v(f.runtime_profile,"avatar.runtime_profile"),...X(f.presentation_profile)}}let h=v(o.session_id,"session_id");return{id:h,agentId:l,...c?{agentStudioUrl:c}:{},...d?{avatarId:d}:{},...n?{developerFeedback:{sourceSessionId:h}}:{},avatar:b,inputMode:e,room:s,token:v(a.token,"connection.token")}}function X(r){if(r===void 0)return{};let e=S(r);if(!e)throw new i("Session response contains an invalid Avatar presentation profile.");let t=v(e.framing,"avatar.presentation_profile.framing"),n=v(e.attention,"avatar.presentation_profile.attention"),o=v(e.emotion,"avatar.presentation_profile.emotion");if(!["default","full","upper","face"].includes(t)||!["natural","focused","lively"].includes(n)||!["natural","subtle","expressive"].includes(o))throw new i("Session response contains an unsupported Avatar presentation profile.");return{presentationProfile:{framing:t,attention:n,emotion:o}}}function Q(r){let e=S(r);if(!e||typeof e.type!="string")return null;let t={contextId:g(e.contextId)??null,segmentId:g(e.segmentId),occurrence:x(e.occurrence),audioTime:x(e.audioTime)};if(e.type==="avatar-emotion"){let n=g(e.emotion);return n?{phase:"emotion",emotion:n,...t}:null}if(e.type==="avatar-vocal-action"){let n=g(e.action);return n?{phase:"vocal-action",action:n,...t}:null}return null}function ee(r){if(!r||typeof r!="object")return null;let e=r;if(e.type!=="agent-declined")return null;let t=g(e.reason);return t?{reason:t}:null}function E(r){let{avatar:e,room:t,token:n,...o}=r;return o}var F=class{room;listeners=new Map;options;apiOrigin;currentStatus="disconnected";currentSession=null;startPromise=null;startVersion=null;stopPromise=null;requestAbort=null;readyWaiter=null;audio=null;audioMuted=!1;currentRemoteAudioTrack=null;activeInputMode="push-to-talk";agentIdentity=null;progressExecutions=new Map;progressText=new Map;completedProgressExecutions=new Set;turnId=null;nextTurnId=0;turnControl=Promise.resolve();failedStartTurnKey=null;lifecycleVersion=0;recreateRoomOnConnect=!1;developerFeedbackGrant=null;developerFeedbackGrantPromise=null;developerFeedbackGrantAbort=null;constructor(e){if(this.options=e,this.apiOrigin=J(e.apiOrigin),!A.test(e.agentId))throw new i("agentId must be a UUID.",void 0,"invalid_agent_id");if(!e.startSession&&typeof e.sessionToken!="function")throw new i("sessionToken must be a function.",void 0,"invalid_session_token");this.room=this.createRoom()}createRoom(){let e=new G({adaptiveStream:!0,dynacast:!0,audioCaptureDefaults:{voiceIsolation:!1}});return e.on(P.ParticipantConnected,t=>{e===this.room&&this.handleParticipant(t)}),e.on(P.ParticipantDisconnected,t=>{e!==this.room||t.kind!==W.AGENT||(this.agentIdentity=null,this.currentStatus!=="disconnecting"&&(this.handleDisconnected("The Waffle Agent disconnected."),e.disconnect().catch(n=>{this.emit("error",T(n,"transport","Could not close the Agent room."))})))}),e.on(P.ParticipantAttributesChanged,(t,n)=>{e===this.room&&this.handleParticipant(n)}),e.on(P.TrackSubscribed,(t,n,o)=>{e===this.room&&o.kind===W.AGENT&&t.kind===C.Kind.Audio&&this.setAudioTrack(t.mediaStreamTrack)}),e.on(P.TrackUnsubscribed,(t,n,o)=>{e===this.room&&o.kind===W.AGENT&&t.kind===C.Kind.Audio&&this.setAudioTrack(null)}),e.on(P.DataReceived,(t,n,o,a)=>{if(!(e!==this.room||n?.kind!==W.AGENT||o!==U.RELIABLE)&&!(a!=="avatar.performance"&&a!=="animated-waffle.turn"))try{let s=JSON.parse(new TextDecoder().decode(t));if(a==="animated-waffle.turn"){let c=ee(s);c&&this.emit("declined",c);return}let l=Q(s);l&&_(this,"avatar",l)}catch{}}),e.on(P.Disconnected,()=>{e===this.room&&this.handleDisconnected("The Agent session disconnected before it was ready.")}),e.registerTextStreamHandler("lk.transcription",(t,n)=>{(async()=>{let o=this.agentIdentity?e.remoteParticipants.get(this.agentIdentity):void 0;if(e!==this.room||this.currentStatus!=="connected"||o?.kind!==W.AGENT)return;let a=t.info.attributes??{},s=g(a["lk.segment_id"]),l=g(a["lk.transcribed_track_id"]),c=a["lk.transcription_final"];if(!s||!l||c!=="true"&&c!=="false")return;let d=e.localParticipant.audioTrackPublications.has(l),f=o.audioTrackPublications.has(l);if(d===f)return;let b=d?"user":"assistant";if(n.identity!==(d?e.localParticipant.identity:o.identity))return;let h=b==="assistant"?O(a,"lk"):null;if(b==="assistant"&&!h||h&&(this.progressExecutions.set(s,h),this.completedProgressExecutions.has(h)))return;let p="";for await(let k of t){if(p+=k,e!==this.room||this.currentStatus!=="connected"||h&&this.completedProgressExecutions.has(h))return;if(p.length>0){let w={segmentId:s,role:b,text:p,complete:!1};this.progressText.set(s,w),this.emit("transcriptProgress",w)}}c==="true"&&p.length>0&&(this.emit("transcriptProgress",{segmentId:s,role:b,text:p,complete:!0}),this.progressExecutions.delete(s),this.progressText.delete(s))})().catch(o=>{this.emit("error",T(o,"transcript","Transcript stream failed."))})}),e.registerTextStreamHandler("animated-waffle.transcript",(t,n)=>{t.readAll().then(o=>{if(e!==this.room||n.identity!==this.agentIdentity||!o)return;let a=t.info.attributes??{},s=g(a["animated-waffle.message_id"]),l=a["animated-waffle.role"];if(!s||l!=="user"&&l!=="assistant"||a["animated-waffle.final"]!=="true")return;let c=l==="assistant"?O(a,"animated-waffle"):null;c&&this.completeTranscriptExecution(c);let d=Number(t.info.timestamp);this.emit("transcript",{id:s,role:l,text:o,final:!0,...Number.isFinite(d)&&d>0?{timestamp:new Date(d).toISOString()}:{}})}).catch(o=>{this.emit("error",T(o,"transcript","Transcript stream failed."))})}),e}get status(){return this.currentStatus}get connectionState(){return this.currentStatus}get session(){return this.currentSession?E(this.currentSession):null}get remoteAudioTrack(){return this.currentRemoteAudioTrack}get muted(){return this.audioMuted}set muted(e){this.audioMuted=e,this.audio&&(this.audio.muted=e)}on(e,t){let n=this.listeners.get(e)??new Set;return n.add(t),this.listeners.set(e,n),()=>n.delete(t)}connect(e={}){if(this.currentSession&&this.status==="connected")return Promise.resolve(E(this.currentSession));if(this.startPromise&&this.startVersion===this.lifecycleVersion)return this.startPromise;let t=++this.lifecycleVersion,n=(async()=>{let o=this.stopPromise;if(o&&await o,t!==this.lifecycleVersion)throw new i("The connection attempt was cancelled.");return this.recreateRoomOnConnect&&(this.room=this.createRoom(),this.recreateRoomOnConnect=!1),this.startInternal(e,t)})().finally(()=>{this.startPromise===n&&(this.startPromise=null,this.startVersion=null)});return this.startVersion=t,this.startPromise=n,n}disconnect(){if(this.lifecycleVersion+=1,this.stopPromise)return this.stopPromise;let e=this.stopInternal().finally(()=>{this.stopPromise===e&&(this.stopPromise=null)});return this.stopPromise=e,e}async sendText(e,t){if(!e.trim())return;this.assertConnected();let n=t?.audioResponse===!1?{"animated-waffle.audio_response":"false"}:void 0;await this.room.localParticipant.sendText(e,{topic:"lk.chat",...n?{attributes:n}:{}})}async setInputMode(e){await this.applicationControl("input_mode",{mode:e}),this.activeInputMode=e,this.turnId=null}async interrupt(){await this.applicationControl("interrupt",{})}get microphoneTrack(){return this.room.localParticipant.getTrackPublication(C.Source.Microphone)?.audioTrack?.mediaStreamTrack??null}async setMicrophoneEnabled(e){if(this.assertConnected(),e&&this.options.inputAudioEnabled===!1)throw new i("Input audio is disabled.",void 0,"input_audio_disabled");await this.room.localParticipant.setMicrophoneEnabled(e)}async applicationControl(e,t){if(this.assertConnected(),!this.options.application||!this.agentIdentity)throw new i("Application sessions are required.",void 0,"application_session_required");return this.room.localParticipant.performRpc({destinationIdentity:this.agentIdentity,method:"application.control",payload:JSON.stringify({action:e,...t})})}async loadDeveloperFeedback(){return this.requestDeveloperFeedback("GET")}async submitDeveloperFeedback(e){let t=e.id??crypto.randomUUID();if(!A.test(t))throw new i("Developer Feedback id must be a UUID.",void 0,"invalid_feedback");if(e.scope==="message"&&!e.targetMessageId)throw new i("Message feedback requires a persisted Assistant message id.",void 0,"invalid_feedback");if(e.scope==="session"&&e.targetMessageId)throw new i("Session feedback cannot target one message.",void 0,"invalid_feedback");if(e.targetMessageId&&!A.test(e.targetMessageId))throw new i("Developer Feedback targetMessageId must be a UUID.",void 0,"invalid_feedback");if(e.action==="change"&&!e.note?.trim()&&!e.preferredReply?.trim()&&e.dimension!=="avatar"&&e.dimension!=="voice")throw new i("Describe the requested change or provide a preferred reply.",void 0,"invalid_feedback");return this.requestDeveloperFeedback("POST",{id:t,scope:e.scope,targetMessageId:e.targetMessageId??null,action:e.action,dimension:e.dimension,note:e.note??null,preferredReply:e.preferredReply??null})}startPushToTalk(){if(this.assertPushToTalkAvailable(),this.turnId!==null)return this.turnControl;this.nextTurnId+=1,this.turnId=this.nextTurnId;try{return this.sendInputControl("start_turn",String(this.turnId))}catch(e){throw this.turnId=null,e}}commitPushToTalk(){if(this.assertPushToTalkAvailable(),this.turnId===null)return this.turnControl;let e=String(this.turnId);return this.turnId=null,this.sendInputControl("end_turn",e)}stopPushToTalk(){this.commitPushToTalk()}cancelPushToTalk(){if(this.assertPushToTalkAvailable(),this.turnId===null)return this.turnControl;let e=String(this.turnId);return this.turnId=null,this.sendInputControl("cancel_turn",e)}async startInternal(e,t){this.activeInputMode=e.inputMode??"push-to-talk",this.setStatus("connecting");let n=new AbortController;this.requestAbort=n;let o=e.sessionId??crypto.randomUUID(),a=crypto.randomUUID(),s=this.options.startSession,l=s?void 0:this.options.sessionContext,c=s?void 0:JSON.stringify({chat_session_id:o,input_mode:this.activeInputMode==="push-to-talk"?"push_to_talk":"automatic",...e.overrides?{overrides:e.overrides}:{},...l?.userName?.trim()?{user_name:l.userName}:{},...l?.timeZone?.trim()?{time_zone:l.timeZone}:{}}),d=()=>this.options.startSession?Promise.resolve(this.options.startSession({agentId:this.options.agentId,inputMode:this.activeInputMode,requestId:a,sessionId:o,reportedSource:this.options.reportedSource??"SDK",...e.overrides?{overrides:e.overrides}:{},signal:n.signal})):this.resolveSessionToken().then(u=>fetch(this.apiOrigin+"/v1/agents/"+encodeURIComponent(this.options.agentId)+"/sessions",{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json",Authorization:"Bearer "+u,"Idempotency-Key":a,"X-Animated-Waffle-Reported-Source":this.options.reportedSource??"SDK"},signal:n.signal,body:c})),f;try{f=await this.withTimeout(s?d():d().catch(u=>{if(n.signal.aborted)throw u;return d()}),D,"Timed out creating the Waffle session.",()=>n.abort())}catch(u){let m=T(u,"session","Could not create session.");throw t!==this.lifecycleVersion?new i("The connection attempt was cancelled."):(this.fail(m),m)}finally{this.requestAbort===n&&(this.requestAbort=null)}if(t!==this.lifecycleVersion)throw new i("The connection attempt was cancelled.");let b=await f.json().catch(()=>null);if(!f.ok){let u=S(b),m=new i(g(u?.detail)??"Could not create session.",f.status,g(u?.code));throw this.fail(m),m}let h;try{h=Y(b,this.activeInputMode,this.options.agentId,typeof this.options.developerFeedback=="function")}catch(u){let m=T(u,"session","Could not parse the Waffle session.");throw this.fail(m),m}this.currentSession?.id!==h.id&&this.clearDeveloperFeedbackGrant(),this.currentSession=h,this.emit("session",E(h)),_(this,"presentation",h.avatar),this.setStatus("connecting");let p,k,w=new Promise((u,m)=>{p=u,k=m});this.readyWaiter={reject:k,resolve:p,version:t};let y=this.room;try{if(await y.connect(h.room,h.token).catch(u=>{throw T(u,"transport","Could not connect to the Agent room.")}),await y.startAudio().catch(u=>{throw T(u,"audio","Could not start Agent audio.")}),this.options.inputAudioEnabled!==!1){let u=await y.localParticipant.setMicrophoneEnabled(!0).catch(N=>{throw T(N,"audio","Could not start the microphone.")}),m=u?.audioTrack?.mediaStreamTrack;if(!u||u.isMuted||u.isUpstreamPaused||!m||!m.enabled||m.muted||m.readyState!=="live")throw new i("The microphone track did not publish.",void 0,void 0,"audio")}for(let u of y.remoteParticipants.values())this.handleParticipant(u);await this.withTimeout(w,K,"Timed out waiting for the Waffle Agent.",()=>{y.disconnect().catch(()=>{})},"transport")}catch(u){this.readyWaiter?.version===t&&(this.readyWaiter=null),this.currentStatus!=="disconnected"&&await y.disconnect().catch(()=>{});let m=T(u,"transport","Could not connect.");throw t!==this.lifecycleVersion?new i("The connection attempt was cancelled."):(this.fail(m),m)}finally{this.readyWaiter?.version===t&&(this.readyWaiter=null)}if(t!==this.lifecycleVersion)throw new i("The connection attempt was cancelled.");return this.setStatus("connected"),E(h)}async stopInternal(){if(this.readyWaiter?.reject(new i("The connection attempt was cancelled.")),this.readyWaiter=null,this.requestAbort?.abort(),this.turnId=null,this.turnControl=Promise.resolve(),this.failedStartTurnKey=null,this.agentIdentity=null,this.clearTranscriptProgress(),this.stopAudio(),this.currentStatus!=="disconnected"){this.setStatus("disconnecting");try{await this.room.disconnect()}finally{this.emit("agentSpeaking",!1),this.setStatus("disconnected")}}}sendInputControl(e,t){if(this.activeInputMode!=="push-to-talk")throw new i('Push-to-talk controls require inputMode: "push-to-talk".');this.assertConnected();let n=this.agentIdentity;if(!n)throw new i("The Waffle Agent is not ready.",void 0,"push_to_talk_agent_unavailable","transport");let o=this.room,a=this.turnControl.then(()=>{if(e!=="start_turn"&&this.failedStartTurnKey===t){this.failedStartTurnKey=null;return}return this.performControlRpc(o,n,e,t)});return this.turnControl=a.then(()=>{},s=>{e==="start_turn"&&(this.failedStartTurnKey=t),this.turnId!==null&&String(this.turnId)===t&&(this.turnId=null),this.emit("error",$(s,e))}),this.turnControl}async performControlRpc(e,t,n,o){let a=JSON.stringify({turnId:o}),s=n==="start_turn"?2:1,l;for(let c=1;c<=s;c+=1)try{return await e.localParticipant.performRpc({destinationIdentity:t,method:n,payload:a})}catch(d){if(l=d,c<s&&V(d))continue;throw d}throw l}assertPushToTalkAvailable(){if(this.options.inputAudioEnabled===!1)throw new i("Push-to-talk requires input audio to be enabled.",void 0,"input_audio_disabled")}assertConnected(){if(this.status!=="connected")throw new i("Start the Waffle session before sending input.")}async requestDeveloperFeedback(e,t){if(typeof this.options.developerFeedback!="function")throw new i("Developer Feedback is not enabled for this client.",void 0,"developer_feedback_disabled");let n=await this.resolveDeveloperFeedbackGrant(),o;try{o=await fetch(n.endpoint,{method:e,credentials:"omit",cache:"no-store",headers:{Accept:"application/json",Authorization:`Bearer ${n.token}`,...t?{"Content-Type":"application/json"}:{}},...t?{body:JSON.stringify(t)}:{}})}catch{throw new i("Could not save Developer Feedback.",void 0,"developer_feedback_network_error")}let a=await o.json().catch(()=>null);if(!o.ok){let f=S(a);throw new i(g(f?.detail)??"Could not save Developer Feedback.",o.status,g(f?.code))}let s=S(a),l=s?.round_id,c=s?.pending_count,d=s?.dashboard_url;if(l!==null&&(typeof l!="string"||!A.test(l))||typeof c!="number"||!Number.isSafeInteger(c)||c<0||d!==null&&typeof d!="string")throw new i("Developer Feedback response is invalid.");return typeof d=="string"&&R(d,"dashboard_url"),{roundId:l,pendingCount:c,dashboardUrl:d}}resolveDeveloperFeedbackGrant(){let e=this.currentSession,t=this.options.developerFeedback;if(!e||typeof t!="function")throw new i("Start a Developer Feedback-enabled Waffle session first.",void 0,"developer_feedback_unavailable");if(this.developerFeedbackGrant?.sourceSessionId===e.id&&Date.parse(this.developerFeedbackGrant.expiresAt)>Date.now())return Promise.resolve(this.developerFeedbackGrant);if(this.developerFeedbackGrantPromise)return this.developerFeedbackGrantPromise;let n=new AbortController;this.developerFeedbackGrantAbort=n;let o=this.withTimeout(Promise.resolve(t({agentId:e.agentId,sourceSessionId:e.id,signal:n.signal})),D,"Timed out requesting a Developer Feedback grant.",()=>n.abort()).then(async a=>{let s=await a.json().catch(()=>null);if(!a.ok){let d=S(s);throw new i(g(d?.detail)??"Could not authorize Developer Feedback.",a.status,g(d?.code))}let l=S(s),c={endpoint:R(l?.endpoint,"developerFeedback.endpoint"),token:v(l?.token,"developerFeedback.token"),expiresAt:Z(l?.expires_at,"developer_feedback.expires_at"),sourceSessionId:v(l?.source_session_id,"developer_feedback.source_session_id")};if(c.sourceSessionId!==e.id)throw new i("Developer Feedback grant targets a different Session.");if(Date.parse(c.expiresAt)<=Date.now())throw new i("Developer Feedback grant is already expired.");if(this.currentSession?.id!==e.id)throw new i("The Waffle session changed while authorizing feedback.");return this.developerFeedbackGrant=c,c}).finally(()=>{this.developerFeedbackGrantPromise===o&&(this.developerFeedbackGrantPromise=null),this.developerFeedbackGrantAbort===n&&(this.developerFeedbackGrantAbort=null)});return this.developerFeedbackGrantPromise=o,o}handleParticipant(e){if(e.kind===W.AGENT){if(this.agentIdentity=e.identity,this.emit("agentSpeaking",e.attributes["lk.agent.state"]==="speaking"),e.attributes["animated-waffle.runtime.ready"]==="true"){if(this.options.application&&e.attributes["animated-waffle.application.version"]!=="2"){this.readyWaiter?.reject(new i("The voice service does not support application sessions.",void 0,"application_session_unsupported"));return}this.readyWaiter?.resolve()}for(let t of e.audioTrackPublications.values()){let n=t.track;if(n?.kind===C.Kind.Audio){this.setAudioTrack(n.mediaStreamTrack);break}}}}handleDisconnected(e){this.readyWaiter?.reject(new i(e,void 0,void 0,"transport")),this.readyWaiter=null,this.turnId=null,this.turnControl=Promise.resolve(),this.failedStartTurnKey=null,this.agentIdentity=null,this.clearTranscriptProgress(),this.stopAudio(),this.emit("agentSpeaking",!1),this.setStatus("disconnected")}setAudioTrack(e){if(e===this.currentRemoteAudioTrack||(this.stopAudio(!1),this.currentRemoteAudioTrack=e,this.emit("remoteAudioTrack",e),_(this,"audioTrack",e),!e||typeof document>"u")||this.options.audioPlayback===!1)return;let t=document.createElement("audio");t.autoplay=!0,t.muted=this.audioMuted,t.setAttribute("playsinline",""),t.srcObject=new MediaStream([e]),this.audio=t,t.play().catch(n=>{this.emit("error",T(n,"audio","Browser blocked agent audio."))})}stopAudio(e=!0){this.audio&&(this.audio.pause(),this.audio.srcObject=null,this.audio=null),e&&this.currentRemoteAudioTrack&&(this.currentRemoteAudioTrack=null,this.emit("remoteAudioTrack",null),_(this,"audioTrack",null))}completeTranscriptExecution(e){this.completedProgressExecutions.add(e);for(let[t,n]of this.progressText)this.progressExecutions.get(t)===e&&(this.emit("transcriptProgress",{...n,complete:!0}),this.progressExecutions.delete(t),this.progressText.delete(t))}clearTranscriptProgress(){this.progressExecutions.clear(),this.progressText.clear(),this.completedProgressExecutions.clear()}setStatus(e){e==="disconnected"&&(this.recreateRoomOnConnect=!0),e==="disconnected"&&this.currentSession&&(this.clearDeveloperFeedbackGrant(),this.currentSession=null,this.emit("session",null),_(this,"presentation",null)),this.currentStatus!==e&&(this.currentStatus=e,this.emit("connectionState",e),this.emit("status",e))}clearDeveloperFeedbackGrant(){this.developerFeedbackGrantAbort?.abort(),this.developerFeedbackGrantAbort=null,this.developerFeedbackGrantPromise=null,this.developerFeedbackGrant=null}fail(e){this.readyWaiter?.reject(e),this.readyWaiter=null,this.stopAudio(),this.emit("agentSpeaking",!1),this.setStatus("disconnected"),this.emit("error",e)}async resolveSessionToken(){if(!this.options.sessionToken)throw new i("No session token provider is configured.");let e=await this.options.sessionToken();if(!e.trim())throw new i("sessionToken must not be empty.",void 0,"invalid_session_token");return e}withTimeout(e,t,n,o,a="session"){return new Promise((s,l)=>{let c=setTimeout(()=>{o?.(),l(new i(n,void 0,void 0,a))},t);e.then(d=>{clearTimeout(c),s(d)},d=>{clearTimeout(c),l(d)})})}emit(e,t){this.listeners.get(e)?.forEach(n=>{n(t)})}};export{F as WaffleClient,i as WaffleError};
|
|
1
|
+
import{b as x}from"./chunk-4OFU2T5E.js";import{z as f}from"zod";var ne=["pelvis","left_hip","right_hip","spine1","left_knee","right_knee","spine2","left_ankle","right_ankle","spine3","left_foot","right_foot","neck","left_collar","right_collar","head","left_shoulder","right_shoulder","left_elbow","right_elbow","left_wrist","right_wrist","jaw","left_eye","right_eye",...["left","right"].flatMap(s=>["index","middle","pinky","ring","thumb"].flatMap(e=>[1,2,3].map(t=>`${s}_${e}${t}`)))],ie=["browDownLeft","browDownRight","browInnerUp","browOuterUpLeft","browOuterUpRight","cheekPuff","cheekSquintLeft","cheekSquintRight","eyeBlinkLeft","eyeBlinkRight","eyeLookDownLeft","eyeLookDownRight","eyeLookInLeft","eyeLookInRight","eyeLookOutLeft","eyeLookOutRight","eyeLookUpLeft","eyeLookUpRight","eyeSquintLeft","eyeSquintRight","eyeWideLeft","eyeWideRight","jawForward","jawLeft","jawOpen","jawRight","mouthClose","mouthDimpleLeft","mouthDimpleRight","mouthFrownLeft","mouthFrownRight","mouthFunnel","mouthLeft","mouthLowerDownLeft","mouthLowerDownRight","mouthPressLeft","mouthPressRight","mouthPucker","mouthRight","mouthRollLower","mouthRollUpper","mouthShrugLower","mouthShrugUpper","mouthSmileLeft","mouthSmileRight","mouthStretchLeft","mouthStretchRight","mouthUpperUpLeft","mouthUpperUpRight","noseSneerLeft","noseSneerRight","tongueOut"];var q=ne.length*4,V=q+3,O=V+ie.length;var W="fused-motion-paired.v5",oe="animated-waffle.avatar-motion.frames",re="avatar.motion.configure",se="avatar.motion.cancel",ae="avatar.motion.playback",le="avatar.motion.interrupt",Ze=f.object({}).strict(),de="avatar.motion.caption",Y=15e3,B=30,Ae=12,Ie=8*1024,Z=60,ce=8*1024*1024,pe=1024,_e=1e4;function U(s,e){if(!Number.isSafeInteger(s)||s<0||!Number.isInteger(e)||e<8e3||e>48e3)throw new Error("Invalid motion sample grid");let t=s*e/B,n=Math.floor(t),i=t-n===.5?n+n%2:Math.round(t);if(!Number.isSafeInteger(i))throw new Error("Invalid motion sample grid");return i}var Pe=f.object({id:f.string().uuid(),start:f.number().int().nonnegative(),end:f.number().int().nonnegative().optional(),cancelled:f.literal(!0).optional()}).strict(),Me=f.object({protocol:f.literal(W),id:f.string().uuid(),seq:f.number().int().nonnegative(),sampleRate:f.number().int().min(8e3).max(48e3),frameStart:f.number().int().nonnegative(),frameCount:f.number().int().nonnegative().max(Ae),replies:f.array(Pe).max(16).refine(s=>new Set(s.map(e=>e.id)).size===s.length,"A reply may appear only once per packet")}).strict(),ue=f.object({id:f.string().uuid(),reason:f.enum(["interrupted","connection_closed"]).optional()}).strict(),et=f.object({id:f.string().uuid(),receivedSeq:f.number().int().min(-1).max(4294967295),missingSeq:f.number().int().nonnegative().max(4294967295).optional(),sample:f.number().int().nonnegative(),state:f.enum(["playing","buffering","stopped"]),replies:f.array(f.object({id:f.string().uuid(),playedSamples:f.number().int().nonnegative(),state:f.enum(["playing","finished","interrupted"])}).strict()).max(16).refine(s=>new Set(s.map(e=>e.id)).size===s.length,"A reply may have only one playback state per report")}).strict().superRefine((s,e)=>{s.missingSeq!==void 0&&s.missingSeq!==s.receivedSeq+1&&e.addIssue({code:"custom",path:["missingSeq"],message:"Missing packet must immediately follow the contiguous receipt"})}),he=f.object({id:f.string().uuid(),replyId:f.string().uuid(),segmentId:f.string().max(128),start:f.number().int().nonnegative(),end:f.number().int().nonnegative().optional(),text:f.string().max(4096)}).strict().superRefine((s,e)=>{s.end!==void 0&&s.end<s.start&&e.addIssue({code:"custom",path:["end"],message:"Caption end precedes its start"}),new TextEncoder().encode(JSON.stringify(s)).byteLength>Y&&e.addIssue({code:"custom",message:"Caption packet exceeds its size limit"})});function fe(s){if(s.byteLength>Ie)throw new Error("Motion packet exceeds its size limit");let e=new DataView(s.buffer,s.byteOffset,s.byteLength);if(s.byteLength<4)throw new Error("Motion chunk is truncated");let t=e.getUint32(0,!0);if(t===0||4+t>s.byteLength)throw new Error("Motion chunk header is invalid");let n=Me.parse(JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(s.subarray(4,4+t)))),i=n.frameCount*O,o=U(n.frameStart+n.frameCount,n.sampleRate)-U(n.frameStart,n.sampleRate),r=4+t+i*2;if(s.byteLength!==r+o*2)throw new Error("Motion chunk payload does not match its frame and sample intervals");let l=new Float32Array(i);for(let a=0;a<i;a++){let p=a%O,g=p>=q&&p<V?_e:32767;l[a]=e.getInt16(4+t+a*2,!0)/g}for(let a=0;a<i;a+=O)for(let p=a;p<a+q;p+=4)if(Math.abs(Math.hypot(l[p],l[p+1],l[p+2],l[p+3])-1)>.01)throw new Error("Motion quaternion is not normalized");let c=new Int16Array(o);for(let a=0;a<o;a++)c[a]=e.getInt16(r+a*2,!0);return{...n,frames:l,audio:c}}function Ee(s,e=60,t=.1){let n="",i=0,o=0,r=0,l=0,c=!0,a=!0,p=[],g=new Map,v=new Set,y=m=>{v.add(m),v.size>64&&v.delete(v.values().next().value)},b=()=>o/s,u=m=>{m>b()&&(o=m*s,l++)},k=()=>{for(;p.length&&p[0].start+p[0].audio.length<=Math.floor(b());)p.shift();for(let[m,S]of g)S.end!==void 0&&S.start+S.end<b()-i&&g.delete(m)},M=()=>[...g.values()].sort((m,S)=>m.start-S.start),_=()=>{for(let m of M()){let S=b(),I=m.end===void 0?1/0:m.start+m.end;if(!(I<=S)){if((m.cancelled||v.has(m.id))&&S>=m.start){if(!Number.isFinite(I)||I>r)return!1;u(I);continue}S<m.start&&m.start<r&&u(m.start);break}}return k(),!0};return{reset(m,S){n=m,i=S,o=0,r=0,l++,c=!0,a=!1,p=[],g.clear(),v.clear()},push(m,S,I){if(a)throw new Error("paired_player_stopped");if(m!==r)throw new Error("paired_audio_gap");if(r+S.length-b()>e*i)throw new Error("paired_buffer_overflow");for(let h of I){let w=g.get(h.id);if(w&&(w.start!==h.start||w.end!==void 0&&h.end!==void 0&&w.end!==h.end))throw new Error("paired_reply_changed");g.set(h.id,{...w,...h}),h.cancelled&&y(h.id)}if(g.size>64)throw new Error("paired_reply_overflow");S.length&&p.push({start:m,audio:S}),r+=S.length},cancel(m){if(m)y(m);else for(let S of g.values())y(S.id);c=!0},stop(){a=!0,p=[],g.clear(),c=!0},render(m){if(m.fill(0),a||!n)return{id:n,start:b(),sample:b(),epoch:l,state:"stopped",rendered:0};let S=_(),I=b(),h=l,w=M().find(P=>P.start<=I&&(P.end===void 0||P.start+P.end>I)),$=w?.end!==void 0&&w.start+w.end<=r;if(c&&(r-I>=i*t||$)&&(c=!1),!S||c)return{id:n,start:I,sample:I,epoch:h,state:"buffering",rendered:0};let Q=0,D=0,ke=M().find(P=>(P.cancelled||v.has(P.id))&&(P.end===void 0||P.start+P.end>I))?.start??1/0;for(let P=0;P<m.length;P++){if(b()>=ke){c=!0;break}let G=Math.floor(b()),ee=b()-G;for(;D<p.length&&G>=p[D].start+p[D].audio.length;)D++;let L=p[D];if(!L||G<L.start||b()+i/s>r){c=!0;break}let J=G-L.start,X=L.audio[J]/32768,te=J+1<L.audio.length?L.audio[J+1]:p[D+1]?.audio[0],Te=ee>0&&te!==void 0?te/32768:X;m[P]=X+(Te-X)*ee,o+=i,Q++}return k(),{id:n,start:I,sample:b(),epoch:h,state:c?"buffering":"playing",rendered:Q}},position:b}}var me=`
|
|
2
|
+
const createEngine = ${Ee.toString()};
|
|
3
|
+
registerProcessor('waffle-paired-playout', class extends AudioWorkletProcessor {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
super(); this.engine = createEngine(sampleRate, options.processorOptions.maxSeconds, options.processorOptions.bufferSeconds);
|
|
6
|
+
this.port.onmessage = ({data}) => {
|
|
7
|
+
try {
|
|
8
|
+
if (data.type === 'reset') this.engine.reset(data.id, data.sampleRate);
|
|
9
|
+
else if (data.type === 'push') this.engine.push(data.start, data.audio, data.replies);
|
|
10
|
+
else if (data.type === 'cancel') this.engine.cancel(data.id);
|
|
11
|
+
else if (data.type === 'stop') this.engine.stop();
|
|
12
|
+
} catch (_) { this.engine.stop(); this.port.postMessage({type:'failed'}); }
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
process(inputs, outputs) {
|
|
16
|
+
const output = outputs[0] && outputs[0][0];
|
|
17
|
+
if (!output) return true;
|
|
18
|
+
const state = this.engine.render(output);
|
|
19
|
+
this.port.postMessage({type:'clock', ...state, contextStart:currentFrame/sampleRate,
|
|
20
|
+
contextEnd:(currentFrame+state.rendered)/sampleRate, quantumEnd:(currentFrame+output.length)/sampleRate});
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
`;var Ce=15e3,j=class{constructor(e,t=()=>new AudioContext){this.callbacks=e;this.makeContext=t}callbacks;makeContext;context=null;graph=null;ready=null;timeline=null;clocks=[];ended=new Set;captions=new Map;visible=new Map;cancelled=new Set;cancelledSamples=new Map;animation=null;shown=null;muted=!1;failed=!1;disposed=!1;lastProgress="";lastProgressAt=-1/0;replyStates=new Map;snapshot=null;heartbeat=null;prepare(){if(this.ready)return this.ready;let e=this.context=this.makeContext(),t=e.resume();e.onstatechange=()=>{e.state==="closed"&&!this.disposed?this.fail(new Error("paired_audio_closed")):(this.update(!0,!1),this.animate())};let n=URL.createObjectURL(new Blob([me],{type:"text/javascript"}));return this.ready=Promise.all([t,e.audioWorklet.addModule(n)]).then(()=>{if(this.disposed||this.context!==e)return;if(e.state!=="running")throw new Error("paired_audio_suspended");let i=new AudioWorkletNode(e,"waffle-paired-playout",{numberOfInputs:0,numberOfOutputs:1,outputChannelCount:[1],processorOptions:{maxSeconds:Z,bufferSeconds:.1}}),o=e.createGain();o.gain.value=this.muted?0:1,i.port.onmessage=({data:r})=>{if(!(this.disposed||this.failed)){if(r.type==="failed")return this.fail(new Error("paired_worklet_failed"));r.type!=="clock"||r.id!==this.timeline?.id||(this.clocks.push(r),this.clocks=this.clocks.filter(l=>l.quantumEnd>=e.currentTime-2),this.update(!1,!1),this.animate())}},i.onprocessorerror=()=>this.fail(new Error("paired_worklet_failed")),i.connect(o),o.connect(e.destination),this.graph={node:i,gain:o},this.heartbeat=setInterval(()=>{if(this.disposed||this.failed)return;let r=this.timeline?.gapSince;if(r!=null&&performance.now()-r>=Ce){this.fail(new Error(`paired_chunk_gap_timeout: seq ${this.timeline.seq+1}`));return}this.update(!0,!1)},1e3)}).finally(()=>URL.revokeObjectURL(n)),this.ready}async resume(){await this.prepare(),await this.context?.resume()}setMuted(e){this.muted=e,this.graph&&(this.graph.gain.gain.value=e?0:1)}add(e){if(this.disposed||this.failed||this.ended.has(e.id))return;if(!this.graph)throw new Error("paired_audio_not_ready");let t=this.timeline;if(t?.id!==e.id){t&&(this.clear(),this.remember(this.ended,t.id)),t=this.timeline={id:e.id,rate:e.sampleRate,nextFrame:0,seq:-1,sample:0,frames:new Map,replies:new Map,cancelled:new Set,pending:new Map,pendingBytes:0,gapSince:null},this.clocks=[],this.lastProgress="",this.lastProgressAt=-1/0,this.replyStates.clear(),this.snapshot=null,this.visible.clear(),this.show(null),this.graph.node.port.postMessage({type:"reset",id:e.id,sampleRate:e.sampleRate});for(let[c,a]of this.captions)this.ended.has(a.id)&&this.captions.delete(c)}if(e.seq<=t.seq||t.pending.has(e.seq))return;if(e.sampleRate!==t.rate)throw new Error("paired_sample_rate_changed");let n=U(e.frameStart,e.sampleRate),i=U(e.frameStart+e.frameCount,e.sampleRate);if(e.audio.length!==i-n||e.frames.length!==e.frameCount*O)throw new Error("paired_chunk_length");if(i-t.sample>Z*t.rate)throw new Error("paired_buffer_overflow");let o=e.frames.byteLength+e.audio.byteLength+new TextEncoder().encode(JSON.stringify({...e,frames:void 0,audio:void 0})).byteLength;if(t.pending.size>=pe||t.pendingBytes+o>ce)throw new Error("paired_reorder_overflow");t.pending.set(e.seq,{chunk:e,bytes:o}),t.pendingBytes+=o;let r=t.seq,l=t.gapSince!==null;for(;t.pending.has(t.seq+1);){let c=t.pending.get(t.seq+1);this.append(t,c.chunk),t.pending.delete(t.seq),t.pendingBytes-=c.bytes}t.gapSince=t.pending.size?t.gapSince??performance.now():null,this.update(l!==(t.gapSince!==null)||r===-1,!1),this.animate()}append(e,t){if(t.frameStart!==e.nextFrame)throw new Error(`paired_frame_gap: seq ${t.seq}; frame ${e.nextFrame}->${t.frameStart}`);let n=U(t.frameStart,t.sampleRate);for(let o of t.replies){let r=e.replies.get(o.id);if(r&&(r.start!==o.start||r.end!==void 0&&o.end!==void 0&&r.end!==o.end))throw new Error("paired_reply_changed");e.replies.set(o.id,{...r,...o}),(o.cancelled||this.cancelled.has(o.id))&&(e.cancelled.add(o.id),this.cancelledSamples.has(o.id)||this.cancelledSamples.set(o.id,Math.min(o.end??1/0,Math.max(0,Math.floor(e.sample-o.start)))))}if(e.replies.size>64)throw new Error("paired_reply_overflow");for(let o=0;o<t.frameCount;o++)e.frames.set(t.frameStart+o,t.frames.slice(o*O,(o+1)*O));e.nextFrame+=t.frameCount,e.seq=t.seq;let i=Int16Array.from(t.audio);this.graph.node.port.postMessage({type:"push",start:n,audio:i,replies:t.replies},[i.buffer]);for(let o of t.replies)e.cancelled.has(o.id)&&this.graph.node.port.postMessage({type:"cancel",id:o.id})}addCaption(e){if(this.ended.has(e.id)||this.disposed||this.failed)return;let t=`${e.id}:${e.replyId}:${e.segmentId}`,n=this.captions.get(t);if(n&&(n.start!==e.start||n.text!==e.text||n.end!==void 0&&e.end!==void 0&&n.end!==e.end))throw new Error("paired_caption_changed");if(this.captions.size>=128&&!n){for(let i of this.captions.values())if(!(i.replyId===e.replyId||!this.visible.get(i.replyId)?.complete)){for(let[o,r]of this.captions)r.replyId===i.replyId&&this.captions.delete(o);if(this.captions.size<128)break}}if(this.captions.size>=128&&!n)throw new Error("paired_caption_overflow");this.captions.set(t,{...n,...e}),this.animate()}cancel(e){this.update();let t=this.timeline?.replies.get(e);return t?.end!==void 0&&this.timeline.sample>=t.start+t.end&&!this.timeline.cancelled.has(e)?this.snapshot:(this.cancelledSamples.has(e)||this.cancelledSamples.set(e,t?Math.min(t.end??1/0,Math.max(0,Math.floor(this.timeline.sample-t.start))):0),this.cancelledSamples.size>64&&this.cancelledSamples.delete(this.cancelledSamples.keys().next().value),this.remember(this.cancelled,e),this.timeline?.cancelled.add(e),this.graph?.node.port.postMessage({type:"cancel",id:e}),this.show(null),this.update(!0),this.snapshot)}clear(){for(let e of this.timeline?.replies.values()??[])this.cancel(e.id)}fail(e){this.failed||this.disposed||(this.clear(),this.failed=!0,this.heartbeat!==null&&clearInterval(this.heartbeat),this.heartbeat=null,this.timeline?.pending.clear(),this.timeline&&(this.timeline.pendingBytes=0),this.graph?.node.port.postMessage({type:"stop"}),this.show(null),this.update(!0),this.callbacks.fail(e))}dispose(){this.disposed||(this.clear(),this.disposed=!0,this.heartbeat!==null&&clearInterval(this.heartbeat),this.heartbeat=null,this.graph?.node.port.postMessage({type:"stop"}),this.graph&&(this.graph.node.port.onmessage=null,this.graph.node.disconnect(),this.graph.gain.disconnect()),this.graph=null,this.animation!==null&&cancelAnimationFrame(this.animation),this.animation=null,this.context&&(this.context.onstatechange=null,this.context.close().catch(()=>{})),this.context=null,this.timeline=null,this.clocks=[],this.captions.clear(),this.visible.clear(),this.show(null))}audibleClock(){let e=this.context,t=this.timeline;if(!e||!t)return null;if(e.state!=="running")return{sample:t.sample,epoch:this.clocks.at(-1)?.epoch??0,state:this.failed?"stopped":"buffering"};let n=e.getOutputTimestamp?.(),i=typeof n?.performanceTime=="number"&&n.performanceTime>0?Math.max(0,(performance.now()-n.performanceTime)/1e3):0,o=typeof n?.contextTime=="number"&&n.contextTime>0?Math.min(e.currentTime,n.contextTime+i):Math.max(0,e.currentTime-(e.outputLatency||0)-(e.baseLatency||0)),r=[...this.clocks].reverse().find(a=>a.contextStart<=o);if(!r)return{sample:t.sample,epoch:0,state:"buffering"};let l=r.contextEnd>r.contextStart?Math.min(1,Math.max(0,(o-r.contextStart)/(r.contextEnd-r.contextStart))):0,c=r.start+(r.sample-r.start)*l;return{sample:Math.max(t.sample,c),epoch:r.epoch,state:this.failed?"stopped":e.state!=="running"||o>=r.contextEnd?"buffering":"playing"}}update(e=!1,t=!0){let n=this.timeline,i=this.audibleClock();if(!n||!i)return!1;n.sample=i.sample;let o=[];for(let u of n.replies.values()){let k=n.cancelled.has(u.id),M=k?this.cancelledSamples.get(u.id)??0:Math.min(u.end??1/0,Math.max(0,Math.floor(n.sample-u.start)));if(n.sample<u.start&&!k||M===0&&!k&&u.end!==0)continue;let _=k?"interrupted":u.end!==void 0&&M>=u.end?"finished":"playing";o.push({id:u.id,playedSamples:M,state:_}),this.caption(u,M,_!=="playing",_==="finished")}let r=[...n.replies.values()].find(u=>u.start<=n.sample&&(u.end===void 0||n.sample<u.start+u.end)),l=Math.min(n.nextFrame-1,Math.floor(n.sample*B/n.rate)),c=n.frames.get(l);t&&!this.failed&&!(r&&n.cancelled.has(r.id))&&c&&this.show({id:`${n.id}:${i.epoch}`,index:l,values:c});let a={id:n.id,sample:Math.floor(n.sample),receivedSeq:n.seq,...n.pending.size?{missingSeq:n.seq+1}:{},state:this.failed?"stopped":i.state,replies:o.slice(-16)};this.snapshot=a;let p=a.replies.filter(u=>u.state==="finished"&&u.playedSamples>0&&!this.replyStates.has(u.id));if(p.length){let u=new Set(p.map(k=>k.id));this.callbacks.progress({...a,replies:a.replies.map(k=>u.has(k.id)?{...k,state:"playing"}:k)});for(let k of p)this.replyStates.set(k.id,"playing")}let g=JSON.stringify(a),v=o.some(u=>this.replyStates.get(u.id)!==u.state);if(e||g!==this.lastProgress&&(v||performance.now()-this.lastProgressAt>=100)){this.lastProgress=g,this.lastProgressAt=performance.now(),this.callbacks.progress(a);for(let u of o)this.replyStates.set(u.id,u.state)}let y=Math.floor(n.sample*B/n.rate)-2;for(let u of n.frames.keys())u<y&&n.frames.delete(u);let b=o.filter(u=>u.state!=="playing").slice(0,-16);for(let u of b){n.replies.delete(u.id),n.cancelled.delete(u.id),this.visible.delete(u.id),this.replyStates.delete(u.id);for(let[k,M]of this.captions)M.replyId===u.id&&this.captions.delete(k)}return!this.disposed&&!this.failed}caption(e,t,n,i){let o=this.timeline,r=[...this.captions.values()].filter(p=>p.id===o.id&&p.replyId===e.id).sort((p,g)=>p.start-g.start);if(!r.length)return;let l=r.map(p=>{let g=Array.from(p.text),v=Math.max(0,t-p.start),y=i?g.length:p.end!==void 0?Math.floor(g.length*Math.min(1,v/Math.max(1,p.end-p.start))):Math.floor(v/o.rate*13);return g.slice(0,y).join("")}).join(""),c=this.visible.get(e.id),a={segmentId:e.id,role:"assistant",text:l.length<(c?.text.length??0)?c.text:l,complete:n};a.text&&(a.text!==c?.text||a.complete!==c.complete)&&(this.visible.set(e.id,a),this.callbacks.caption(a))}animate(){this.animation!==null||this.disposed||this.failed||typeof requestAnimationFrame!="function"||(this.animation=requestAnimationFrame(()=>{this.animation=null,this.update()&&this.animate()}))}show(e){let t=e?`${e.id}:${e.index}`:null;this.shown!==t&&(this.shown=t,this.callbacks.frame(e))}remember(e,t){e.add(t),e.size>64&&e.delete(e.values().next().value)}};import{DataPacket_Kind as xe,ParticipantKind as N,Room as Re,RoomEvent as C,RpcError as Se,Track as K}from"livekit-client";var ge=15e3,Oe=3e4,Fe="https://animated-waffle.narya.ai",z=/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,We=/^sha256:[a-f0-9]{64}$/,d=class extends Error{constructor(t,n,i,o="session",r){super(t,r);this.status=n;this.code=i;this.stage=o;this.name="WaffleError"}status;code;stage;rpcMethod;livekitErrorCode},F={APPLICATION_ERROR:1500,CONNECTION_TIMEOUT:1501,RESPONSE_TIMEOUT:1502,RECIPIENT_DISCONNECTED:1503},Ne=new Set([F.CONNECTION_TIMEOUT,F.RESPONSE_TIMEOUT]);function De(s){return s instanceof Se&&Ne.has(s.code)}function Le(s){return s===F.RECIPIENT_DISCONNECTED?"push_to_talk_agent_unavailable":s===F.CONNECTION_TIMEOUT||s===F.RESPONSE_TIMEOUT?"push_to_talk_timeout":"push_to_talk_failed"}function Ue(s){return s===F.RECIPIENT_DISCONNECTED?"The Waffle agent dropped out for a moment. Release and press again to talk.":s===F.CONNECTION_TIMEOUT||s===F.RESPONSE_TIMEOUT?"The voice turn took too long to reach the agent. Release and try again.":"That voice turn didn\u2019t go through. Press and hold to talk again."}function qe(s,e){let t=s instanceof Se?s.code:void 0,n=new d(Ue(t),void 0,Le(t),"transport",{cause:s});return n.rpcMethod=e,typeof t=="number"&&(n.livekitErrorCode=t),n}function E(s,e,t){return s instanceof d?s:new d(s instanceof Error?s.message:t,void 0,void 0,e,{cause:s})}function R(s){return s!==null&&typeof s=="object"&&!Array.isArray(s)?s:null}function Ge(s=Fe){let e;try{e=new URL(s)}catch{throw new d("apiOrigin must be a valid URL.",void 0,"invalid_api_origin")}let t=e.hostname==="localhost"||e.hostname==="127.0.0.1"||e.hostname==="[::1]";if(e.protocol!=="https:"&&!(e.protocol==="http:"&&t)||e.username||e.password||e.pathname!=="/"&&e.pathname!==""||e.search||e.hash)throw new d("apiOrigin must be an HTTPS origin or an HTTP loopback origin.",void 0,"invalid_api_origin");return e.origin}function T(s,e){if(typeof s!="string"||s.trim().length===0)throw new d(`Session response is missing ${e}.`);return s}function Ve(s){let e=T(s,"avatar.asset_hash");if(!We.test(e))throw new d("Session response contains an invalid Avatar asset hash.");return e}function ve(s){return typeof s=="number"&&Number.isFinite(s)?s:void 0}function A(s){return typeof s=="string"&&s.length>0?s:void 0}function ye(s,e){let t=T(s,e);try{let n=new URL(t),i=n.hostname==="localhost"||n.hostname==="127.0.0.1"||n.hostname==="[::1]";if(n.protocol!=="https:"&&!(n.protocol==="http:"&&i))throw new Error;if(n.username||n.password||n.hash)throw new Error;return n.toString()}catch{throw new d(`Received an invalid ${e}.`)}}function Be(s,e){let t=T(s,e);if(!/^\d{4}-\d{2}-\d{2}T/.test(t)||!Number.isFinite(Date.parse(t)))throw new d(`Received an invalid ${e}.`);return t}function we(s,e){let t=A(s[`${e}.speech_id`]),n=Number(s[`${e}.speech_step_index`]);return t&&Number.isSafeInteger(n)&&n>0?JSON.stringify([t,n]):null}function je(s,e,t,n){let i=R(s);if(!i)throw new d("Session response must be a JSON object.");if(i.protocol_version!==2)throw new d("Session response has an unsupported protocol version.");let o=R(i.connection);if(!o||o.transport!=="webrtc")throw new d("Session response is missing a WebRTC connection.");let r=T(o.url,"connection.url");try{let y=new URL(r),b=y.hostname==="localhost"||y.hostname==="127.0.0.1"||y.hostname==="[::1]";if(y.protocol!=="wss:"&&!(y.protocol==="ws:"&&b))throw new Error}catch{throw new d("Session response contains an invalid connection URL.")}let l=T(i.agent_id,"agent_id");if(l!==t)throw new d("Session response contains a mismatched Agent id.");let c=A(i.agent_studio_url);if(c)try{let y=new URL(c),b=y.hostname==="localhost"||y.hostname==="127.0.0.1"||y.hostname==="[::1]";if(y.protocol!=="https:"&&!(y.protocol==="http:"&&b))throw new Error}catch{throw new d("Session response contains an invalid Agent Studio URL.")}let a=A(i.avatar_id),p=R(i.avatar),g=null;if(a||p){if(!a||!p||T(p.id,"avatar.id")!==a)throw new d("Session response contains mismatched Avatar metadata.");let y=T(p.engine,"avatar.engine");if(y!=="vrm")throw new d("Session response contains an unsupported Avatar engine.");let b=T(p.model_url,"avatar.model_url");try{let u=new URL(b),k=u.hostname==="localhost"||u.hostname==="127.0.0.1"||u.hostname==="[::1]";if(u.protocol!=="https:"&&!(u.protocol==="http:"&&k))throw new Error}catch{throw new d("Session response contains an invalid Avatar model URL.")}g={id:a,name:T(p.name,"avatar.name"),engine:y,modelUrl:b,assetHash:Ve(p.asset_hash),runtimeProfile:T(p.runtime_profile,"avatar.runtime_profile"),...Ke(p.presentation_profile)}}let v=T(i.session_id,"session_id");return{id:v,agentId:l,...c?{agentStudioUrl:c}:{},...a?{avatarId:a}:{},...n?{developerFeedback:{sourceSessionId:v}}:{},avatar:g,inputMode:e,room:r,token:T(o.token,"connection.token")}}function Ke(s){if(s===void 0)return{};let e=R(s);if(!e)throw new d("Session response contains an invalid Avatar presentation profile.");let t=T(e.framing,"avatar.presentation_profile.framing"),n=T(e.attention,"avatar.presentation_profile.attention"),i=T(e.emotion,"avatar.presentation_profile.emotion");if(!["default","full","upper","face"].includes(t)||!["natural","focused","lively"].includes(n)||!["natural","subtle","expressive"].includes(i))throw new d("Session response contains an unsupported Avatar presentation profile.");return{presentationProfile:{framing:t,attention:n,emotion:i}}}function ze(s){let e=R(s);if(!e||typeof e.type!="string")return null;let t={contextId:A(e.contextId)??null,segmentId:A(e.segmentId),occurrence:ve(e.occurrence),audioTime:ve(e.audioTime)};if(e.type==="avatar-emotion"){let n=A(e.emotion);return n?{phase:"emotion",emotion:n,...t}:null}if(e.type==="avatar-vocal-action"){let n=A(e.action);return n?{phase:"vocal-action",action:n,...t}:null}return null}function He(s){if(!s||typeof s!="object")return null;let e=s;if(e.type!=="agent-declined")return null;let t=A(e.reason);return t?{reason:t}:null}function H(s){let{avatar:e,room:t,token:n,...i}=s;return i}var be=class{room;listeners=new Map;options;apiOrigin;currentStatus="disconnected";currentSession=null;startPromise=null;startVersion=null;stopPromise=null;requestAbort=null;readyWaiter=null;audio=null;audioMuted=!1;motionSync=null;motionAgentIdentity=null;motionPreparing=null;motionFeedback=[];motionFeedbackRevision=0;motionFeedbackAcknowledged=0;motionInterruptBarrier=null;motionFeedbackSending=null;currentRemoteAudioTrack=null;activeInputMode="push-to-talk";agentIdentity=null;progressExecutions=new Map;progressText=new Map;completedProgressExecutions=new Set;turnId=null;nextTurnId=0;turnControl=Promise.resolve();failedStartTurnKey=null;lifecycleVersion=0;recreateRoomOnConnect=!1;developerFeedbackGrant=null;developerFeedbackGrantPromise=null;developerFeedbackGrantAbort=null;constructor(e){if(e.avatarMotion===!0&&e.audioPlayback===!1)throw new d("avatarMotion requires SDK-owned audio playback.",void 0,"invalid_avatar_motion");if(this.options=e,this.apiOrigin=Ge(e.apiOrigin),!z.test(e.agentId))throw new d("agentId must be a UUID.",void 0,"invalid_agent_id");if(!e.startSession&&typeof e.sessionToken!="function")throw new d("sessionToken must be a function.",void 0,"invalid_session_token");this.room=this.createRoom()}createRoom(){let e=new Re({adaptiveStream:!0,dynacast:!0,audioCaptureDefaults:{voiceIsolation:!1}});return e.registerRpcMethod(re,async t=>{let n=this.lifecycleVersion,i;try{i=JSON.parse(t.payload)?.protocol}catch{}e===this.room&&this.currentStatus==="connecting"&&this.options.avatarMotion===!0&&i===W&&!e.remoteParticipants.has(t.callerIdentity)&&await this.waitForMotionParticipant(e,t.callerIdentity),this.options.avatarMotion===!0&&i===W&&this.motionPreparing&&await this.motionPreparing;let o=e.remoteParticipants.get(t.callerIdentity),r=n===this.lifecycleVersion&&e===this.room&&this.currentStatus==="connecting"&&o?.kind===N.AGENT&&(!this.agentIdentity||this.agentIdentity===t.callerIdentity)&&(!this.motionAgentIdentity||this.motionAgentIdentity===t.callerIdentity)&&this.options.avatarMotion===!0&&this.options.audioPlayback!==!1&&this.motionSync!==null&&i===W;return r&&(this.motionAgentIdentity=t.callerIdentity,this.stopAudioElement(),this.currentRemoteAudioTrack=null,this.emit("remoteAudioTrack",null),x(this,"audioTrack",null),x(this,"motionMode",!0)),JSON.stringify({accepted:r,protocol:W})}),e.registerRpcMethod(se,async t=>{if(e!==this.room||t.callerIdentity!==this.motionAgentIdentity)throw new Error("Untrusted motion cancellation.");let{id:n}=ue.parse(JSON.parse(t.payload)),i=this.motionSync?.cancel(n);return i?JSON.stringify(i):"ok"}),e.registerRpcMethod(de,async t=>{if(e!==this.room||t.callerIdentity!==this.motionAgentIdentity||!this.motionSync)throw new Error("Untrusted motion caption.");try{if(new TextEncoder().encode(t.payload).byteLength>Y)throw new Error("paired_caption_too_large");return this.motionSync.addCaption(he.parse(JSON.parse(t.payload))),"{}"}catch(n){throw this.failMotion(n),n}}),e.on(C.ParticipantConnected,t=>{e===this.room&&this.handleParticipant(t)}),e.on(C.ParticipantDisconnected,t=>{e!==this.room||t.kind!==N.AGENT||(this.agentIdentity=null,this.currentStatus!=="disconnecting"&&(this.handleDisconnected("The Waffle Agent disconnected."),e.disconnect().catch(n=>{this.emit("error",E(n,"transport","Could not close the Agent room."))})))}),e.on(C.ParticipantAttributesChanged,(t,n)=>{e===this.room&&this.handleParticipant(n)}),e.on(C.TrackSubscribed,(t,n,i)=>{e===this.room&&i.kind===N.AGENT&&t.kind===K.Kind.Audio&&this.setAudioTrack(t.mediaStreamTrack)}),e.on(C.TrackUnsubscribed,(t,n,i)=>{e===this.room&&i.kind===N.AGENT&&t.kind===K.Kind.Audio&&this.setAudioTrack(null)}),e.on(C.DataReceived,(t,n,i,o)=>{if(!(e!==this.room||n?.kind!==N.AGENT||i!==xe.RELIABLE)){if(o===oe){if(n.identity!==this.motionAgentIdentity)return;try{this.motionSync?.add(fe(t))}catch(r){this.failMotion(r)}return}if(!(o!=="avatar.performance"&&o!=="animated-waffle.turn"))try{let r=JSON.parse(new TextDecoder().decode(t));if(o==="animated-waffle.turn"){let c=He(r);c&&this.emit("declined",c);return}let l=ze(r);l&&x(this,"avatar",l)}catch{}}}),e.on(C.Disconnected,()=>{e===this.room&&this.handleDisconnected("The Agent session disconnected before it was ready.")}),e.registerTextStreamHandler("lk.transcription",(t,n)=>{(async()=>{let i=this.agentIdentity?e.remoteParticipants.get(this.agentIdentity):void 0;if(e!==this.room||this.currentStatus!=="connected"||i?.kind!==N.AGENT)return;let o=t.info.attributes??{},r=A(o["lk.segment_id"]),l=A(o["lk.transcribed_track_id"]),c=o["lk.transcription_final"];if(!r||!l||c!=="true"&&c!=="false")return;let a=e.localParticipant.audioTrackPublications.has(l),p=i.audioTrackPublications.has(l);if(a===p)return;let g=a?"user":"assistant";if(n.identity!==(a?e.localParticipant.identity:i.identity))return;let v=g==="assistant"?we(o,"lk"):null;if(g==="assistant"&&!v||v&&(this.progressExecutions.set(r,v),this.completedProgressExecutions.has(v)))return;let y="";for await(let b of t){if(y+=b,e!==this.room||this.currentStatus!=="connected"||v&&this.completedProgressExecutions.has(v))return;if(y.length>0){let u={segmentId:r,role:g,text:y,complete:!1};this.progressText.set(r,u),this.emit("transcriptProgress",u)}}c==="true"&&y.length>0&&(this.emit("transcriptProgress",{segmentId:r,role:g,text:y,complete:!0}),this.progressExecutions.delete(r),this.progressText.delete(r))})().catch(i=>{this.emit("error",E(i,"transcript","Transcript stream failed."))})}),e.registerTextStreamHandler("animated-waffle.transcript",(t,n)=>{t.readAll().then(i=>{if(e!==this.room||n.identity!==this.agentIdentity||!i)return;let o=t.info.attributes??{},r=A(o["animated-waffle.message_id"]),l=o["animated-waffle.role"];if(!r||l!=="user"&&l!=="assistant"||o["animated-waffle.final"]!=="true")return;let c=l==="assistant"?we(o,"animated-waffle"):null;c&&!this.motionAgentIdentity&&this.completeTranscriptExecution(c);let a=Number(t.info.timestamp);this.emit("transcript",{id:r,role:l,text:i,final:!0,...Number.isFinite(a)&&a>0?{timestamp:new Date(a).toISOString()}:{}})}).catch(i=>{this.emit("error",E(i,"transcript","Transcript stream failed."))})}),e}get status(){return this.currentStatus}get connectionState(){return this.currentStatus}get session(){return this.currentSession?H(this.currentSession):null}get remoteAudioTrack(){return this.currentRemoteAudioTrack}get muted(){return this.audioMuted}set muted(e){this.audioMuted=e,this.audio&&(this.audio.muted=e),this.motionSync?.setMuted(e)}on(e,t){let n=this.listeners.get(e)??new Set;return n.add(t),this.listeners.set(e,n),()=>n.delete(t)}connect(e={}){if(this.currentSession&&this.status==="connected"){let i=H(this.currentSession);return this.motionSync?this.motionSync.resume().then(()=>i):Promise.resolve(i)}if(this.startPromise&&this.startVersion===this.lifecycleVersion)return this.startPromise;if(this.options.avatarMotion===!0&&this.options.audioPlayback!==!1&&typeof AudioContext<"u")try{this.prepareMotionSync().catch(()=>{})}catch(i){let o=E(i,"audio","Could not prepare avatar motion audio.");return this.fail(o),Promise.reject(o)}let t=++this.lifecycleVersion,n=(async()=>{let i=this.stopPromise;if(i&&await i,t!==this.lifecycleVersion)throw new d("The connection attempt was cancelled.");if(this.options.avatarMotion===!0&&typeof AudioContext<"u")try{await this.prepareMotionSync()}catch(o){if(t!==this.lifecycleVersion)throw new d("The connection attempt was cancelled.");let r=E(o,"audio","Could not prepare avatar motion audio.");throw this.fail(r),r}if(t!==this.lifecycleVersion)throw new d("The connection attempt was cancelled.");return this.recreateRoomOnConnect&&(this.room=this.createRoom(),this.recreateRoomOnConnect=!1),this.startInternal(e,t)})().finally(()=>{this.startPromise===n&&(this.startPromise=null,this.startVersion=null)});return this.startVersion=t,this.startPromise=n,n}disconnect(){if(this.lifecycleVersion+=1,this.stopPromise)return this.stopPromise;let e=this.stopInternal().finally(()=>{this.stopPromise===e&&(this.stopPromise=null)});return this.stopPromise=e,e}async sendText(e,t){if(!e.trim())return;this.assertConnected();let n=t?.audioResponse===!1?{"animated-waffle.audio_response":"false"}:void 0;await this.room.localParticipant.sendText(e,{topic:"lk.chat",...n?{attributes:n}:{}})}async setInputMode(e){await this.applicationControl("input_mode",{mode:e}),this.activeInputMode=e,this.turnId=null}async interrupt(){this.assertConnected();let e=this.motionAgentIdentity!==null&&this.motionSync!==null,t=this.room,n=this.motionAgentIdentity,i=this.motionSync;if(this.motionSync?.clear(),e&&(await this.waitForMotionInterruption(),!this.options.application)){if(t!==this.room||n!==this.motionAgentIdentity||i!==this.motionSync)throw new d("The interrupted session is no longer connected.");try{await t.localParticipant.performRpc({destinationIdentity:n,method:le,payload:"{}",responseTimeout:5e3})}catch(o){throw t===this.room&&i===this.motionSync&&this.failMotion(o),o}return}await this.applicationControl("interrupt",{})}get microphoneTrack(){return this.room.localParticipant.getTrackPublication(K.Source.Microphone)?.audioTrack?.mediaStreamTrack??null}async setMicrophoneEnabled(e){if(this.assertConnected(),e&&this.options.inputAudioEnabled===!1)throw new d("Input audio is disabled.",void 0,"input_audio_disabled");await this.room.localParticipant.setMicrophoneEnabled(e)}async applicationControl(e,t){if(this.assertConnected(),!this.options.application||!this.agentIdentity)throw new d("Application sessions are required.",void 0,"application_session_required");return this.room.localParticipant.performRpc({destinationIdentity:this.agentIdentity,method:"application.control",payload:JSON.stringify({action:e,...t})})}async loadDeveloperFeedback(){return this.requestDeveloperFeedback("GET")}async submitDeveloperFeedback(e){let t=e.id??crypto.randomUUID();if(!z.test(t))throw new d("Developer Feedback id must be a UUID.",void 0,"invalid_feedback");if(e.scope==="message"&&!e.targetMessageId)throw new d("Message feedback requires a persisted Assistant message id.",void 0,"invalid_feedback");if(e.scope==="session"&&e.targetMessageId)throw new d("Session feedback cannot target one message.",void 0,"invalid_feedback");if(e.targetMessageId&&!z.test(e.targetMessageId))throw new d("Developer Feedback targetMessageId must be a UUID.",void 0,"invalid_feedback");if(e.action==="change"&&!e.note?.trim()&&!e.preferredReply?.trim()&&e.dimension!=="avatar"&&e.dimension!=="voice")throw new d("Describe the requested change or provide a preferred reply.",void 0,"invalid_feedback");return this.requestDeveloperFeedback("POST",{id:t,scope:e.scope,targetMessageId:e.targetMessageId??null,action:e.action,dimension:e.dimension,note:e.note??null,preferredReply:e.preferredReply??null})}startPushToTalk(){if(this.assertPushToTalkAvailable(),this.turnId!==null)return this.turnControl;this.nextTurnId+=1,this.turnId=this.nextTurnId;try{return this.sendInputControl("start_turn",String(this.turnId))}catch(e){throw this.turnId=null,e}}commitPushToTalk(){if(this.assertPushToTalkAvailable(),this.turnId===null)return this.turnControl;let e=String(this.turnId);return this.turnId=null,this.sendInputControl("end_turn",e)}stopPushToTalk(){this.commitPushToTalk()}cancelPushToTalk(){if(this.assertPushToTalkAvailable(),this.turnId===null)return this.turnControl;let e=String(this.turnId);return this.turnId=null,this.sendInputControl("cancel_turn",e)}async startInternal(e,t){this.activeInputMode=e.inputMode??"push-to-talk",this.setStatus("connecting");let n=new AbortController;this.requestAbort=n;let i=e.sessionId??crypto.randomUUID(),o=crypto.randomUUID(),r=this.options.startSession,l=r?void 0:this.options.sessionContext,c=r?void 0:JSON.stringify({chat_session_id:i,input_mode:this.activeInputMode==="push-to-talk"?"push_to_talk":"automatic",...e.overrides?{overrides:e.overrides}:{},...l?.userName?.trim()?{user_name:l.userName}:{},...l?.timeZone?.trim()?{time_zone:l.timeZone}:{}}),a=()=>this.options.startSession?Promise.resolve(this.options.startSession({agentId:this.options.agentId,inputMode:this.activeInputMode,requestId:o,sessionId:i,reportedSource:this.options.reportedSource??"SDK",...e.overrides?{overrides:e.overrides}:{},signal:n.signal})):this.resolveSessionToken().then(h=>fetch(this.apiOrigin+"/v1/agents/"+encodeURIComponent(this.options.agentId)+"/sessions",{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json",Authorization:"Bearer "+h,"Idempotency-Key":o,"X-Animated-Waffle-Reported-Source":this.options.reportedSource??"SDK"},signal:n.signal,body:c})),p;try{p=await this.withTimeout(r?a():a().catch(h=>{if(n.signal.aborted)throw h;return a()}),ge,"Timed out creating the Waffle session.",()=>n.abort())}catch(h){let w=E(h,"session","Could not create session.");throw t!==this.lifecycleVersion?new d("The connection attempt was cancelled."):(this.fail(w),w)}finally{this.requestAbort===n&&(this.requestAbort=null)}if(t!==this.lifecycleVersion)throw new d("The connection attempt was cancelled.");let g=await p.json().catch(()=>null);if(!p.ok){let h=R(g),w=new d(A(h?.detail)??"Could not create session.",p.status,A(h?.code));throw this.fail(w),w}let v;try{v=je(g,this.activeInputMode,this.options.agentId,typeof this.options.developerFeedback=="function")}catch(h){let w=E(h,"session","Could not parse the Waffle session.");throw this.fail(w),w}this.currentSession?.id!==v.id&&this.clearDeveloperFeedbackGrant(),this.currentSession=v,this.emit("session",H(v)),x(this,"presentation",v.avatar),this.setStatus("connecting");let y,b,u=new Promise((h,w)=>{y=h,b=w}),k,M=new Promise((h,w)=>{k=w});this.readyWaiter={reject:h=>{b(h),k(h)},resolve:y,version:t};let _=this.room,m=!0,S=()=>m&&t===this.lifecycleVersion&&_===this.room&&this.currentStatus==="connecting",I=async()=>{if(await _.connect(v.room,v.token).catch(h=>{throw E(h,"transport","Could not connect to the Agent room.")}),!S())throw await _.disconnect().catch(()=>{}),new d("The connection attempt was cancelled.");if(this.options.avatarMotion||await _.startAudio().catch(h=>{throw E(h,"audio","Could not start Agent audio.")}),!S())throw new d("The connection attempt was cancelled.");if(this.options.inputAudioEnabled!==!1){let h=await _.localParticipant.setMicrophoneEnabled(!0).catch($=>{throw E($,"audio","Could not start the microphone.")}),w=h?.audioTrack?.mediaStreamTrack;if(!S())throw w?.stop(),await _.disconnect().catch(()=>{}),new d("The connection attempt was cancelled.");if(!h||h.isMuted||h.isUpstreamPaused||!w||!w.enabled||w.muted||w.readyState!=="live")throw new d("The microphone track did not publish.",void 0,void 0,"audio")}for(let h of _.remoteParticipants.values())this.handleParticipant(h)};try{await Promise.race([Promise.all([I().then(()=>this.withTimeout(u,Oe,"Timed out waiting for the Waffle Agent.",()=>{_.disconnect().catch(()=>{})},"transport")),u]),M])}catch(h){m=!1,this.readyWaiter?.version===t&&(this.readyWaiter=null),this.currentStatus!=="disconnected"&&await _.disconnect().catch(()=>{});let w=E(h,"transport","Could not connect.");throw t!==this.lifecycleVersion?new d("The connection attempt was cancelled."):(this.fail(w),w)}finally{m=!1,this.readyWaiter?.version===t&&(this.readyWaiter=null)}if(t!==this.lifecycleVersion)throw new d("The connection attempt was cancelled.");return this.setStatus("connected"),H(v)}async stopInternal(){if(this.readyWaiter?.reject(new d("The connection attempt was cancelled.")),this.readyWaiter=null,this.requestAbort?.abort(),this.turnId=null,this.turnControl=Promise.resolve(),this.failedStartTurnKey=null,this.agentIdentity=null,this.clearTranscriptProgress(),this.stopAudio(),this.currentStatus!=="disconnected"){this.setStatus("disconnecting");try{await this.room.disconnect()}finally{this.emit("agentSpeaking",!1),this.setStatus("disconnected")}}}sendInputControl(e,t){if(this.activeInputMode!=="push-to-talk")throw new d('Push-to-talk controls require inputMode: "push-to-talk".');this.assertConnected();let n=this.agentIdentity;if(!n)throw new d("The Waffle Agent is not ready.",void 0,"push_to_talk_agent_unavailable","transport");let i=this.room,o=this.turnControl.then(()=>{if(e!=="start_turn"&&this.failedStartTurnKey===t){this.failedStartTurnKey=null;return}return this.performControlRpc(i,n,e,t)});return this.turnControl=o.then(()=>{},r=>{e==="start_turn"&&(this.failedStartTurnKey=t),this.turnId!==null&&String(this.turnId)===t&&(this.turnId=null),this.emit("error",qe(r,e))}),this.turnControl}async performControlRpc(e,t,n,i){let o=JSON.stringify({turnId:i}),r=n==="start_turn"?2:1,l;for(let c=1;c<=r;c+=1)try{return await e.localParticipant.performRpc({destinationIdentity:t,method:n,payload:o})}catch(a){if(l=a,c<r&&De(a))continue;throw a}throw l}assertPushToTalkAvailable(){if(this.options.inputAudioEnabled===!1)throw new d("Push-to-talk requires input audio to be enabled.",void 0,"input_audio_disabled")}assertConnected(){if(this.status!=="connected")throw new d("Start the Waffle session before sending input.")}async requestDeveloperFeedback(e,t){if(typeof this.options.developerFeedback!="function")throw new d("Developer Feedback is not enabled for this client.",void 0,"developer_feedback_disabled");let n=await this.resolveDeveloperFeedbackGrant(),i;try{i=await fetch(n.endpoint,{method:e,credentials:"omit",cache:"no-store",headers:{Accept:"application/json",Authorization:`Bearer ${n.token}`,...t?{"Content-Type":"application/json"}:{}},...t?{body:JSON.stringify(t)}:{}})}catch{throw new d("Could not save Developer Feedback.",void 0,"developer_feedback_network_error")}let o=await i.json().catch(()=>null);if(!i.ok){let p=R(o);throw new d(A(p?.detail)??"Could not save Developer Feedback.",i.status,A(p?.code))}let r=R(o),l=r?.round_id,c=r?.pending_count,a=r?.dashboard_url;if(l!==null&&(typeof l!="string"||!z.test(l))||typeof c!="number"||!Number.isSafeInteger(c)||c<0||a!==null&&typeof a!="string")throw new d("Developer Feedback response is invalid.");return typeof a=="string"&&ye(a,"dashboard_url"),{roundId:l,pendingCount:c,dashboardUrl:a}}resolveDeveloperFeedbackGrant(){let e=this.currentSession,t=this.options.developerFeedback;if(!e||typeof t!="function")throw new d("Start a Developer Feedback-enabled Waffle session first.",void 0,"developer_feedback_unavailable");if(this.developerFeedbackGrant?.sourceSessionId===e.id&&Date.parse(this.developerFeedbackGrant.expiresAt)>Date.now())return Promise.resolve(this.developerFeedbackGrant);if(this.developerFeedbackGrantPromise)return this.developerFeedbackGrantPromise;let n=new AbortController;this.developerFeedbackGrantAbort=n;let i=this.withTimeout(Promise.resolve(t({agentId:e.agentId,sourceSessionId:e.id,signal:n.signal})),ge,"Timed out requesting a Developer Feedback grant.",()=>n.abort()).then(async o=>{let r=await o.json().catch(()=>null);if(!o.ok){let a=R(r);throw new d(A(a?.detail)??"Could not authorize Developer Feedback.",o.status,A(a?.code))}let l=R(r),c={endpoint:ye(l?.endpoint,"developerFeedback.endpoint"),token:T(l?.token,"developerFeedback.token"),expiresAt:Be(l?.expires_at,"developer_feedback.expires_at"),sourceSessionId:T(l?.source_session_id,"developer_feedback.source_session_id")};if(c.sourceSessionId!==e.id)throw new d("Developer Feedback grant targets a different Session.");if(Date.parse(c.expiresAt)<=Date.now())throw new d("Developer Feedback grant is already expired.");if(this.currentSession?.id!==e.id)throw new d("The Waffle session changed while authorizing feedback.");return this.developerFeedbackGrant=c,c}).finally(()=>{this.developerFeedbackGrantPromise===i&&(this.developerFeedbackGrantPromise=null),this.developerFeedbackGrantAbort===n&&(this.developerFeedbackGrantAbort=null)});return this.developerFeedbackGrantPromise=i,i}handleParticipant(e){if(e.kind===N.AGENT){if(this.agentIdentity=e.identity,this.motionAgentIdentity||this.emit("agentSpeaking",e.attributes["lk.agent.state"]==="speaking"),e.attributes["animated-waffle.runtime.ready"]==="true"){if(this.options.avatarMotion===!0&&(this.motionAgentIdentity!==e.identity||e.attributes["animated-waffle.avatar.motion"]!==W)){this.readyWaiter?.reject(new d("The voice service did not negotiate avatar motion.",void 0,"avatar_motion_unsupported"));return}if(this.options.application&&e.attributes["animated-waffle.application.version"]!=="2"){this.readyWaiter?.reject(new d("The voice service does not support application sessions.",void 0,"application_session_unsupported"));return}this.readyWaiter?.resolve()}for(let t of e.audioTrackPublications.values()){let n=t.track;if(n?.kind===K.Kind.Audio){this.setAudioTrack(n.mediaStreamTrack);break}}}}handleDisconnected(e){this.readyWaiter?.reject(new d(e,void 0,void 0,"transport")),this.readyWaiter=null,this.turnId=null,this.turnControl=Promise.resolve(),this.failedStartTurnKey=null,this.agentIdentity=null,this.clearTranscriptProgress(),this.stopAudio(),this.emit("agentSpeaking",!1),this.setStatus("disconnected")}setAudioTrack(e){if(this.options.avatarMotion||e===this.currentRemoteAudioTrack||(this.stopAudio(!1),this.currentRemoteAudioTrack=e,this.emit("remoteAudioTrack",e),x(this,"audioTrack",e),!e||typeof document>"u")||this.options.audioPlayback===!1)return;let t=document.createElement("audio");t.autoplay=!0,t.muted=this.audioMuted,t.setAttribute("playsinline",""),t.srcObject=new MediaStream([e]),this.audio=t,t.play().catch(n=>{this.emit("error",E(n,"audio","Browser blocked agent audio."))})}stopAudioElement(){this.audio&&(this.audio.pause(),this.audio.srcObject=null,this.audio=null)}stopAudio(e=!0){if(this.stopAudioElement(),e){let t=this.motionSync;this.motionSync=null,t?.dispose(),this.motionPreparing=null,this.motionFeedback=[],this.motionInterruptBarrier?.reject(new Error("Paired playback stopped before interruption was acknowledged.")),this.motionInterruptBarrier=null,this.motionAgentIdentity=null,x(this,"motionMode",!1)}e&&this.currentRemoteAudioTrack&&(this.currentRemoteAudioTrack=null,this.emit("remoteAudioTrack",null),x(this,"audioTrack",null))}waitForMotionParticipant(e,t){return new Promise(n=>{let i=!1,o=()=>{i||(i=!0,clearTimeout(c),e.off(C.ParticipantConnected,r),e.off(C.Disconnected,o),l(),n())},r=a=>{a.identity===t&&o()},l=this.on("connectionState",a=>{a!=="connecting"&&o()}),c=setTimeout(o,8e3);e.on(C.ParticipantConnected,r),e.on(C.Disconnected,o),(e!==this.room||this.currentStatus!=="connecting"||e.remoteParticipants.has(t))&&o()})}prepareMotionSync(){if(this.motionPreparing)return this.motionPreparing;let e=new j({frame:t=>x(this,"motion",t),caption:t=>this.emit("transcriptProgress",t),progress:t=>{if(this.motionSync!==e)return;this.emit("agentSpeaking",t.state!=="stopped"&&t.replies.some(r=>r.state==="playing"));let n=this.motionFeedback.at(-1),i=r=>JSON.stringify([r.id,r.replies.map(l=>[l.id,l.state])]),o={progress:t,revision:++this.motionFeedbackRevision};if(n&&i(n.progress)===i(t)?this.motionFeedback[this.motionFeedback.length-1]=o:this.motionFeedback.push(o),this.motionFeedback.length>64){this.failMotion(new Error("paired_feedback_overflow"));return}this.motionFeedbackSending||this.sendMotionFeedback().catch(()=>{})},fail:t=>{this.motionSync===e&&this.failMotion(t)}});return e.setMuted(this.audioMuted),this.motionSync=e,this.motionPreparing=e.prepare()}sendMotionFeedback(){if(this.motionFeedbackSending)return this.motionFeedbackSending;let e=this.room,t=this.motionAgentIdentity,n=this.motionSync;if(!t||!n)return Promise.resolve();let o=(async()=>{try{for(;this.motionFeedback.length&&e===this.room&&t===this.motionAgentIdentity&&n===this.motionSync;){let{progress:r,revision:l}=this.motionFeedback.shift();await e.localParticipant.performRpc({destinationIdentity:t,method:ae,payload:JSON.stringify(r),responseTimeout:5e3}),this.motionFeedbackAcknowledged=Math.max(this.motionFeedbackAcknowledged,l),this.motionInterruptBarrier&&this.motionInterruptBarrier.revision<=this.motionFeedbackAcknowledged&&(this.motionInterruptBarrier.resolve(),this.motionInterruptBarrier=null)}}catch(r){if(e===this.room&&n===this.motionSync)throw this.failMotion(r),r}})().finally(()=>{this.motionFeedbackSending===o&&(this.motionFeedbackSending=null,this.motionFeedback.length&&this.sendMotionFeedback().catch(()=>{}))});return this.motionFeedbackSending=o,o}waitForMotionInterruption(){let e=this.motionFeedbackRevision;if(e<=this.motionFeedbackAcknowledged)return Promise.resolve();if(this.motionInterruptBarrier)return this.motionInterruptBarrier.revision=e,this.motionInterruptBarrier.promise;let t,n,i=new Promise((o,r)=>{t=o,n=r});return this.motionInterruptBarrier={revision:e,promise:i,resolve:t,reject:n},this.motionFeedbackSending||this.sendMotionFeedback().catch(()=>{}),i}failMotion(e){if(!this.motionSync)return;let t=this.room;this.fail(new d("Paired audio and avatar playback stopped. Reconnect to resume.",void 0,"avatar_playback_failed","audio",{cause:e})),t.disconnect().catch(()=>{})}completeTranscriptExecution(e){this.completedProgressExecutions.add(e);for(let[t,n]of this.progressText)this.progressExecutions.get(t)===e&&(this.emit("transcriptProgress",{...n,complete:!0}),this.progressExecutions.delete(t),this.progressText.delete(t))}clearTranscriptProgress(){this.progressExecutions.clear(),this.progressText.clear(),this.completedProgressExecutions.clear()}setStatus(e){e==="disconnected"&&(this.recreateRoomOnConnect=!0),e==="disconnected"&&this.currentSession&&(this.clearDeveloperFeedbackGrant(),this.currentSession=null,this.emit("session",null),x(this,"presentation",null)),this.currentStatus!==e&&(this.currentStatus=e,this.emit("connectionState",e),this.emit("status",e))}clearDeveloperFeedbackGrant(){this.developerFeedbackGrantAbort?.abort(),this.developerFeedbackGrantAbort=null,this.developerFeedbackGrantPromise=null,this.developerFeedbackGrant=null}fail(e){this.readyWaiter?.reject(e),this.readyWaiter=null,this.stopAudio(),this.emit("agentSpeaking",!1),this.setStatus("disconnected"),this.emit("error",e)}async resolveSessionToken(){if(!this.options.sessionToken)throw new d("No session token provider is configured.");let e=await this.options.sessionToken();if(!e.trim())throw new d("sessionToken must not be empty.",void 0,"invalid_session_token");return e}withTimeout(e,t,n,i,o="session"){return new Promise((r,l)=>{let c=setTimeout(()=>{i?.(),l(new d(n,void 0,void 0,o))},t);e.then(a=>{clearTimeout(c),r(a)},a=>{clearTimeout(c),l(a)})})}emit(e,t){this.listeners.get(e)?.forEach(n=>{n(t)})}};export{be as WaffleClient,d as WaffleError};
|
package/dist/internal.d.ts
CHANGED
|
@@ -8,6 +8,12 @@ interface InternalAvatarEvent {
|
|
|
8
8
|
audioTime?: number;
|
|
9
9
|
offset?: number;
|
|
10
10
|
}
|
|
11
|
+
/** The audible frame of a generated reply, in the avatar-motion frame layout. */
|
|
12
|
+
interface InternalAvatarMotionFrame {
|
|
13
|
+
id: string;
|
|
14
|
+
index: number;
|
|
15
|
+
values: Float32Array;
|
|
16
|
+
}
|
|
11
17
|
interface InternalAvatarPresentation {
|
|
12
18
|
id: string;
|
|
13
19
|
name: string;
|
|
@@ -25,9 +31,11 @@ interface InternalWaffleEvents {
|
|
|
25
31
|
avatar: InternalAvatarEvent;
|
|
26
32
|
audioTrack: MediaStreamTrack | null;
|
|
27
33
|
presentation: InternalAvatarPresentation | null;
|
|
34
|
+
motion: InternalAvatarMotionFrame | null;
|
|
35
|
+
motionMode: boolean;
|
|
28
36
|
}
|
|
29
37
|
type InternalListener<K extends keyof InternalWaffleEvents> = (payload: InternalWaffleEvents[K]) => void;
|
|
30
38
|
declare function subscribeInternal<K extends keyof InternalWaffleEvents>(owner: object, event: K, listener: InternalListener<K>): () => void;
|
|
31
39
|
declare function emitInternal<K extends keyof InternalWaffleEvents>(owner: object, event: K, payload: InternalWaffleEvents[K]): void;
|
|
32
40
|
|
|
33
|
-
export { type InternalAvatarEvent, type InternalAvatarPresentation, type InternalWaffleEvents, emitInternal, subscribeInternal };
|
|
41
|
+
export { type InternalAvatarEvent, type InternalAvatarMotionFrame, type InternalAvatarPresentation, type InternalWaffleEvents, emitInternal, subscribeInternal };
|
package/dist/internal.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a,b}from"./chunk-
|
|
1
|
+
import{a,b}from"./chunk-4OFU2T5E.js";export{b as emitInternal,a as subscribeInternal};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@animated-waffle/client",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1-run170.3",
|
|
4
4
|
"description": "Framework-independent browser SDK for Narya Animated Waffle talking Agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
},
|
|
32
32
|
"types": "./dist/index.d.ts",
|
|
33
33
|
"publishConfig": {
|
|
34
|
-
"access": "public"
|
|
34
|
+
"access": "public",
|
|
35
|
+
"tag": "run170"
|
|
35
36
|
},
|
|
36
37
|
"dependencies": {
|
|
37
38
|
"livekit-client": "2.21.0",
|
package/dist/chunk-BXRNROI7.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var o=new WeakMap;function s(n,t,a){let e=o.get(n)??new Map,r=e.get(t)??new Set;return r.add(a),e.set(t,r),o.set(n,e),()=>r.delete(a)}function i(n,t,a){o.get(n)?.get(t)?.forEach(e=>{e(a)})}export{s as a,i as b};
|